# Used to generate tests/run/test_dataclasses.pyx but translating the CPython test suite # dataclass file. Initially run using Python 3.10 - this file is not designed to be # backwards compatible since it will be run manually and infrequently. import ast import os.path import sys unavailable_functions = frozenset( { "dataclass_textanno", # part of CPython test module "dataclass_module_1", # part of CPython test module "make_dataclass", # not implemented in Cython dataclasses (probably won't be implemented) } ) skip_tests = frozenset( { # needs Cython compile # ==================== ("TestCase", "test_field_default_default_factory_error"), ("TestCase", "test_two_fields_one_default"), ("TestCase", "test_overwrite_hash"), ("TestCase", "test_eq_order"), ("TestCase", "test_no_unhashable_default"), ("TestCase", "test_disallowed_mutable_defaults"), ("TestCase", "test_classvar_default_factory"), ("TestCase", "test_field_metadata_mapping"), ("TestFieldNoAnnotation", "test_field_without_annotation"), ( "TestFieldNoAnnotation", "test_field_without_annotation_but_annotation_in_base", ), ( "TestFieldNoAnnotation", "test_field_without_annotation_but_annotation_in_base_not_dataclass", ), ("TestOrdering", "test_overwriting_order"), ("TestHash", "test_hash_rules"), ("TestHash", "test_hash_no_args"), ("TestFrozen", "test_inherit_nonfrozen_from_empty_frozen"), ("TestFrozen", "test_inherit_nonfrozen_from_frozen"), ("TestFrozen", "test_inherit_frozen_from_nonfrozen"), ("TestFrozen", "test_overwriting_frozen"), ("TestSlots", "test_add_slots_when_slots_exists"), ("TestSlots", "test_cant_inherit_from_iterator_slots"), ("TestSlots", "test_weakref_slot_without_slot"), ("TestKeywordArgs", "test_no_classvar_kwarg"), ("TestKeywordArgs", "test_KW_ONLY_twice"), ("TestKeywordArgs", "test_defaults"), # uses local variable in class definition ("TestCase", "test_default_factory"), ("TestCase", "test_default_factory_with_no_init"), ("TestCase", "test_field_default"), ("TestCase", "test_function_annotations"), ("TestDescriptors", "test_lookup_on_instance"), ("TestCase", "test_default_factory_not_called_if_value_given"), ("TestCase", "test_class_attrs"), ("TestCase", "test_hash_field_rules"), ("TestStringAnnotations",), # almost all the texts here use local variables # Currently unsupported # ===================== ( "TestOrdering", "test_functools_total_ordering", ), # combination of cython dataclass and total_ordering ("TestCase", "test_missing_default_factory"), # we're MISSING MISSING ("TestCase", "test_missing_default"), # MISSING ("TestCase", "test_missing_repr"), # MISSING ("TestSlots",), # __slots__ isn't understood ("TestMatchArgs",), ("TestKeywordArgs", "test_field_marked_as_kwonly"), ("TestKeywordArgs", "test_match_args"), ("TestKeywordArgs", "test_KW_ONLY"), ("TestKeywordArgs", "test_KW_ONLY_as_string"), ("TestKeywordArgs", "test_post_init"), ( "TestCase", "test_class_var_frozen", ), # __annotations__ not present on cdef classes https://github.com/cython/cython/issues/4519 ("TestCase", "test_dont_include_other_annotations"), # __annotations__ ("TestDocString",), # don't think cython dataclasses currently set __doc__ # either cython.dataclasses.field or cython.dataclasses.dataclass called directly as functions # (will probably never be supported) ("TestCase", "test_field_repr"), ("TestCase", "test_dynamic_class_creation"), ("TestCase", "test_dynamic_class_creation_using_field"), # Requires inheritance from non-cdef class ("TestCase", "test_is_dataclass_genericalias"), ("TestCase", "test_generic_extending"), ("TestCase", "test_generic_dataclasses"), ("TestCase", "test_generic_dynamic"), ("TestInit", "test_inherit_from_protocol"), ("TestAbstract", "test_abc_implementation"), ("TestAbstract", "test_maintain_abc"), # Requires multiple inheritance from extension types ("TestCase", "test_post_init_not_auto_added"), # Refers to nonlocal from enclosing function ( "TestCase", "test_post_init_staticmethod", ), # TODO replicate the gist of the test elsewhere # PEP487 isn't support in Cython ("TestDescriptors", "test_non_descriptor"), ("TestDescriptors", "test_set_name"), ("TestDescriptors", "test_setting_field_calls_set"), ("TestDescriptors", "test_setting_uninitialized_descriptor_field"), # Looks up __dict__, which cdef classes don't typically have ("TestCase", "test_init_false_no_default"), ("TestCase", "test_init_var_inheritance"), # __dict__ again ("TestCase", "test_base_has_init"), ("TestInit", "test_base_has_init"), # needs __dict__ for vars # Requires arbitrary attributes to be writeable ("TestCase", "test_post_init_super"), ('TestCase', 'test_init_in_order'), # Cython being strict about argument types - expected difference ("TestDescriptors", "test_getting_field_calls_get"), ("TestDescriptors", "test_init_calls_set"), ("TestHash", "test_eq_only"), # I think an expected difference with cdef classes - the property will be in the dict ("TestCase", "test_items_in_dicts"), # These tests are probably fine, but the string substitution in this file doesn't get it right ("TestRepr", "test_repr"), ("TestCase", "test_not_in_repr"), ('TestRepr', 'test_no_repr'), # class variable doesn't exist in Cython so uninitialized variable appears differently - for now this is deliberate ('TestInit', 'test_no_init'), # I believe the test works but the ordering functions do appear in the class dict (and default slot wrappers which # just raise NotImplementedError ('TestOrdering', 'test_no_order'), # not possible to add attributes on extension types ("TestCase", "test_post_init_classmethod"), # Cannot redefine the same field in a base dataclass (tested in dataclass_e6) ("TestCase", "test_field_order"), ( "TestCase", "test_overwrite_fields_in_derived_class", ), # Bugs #====== # not specifically a dataclass issue - a C int crashes classvar ("TestCase", "test_class_var"), ( "TestFrozen", ), # raises AttributeError, not FrozenInstanceError (may be hard to fix) ('TestCase', 'test_post_init'), # Works except for AttributeError instead of FrozenInstanceError ("TestReplace", "test_frozen"), # AttributeError not FrozenInstanceError ( "TestCase", "test_dataclasses_qualnames", ), # doesn't define __setattr__ and just relies on Cython to enforce readonly properties ("TestCase", "test_compare_subclasses"), # wrong comparison ("TestCase", "test_simple_compare"), # wrong comparison ( "TestCase", "test_field_named_self", ), # I think just an error in inspecting the signature ( "TestCase", "test_init_var_default_factory", ), # should be raising a compile error ("TestCase", "test_init_var_no_default"), # should be raising a compile error ("TestCase", "test_init_var_with_default"), # not sure... ("TestReplace", "test_initvar_with_default_value"), # needs investigating # Maybe bugs? # ========== # non-default argument 'z' follows default argument in dataclass __init__ - this message looks right to me! ("TestCase", "test_class_marker"), # cython.dataclasses.field parameter 'metadata' must be a literal value - possibly not something we can support? ("TestCase", "test_field_metadata_custom_mapping"), ( "TestCase", "test_class_var_default_factory", ), # possibly to do with ClassVar being assigned a field ( "TestCase", "test_class_var_with_default", ), # possibly to do with ClassVar being assigned a field ( "TestDescriptors", ), # mostly don't work - I think this may be a limitation of cdef classes but needs investigating } ) version_specific_skips = { # The version numbers are the first version that the test should be run on ("TestCase", "test_init_var_preserve_type"): ( 3, 10, ), # needs language support for | operator on types } class DataclassInDecorators(ast.NodeVisitor): found = False def visit_Name(self, node): if node.id == "dataclass": self.found = True return self.generic_visit(node) def generic_visit(self, node): if self.found: return # skip return super().generic_visit(node) def dataclass_in_decorators(decorator_list): finder = DataclassInDecorators() for dec in decorator_list: finder.visit(dec) if finder.found: return True return False class SubstituteNameString(ast.NodeTransformer): def __init__(self, substitutions): super().__init__() self.substitutions = substitutions def visit_Constant(self, node): # attempt to handle some difference in class names # (note: requires Python>=3.8) if isinstance(node.value, str): if node.value.find("") != -1: import re new_value = new_value2 = re.sub("[\w.]*", "", node.value) for key, value in self.substitutions.items(): new_value2 = re.sub(f"(?