def attr_class_maker_callback(ctx: 'mypy.plugin.ClassDefContext', auto_attribs_default: bool = False) -> None: """Add necessary dunder methods to classes decorated with attr.s. attrs is a package that lets you define classes without writing dull boilerplate code. At a quick glance, the decorator searches the class body for assignments of `attr.ib`s (or annotated variables if auto_attribs=True), then depending on how the decorator is called, it will add an __init__ or all the __cmp__ methods. For frozen=True it will turn the attrs into properties. See http://www.attrs.org/en/stable/how-does-it-work.html for information on how attrs works. """ info = ctx.cls.info init = _get_decorator_bool_argument(ctx, 'init', True) frozen = _get_frozen(ctx) order = _determine_eq_order(ctx) auto_attribs = _get_decorator_bool_argument(ctx, 'auto_attribs', auto_attribs_default) kw_only = _get_decorator_bool_argument(ctx, 'kw_only', False) if ctx.api.options.python_version[0] < 3: if auto_attribs: ctx.api.fail("auto_attribs is not supported in Python 2", ctx.reason) return if not info.defn.base_type_exprs: # Note: This will not catch subclassing old-style classes. ctx.api.fail("attrs only works with new-style classes", info.defn) return if kw_only: ctx.api.fail(KW_ONLY_PYTHON_2_UNSUPPORTED, ctx.reason) return attributes = _analyze_class(ctx, auto_attribs, kw_only) # Check if attribute types are ready. for attr in attributes: node = info.get(attr.name) if node is None: # This name is likely blocked by a star import. We don't need to defer because # defer() is already called by mark_incomplete(). return if node.type is None and not ctx.api.final_iteration: ctx.api.defer() return # Save the attributes so that subclasses can reuse them. ctx.cls.info.metadata['attrs'] = { 'attributes': [attr.serialize() for attr in attributes], 'frozen': frozen, } adder = MethodAdder(ctx) if init: _add_init(ctx, attributes, adder) if order: _add_order(ctx, adder) if frozen: _make_frozen(ctx, attributes)
def attr_class_maker_callback(ctx: 'mypy.plugin.ClassDefContext', auto_attribs_default: bool = False) -> None: """Add necessary dunder methods to classes decorated with attr.s. attrs is a package that lets you define classes without writing dull boilerplate code. At a quick glance, the decorator searches the class body for assignments of `attr.ib`s (or annotated variables if auto_attribs=True), then depending on how the decorator is called, it will add an __init__ or all the __cmp__ methods. For frozen=True it will turn the attrs into properties. See http://www.attrs.org/en/stable/how-does-it-work.html for information on how attrs works. """ info = ctx.cls.info init = _get_decorator_bool_argument(ctx, 'init', True) frozen = _get_frozen(ctx) cmp = _get_decorator_bool_argument(ctx, 'cmp', True) auto_attribs = _get_decorator_bool_argument(ctx, 'auto_attribs', auto_attribs_default) kw_only = _get_decorator_bool_argument(ctx, 'kw_only', False) if ctx.api.options.python_version[0] < 3: if auto_attribs: ctx.api.fail("auto_attribs is not supported in Python 2", ctx.reason) return if not info.defn.base_type_exprs: # Note: This will not catch subclassing old-style classes. ctx.api.fail("attrs only works with new-style classes", info.defn) return if kw_only: ctx.api.fail(KW_ONLY_PYTHON_2_UNSUPPORTED, ctx.reason) return attributes = _analyze_class(ctx, auto_attribs, kw_only) if ctx.api.options.new_semantic_analyzer: # Check if attribute types are ready. for attr in attributes: if info[attr.name].type is None and not ctx.api.final_iteration: ctx.api.defer() return # Save the attributes so that subclasses can reuse them. ctx.cls.info.metadata['attrs'] = { 'attributes': [attr.serialize() for attr in attributes], 'frozen': frozen, } adder = MethodAdder(ctx) if init: _add_init(ctx, attributes, adder) if cmp: _add_cmp(ctx, adder) if frozen: _make_frozen(ctx, attributes)
def _make_dataclassy(ctx: ClassDefContext) -> None: """ This class has the @dataclassy decorator. It is going to be the root-class of a dataclassy hierarchy. """ cls = ctx.cls info = cls.info name = cls.name bases = info.bases # Get the decorator arguments args_dict = { a: _get_decorator_bool_argument(ctx, a, d) for (a, d) in ClassyArgs._field_defaults.items() } args = ClassyArgs(**args_dict) # Then the fields fields = {f.name: f for f in _gather_attributes(cls)} # Finally, annotate the thing classy = ClassyInfo(fields, args, is_root=True) _munge_dataclassy(ctx, classy)
def attr_class_maker_callback(ctx: 'mypy.plugin.ClassDefContext', auto_attribs_default: bool = False) -> None: """Add necessary dunder methods to classes decorated with attr.s. attrs is a package that lets you define classes without writing dull boilerplate code. At a quick glance, the decorator searches the class body for assignments of `attr.ib`s (or annotated variables if auto_attribs=True), then depending on how the decorator is called, it will add an __init__ or all the __cmp__ methods. For frozen=True it will turn the attrs into properties. See http://www.attrs.org/en/stable/how-does-it-work.html for information on how attrs works. """ info = ctx.cls.info init = _get_decorator_bool_argument(ctx, 'init', True) frozen = _get_frozen(ctx) cmp = _get_decorator_bool_argument(ctx, 'cmp', True) auto_attribs = _get_decorator_bool_argument(ctx, 'auto_attribs', auto_attribs_default) if ctx.api.options.python_version[0] < 3: if auto_attribs: ctx.api.fail("auto_attribs is not supported in Python 2", ctx.reason) return if not info.defn.base_type_exprs: # Note: This will not catch subclassing old-style classes. ctx.api.fail("attrs only works with new-style classes", info.defn) return attributes = _analyze_class(ctx, auto_attribs) # Save the attributes so that subclasses can reuse them. ctx.cls.info.metadata['attrs'] = { 'attributes': [attr.serialize() for attr in attributes], 'frozen': frozen, } adder = MethodAdder(ctx) if init: _add_init(ctx, attributes, adder) if cmp: _add_cmp(ctx, adder) if frozen: _make_frozen(ctx, attributes)
def _get_frozen(ctx: 'mypy.plugin.ClassDefContext', frozen_default: bool) -> bool: """Return whether this class is frozen.""" if _get_decorator_bool_argument(ctx, 'frozen', frozen_default): return True # Subclasses of frozen classes are frozen so check that. for super_info in ctx.cls.info.mro[1:-1]: if 'attrs' in super_info.metadata and super_info.metadata['attrs']['frozen']: return True return False
def _get_frozen(ctx: 'mypy.plugin.ClassDefContext') -> bool: """Return whether this class is frozen.""" if _get_decorator_bool_argument(ctx, 'frozen', False): return True # Subclasses of frozen classes are frozen so check that. for super_info in ctx.cls.info.mro[1:-1]: if 'attrs' in super_info.metadata and super_info.metadata['attrs']['frozen']: return True return False
def transform(self) -> None: self.dclass.transform() self._add_self_tvar_expr(self._ctx) jsonschema = _get_decorator_bool_argument(self._ctx, "jsonschema", True) if jsonschema: self.add_schema_method() self.add_primitive_method() self.add_transmute_method() self.add_validate_method() self.add_json_method() self.add_translate_method()
def transform(self) -> None: """Apply all the necessary transformations to the underlying dataclass so as to ensure it is fully type checked according to the rules in PEP 557. """ ctx = self._ctx info = self._ctx.cls.info attributes = self.collect_attributes() if attributes is None: # Some definitions are not ready, defer() should be already called. return for attr in attributes: if attr.type is None: ctx.api.defer() return decorator_arguments = { 'init': _get_decorator_bool_argument(self._ctx, 'init', True), 'eq': _get_decorator_bool_argument(self._ctx, 'eq', True), 'order': _get_decorator_bool_argument(self._ctx, 'order', False), 'frozen': _get_decorator_bool_argument(self._ctx, 'frozen', False), } if info.get('replace') is None: obj_type = ctx.api.named_type('__builtins__.object') self_tvar_expr = TypeVarExpr(SELF_UVAR_NAME, info.fullname + '.' + SELF_UVAR_NAME, [], obj_type) info.names[SELF_UVAR_NAME] = SymbolTableNode(MDEF, self_tvar_expr) replace_tvar_def = TypeVarDef(SELF_UVAR_NAME, info.fullname + '.' + SELF_UVAR_NAME, -1, [], fill_typevars(info)) replace_other_type = TypeVarType(replace_tvar_def) add_method(ctx, 'replace', args=[ Argument( Var('changes', AnyType(TypeOfAny.explicit)), AnyType(TypeOfAny.explicit), None, ARG_STAR2) ], return_type=replace_other_type, self_type=replace_other_type, tvar_def=replace_tvar_def) # If there are no attributes, it may be that the semantic analyzer has not # processed them yet. In order to work around this, we can simply skip generating # __init__ if there are no attributes, because if the user truly did not define any, # then the object default __init__ with an empty signature will be present anyway. if (decorator_arguments['init'] and ('__init__' not in info.names or info.names['__init__'].plugin_generated) and attributes): add_method( ctx, '__init__', args=[ attr.to_argument() for attr in attributes if attr.is_in_init ], return_type=NoneType(), ) if (decorator_arguments['eq'] and info.get('__eq__') is None or decorator_arguments['order']): # Type variable for self types in generated methods. obj_type = ctx.api.named_type('__builtins__.object') self_tvar_expr = TypeVarExpr(SELF_TVAR_NAME, info.fullname + '.' + SELF_TVAR_NAME, [], obj_type) info.names[SELF_TVAR_NAME] = SymbolTableNode(MDEF, self_tvar_expr) # Add <, >, <=, >=, but only if the class has an eq method. if decorator_arguments['order']: if not decorator_arguments['eq']: ctx.api.fail('eq must be True if order is True', ctx.cls) for method_name in ['__lt__', '__gt__', '__le__', '__ge__']: # Like for __eq__ and __ne__, we want "other" to match # the self type. obj_type = ctx.api.named_type('__builtins__.object') order_tvar_def = TypeVarDef( SELF_TVAR_NAME, info.fullname + '.' + SELF_TVAR_NAME, -1, [], obj_type) order_other_type = TypeVarType(order_tvar_def) order_return_type = ctx.api.named_type('__builtins__.bool') order_args = [ Argument(Var('other', order_other_type), order_other_type, None, ARG_POS) ] existing_method = info.get(method_name) if existing_method is not None and not existing_method.plugin_generated: assert existing_method.node ctx.api.fail( 'You may not have a custom %s method when order=True' % method_name, existing_method.node, ) add_method( ctx, method_name, args=order_args, return_type=order_return_type, self_type=order_other_type, tvar_def=order_tvar_def, ) if decorator_arguments['frozen']: self._freeze(attributes) self.reset_init_only_vars(info, attributes) info.metadata['dataclass'] = { 'attributes': [attr.serialize() for attr in attributes], 'frozen': decorator_arguments['frozen'], }
def transform(self) -> None: """Apply all the necessary transformations to the underlying dataclass so as to ensure it is fully type checked according to the rules in PEP 557. """ ctx = self._ctx info = self._ctx.cls.info attributes = self.collect_attributes() decorator_arguments = { 'init': _get_decorator_bool_argument(self._ctx, 'init', True), 'eq': _get_decorator_bool_argument(self._ctx, 'eq', True), 'order': _get_decorator_bool_argument(self._ctx, 'order', False), 'frozen': _get_decorator_bool_argument(self._ctx, 'frozen', False), } if decorator_arguments['init']: _add_method( ctx, '__init__', args=[ attr.to_argument(info) for attr in attributes if attr.is_in_init ], return_type=NoneTyp(), ) for stmt in self._ctx.cls.defs.body: # Fix up the types of classmethods since, by default, # they will be based on the parent class' init. if isinstance(stmt, Decorator) and stmt.func.is_class: func_type = stmt.func.type if isinstance(func_type, CallableType): func_type.arg_types[0] = self._ctx.api.class_type( self._ctx.cls.info) if isinstance(stmt, OverloadedFuncDef) and stmt.is_class: func_type = stmt.type if isinstance(func_type, Overloaded): class_type = ctx.api.class_type(ctx.cls.info) for item in func_type.items(): item.arg_types[0] = class_type if stmt.impl is not None: assert isinstance(stmt.impl, Decorator) if isinstance(stmt.impl.func.type, CallableType): stmt.impl.func.type.arg_types[0] = class_type # Add an eq method, but only if the class doesn't already have one. if decorator_arguments['eq'] and info.get('__eq__') is None: for method_name in ['__eq__', '__ne__']: # The TVar is used to enforce that "other" must have # the same type as self (covariant). Note the # "self_type" parameter to _add_method. obj_type = ctx.api.named_type('__builtins__.object') cmp_tvar_def = TypeVarDef('T', 'T', -1, [], obj_type) cmp_other_type = TypeVarType(cmp_tvar_def) cmp_return_type = ctx.api.named_type('__builtins__.bool') _add_method( ctx, method_name, args=[ Argument(Var('other', cmp_other_type), cmp_other_type, None, ARG_POS) ], return_type=cmp_return_type, self_type=cmp_other_type, tvar_def=cmp_tvar_def, ) # Add <, >, <=, >=, but only if the class has an eq method. if decorator_arguments['order']: if not decorator_arguments['eq']: ctx.api.fail('eq must be True if order is True', ctx.cls) for method_name in ['__lt__', '__gt__', '__le__', '__ge__']: # Like for __eq__ and __ne__, we want "other" to match # the self type. obj_type = ctx.api.named_type('__builtins__.object') order_tvar_def = TypeVarDef('T', 'T', -1, [], obj_type) order_other_type = TypeVarType(order_tvar_def) order_return_type = ctx.api.named_type('__builtins__.bool') order_args = [ Argument(Var('other', order_other_type), order_other_type, None, ARG_POS) ] existing_method = info.get(method_name) if existing_method is not None: assert existing_method.node ctx.api.fail( 'You may not have a custom %s method when order=True' % method_name, existing_method.node, ) _add_method( ctx, method_name, args=order_args, return_type=order_return_type, self_type=order_other_type, tvar_def=order_tvar_def, ) if decorator_arguments['frozen']: self._freeze(attributes) # Remove init-only vars from the class. for attr in attributes: if attr.is_init_var: del info.names[attr.name] info.metadata['dataclass'] = { 'attributes': OrderedDict((attr.name, attr.serialize()) for attr in attributes), 'frozen': decorator_arguments['frozen'], }
def transform(self) -> None: """Apply all the necessary transformations to the underlying dataclass so as to ensure it is fully type checked according to the rules in PEP 557. """ ctx = self._ctx info = self._ctx.cls.info attributes = self.collect_attributes() if ctx.api.options.new_semantic_analyzer: # Check if attribute types are ready. for attr in attributes: if info[attr.name].type is None: ctx.api.defer() return decorator_arguments = { 'init': _get_decorator_bool_argument(self._ctx, 'init', True), 'eq': _get_decorator_bool_argument(self._ctx, 'eq', True), 'order': _get_decorator_bool_argument(self._ctx, 'order', False), 'frozen': _get_decorator_bool_argument(self._ctx, 'frozen', False), } if decorator_arguments['init']: add_method( ctx, '__init__', args=[attr.to_argument(info) for attr in attributes if attr.is_in_init], return_type=NoneTyp(), ) if (decorator_arguments['eq'] and info.get('__eq__') is None or decorator_arguments['order']): # Type variable for self types in generated methods. obj_type = ctx.api.named_type('__builtins__.object') self_tvar_expr = TypeVarExpr(SELF_TVAR_NAME, info.fullname() + '.' + SELF_TVAR_NAME, [], obj_type) info.names[SELF_TVAR_NAME] = SymbolTableNode(MDEF, self_tvar_expr) # Add an eq method, but only if the class doesn't already have one. if decorator_arguments['eq'] and info.get('__eq__') is None: for method_name in ['__eq__', '__ne__']: # The TVar is used to enforce that "other" must have # the same type as self (covariant). Note the # "self_type" parameter to add_method. obj_type = ctx.api.named_type('__builtins__.object') cmp_tvar_def = TypeVarDef(SELF_TVAR_NAME, info.fullname() + '.' + SELF_TVAR_NAME, -1, [], obj_type) cmp_other_type = TypeVarType(cmp_tvar_def) cmp_return_type = ctx.api.named_type('__builtins__.bool') add_method( ctx, method_name, args=[Argument(Var('other', cmp_other_type), cmp_other_type, None, ARG_POS)], return_type=cmp_return_type, self_type=cmp_other_type, tvar_def=cmp_tvar_def, ) # Add <, >, <=, >=, but only if the class has an eq method. if decorator_arguments['order']: if not decorator_arguments['eq']: ctx.api.fail('eq must be True if order is True', ctx.cls) for method_name in ['__lt__', '__gt__', '__le__', '__ge__']: # Like for __eq__ and __ne__, we want "other" to match # the self type. obj_type = ctx.api.named_type('__builtins__.object') order_tvar_def = TypeVarDef(SELF_TVAR_NAME, info.fullname() + '.' + SELF_TVAR_NAME, -1, [], obj_type) order_other_type = TypeVarType(order_tvar_def) order_return_type = ctx.api.named_type('__builtins__.bool') order_args = [ Argument(Var('other', order_other_type), order_other_type, None, ARG_POS) ] existing_method = info.get(method_name) if existing_method is not None: assert existing_method.node ctx.api.fail( 'You may not have a custom %s method when order=True' % method_name, existing_method.node, ) add_method( ctx, method_name, args=order_args, return_type=order_return_type, self_type=order_other_type, tvar_def=order_tvar_def, ) if decorator_arguments['frozen']: self._freeze(attributes) # Remove init-only vars from the class. for attr in attributes: if attr.is_init_var: del info.names[attr.name] info.metadata['dataclass'] = { 'attributes': OrderedDict((attr.name, attr.serialize()) for attr in attributes), 'frozen': decorator_arguments['frozen'], }
def collect_attributes(self) -> Optional[List[DataclassAttribute]]: """Collect all attributes declared in the dataclass and its parents. All assignments of the form a: SomeType b: SomeOtherType = ... are collected. """ # First, collect attributes belonging to the current class. ctx = self._ctx cls = self._ctx.cls attrs: List[DataclassAttribute] = [] known_attrs: Set[str] = set() kw_only = _get_decorator_bool_argument(ctx, 'kw_only', False) for stmt in cls.defs.body: # Any assignment that doesn't use the new type declaration # syntax can be ignored out of hand. if not (isinstance(stmt, AssignmentStmt) and stmt.new_syntax): continue # a: int, b: str = 1, 'foo' is not supported syntax so we # don't have to worry about it. lhs = stmt.lvalues[0] if not isinstance(lhs, NameExpr): continue sym = cls.info.names.get(lhs.name) if sym is None: # This name is likely blocked by a star import. We don't need to defer because # defer() is already called by mark_incomplete(). continue node = sym.node if isinstance(node, PlaceholderNode): # This node is not ready yet. return None assert isinstance(node, Var) # x: ClassVar[int] is ignored by dataclasses. if node.is_classvar: continue # x: InitVar[int] is turned into x: int and is removed from the class. is_init_var = False node_type = get_proper_type(node.type) if (isinstance(node_type, Instance) and node_type.type.fullname == 'dataclasses.InitVar'): is_init_var = True node.type = node_type.args[0] if self._is_kw_only_type(node_type): kw_only = True has_field_call, field_args = _collect_field_args(stmt.rvalue, ctx) is_in_init_param = field_args.get('init') if is_in_init_param is None: is_in_init = True else: is_in_init = bool(ctx.api.parse_bool(is_in_init_param)) has_default = False # Ensure that something like x: int = field() is rejected # after an attribute with a default. if has_field_call: has_default = 'default' in field_args or 'default_factory' in field_args # All other assignments are already type checked. elif not isinstance(stmt.rvalue, TempNode): has_default = True if not has_default: # Make all non-default attributes implicit because they are de-facto set # on self in the generated __init__(), not in the class body. sym.implicit = True is_kw_only = kw_only # Use the kw_only field arg if it is provided. Otherwise use the # kw_only value from the decorator parameter. field_kw_only_param = field_args.get('kw_only') if field_kw_only_param is not None: is_kw_only = bool(ctx.api.parse_bool(field_kw_only_param)) known_attrs.add(lhs.name) attrs.append( DataclassAttribute( name=lhs.name, is_in_init=is_in_init, is_init_var=is_init_var, has_default=has_default, line=stmt.line, column=stmt.column, type=sym.type, info=cls.info, kw_only=is_kw_only, )) # Next, collect attributes belonging to any class in the MRO # as long as those attributes weren't already collected. This # makes it possible to overwrite attributes in subclasses. # copy() because we potentially modify all_attrs below and if this code requires debugging # we'll have unmodified attrs laying around. all_attrs = attrs.copy() for info in cls.info.mro[1:-1]: if 'dataclass' not in info.metadata: continue super_attrs = [] # Each class depends on the set of attributes in its dataclass ancestors. ctx.api.add_plugin_dependency(make_wildcard_trigger(info.fullname)) for data in info.metadata["dataclass"]["attributes"]: name: str = data["name"] if name not in known_attrs: attr = DataclassAttribute.deserialize(info, data, ctx.api) attr.expand_typevar_from_subtype(ctx.cls.info) known_attrs.add(name) super_attrs.append(attr) elif all_attrs: # How early in the attribute list an attribute appears is determined by the # reverse MRO, not simply MRO. # See https://docs.python.org/3/library/dataclasses.html#inheritance for # details. for attr in all_attrs: if attr.name == name: all_attrs.remove(attr) super_attrs.append(attr) break all_attrs = super_attrs + all_attrs all_attrs.sort(key=lambda a: a.kw_only) # Ensure that arguments without a default don't follow # arguments that have a default. found_default = False # Ensure that the KW_ONLY sentinel is only provided once found_kw_sentinel = False for attr in all_attrs: # If we find any attribute that is_in_init, not kw_only, and that # doesn't have a default after one that does have one, # then that's an error. if found_default and attr.is_in_init and not attr.has_default and not attr.kw_only: # If the issue comes from merging different classes, report it # at the class definition point. context = (Context(line=attr.line, column=attr.column) if attr in attrs else ctx.cls) ctx.api.fail( 'Attributes without a default cannot follow attributes with one', context, ) found_default = found_default or (attr.has_default and attr.is_in_init) if found_kw_sentinel and self._is_kw_only_type(attr.type): context = (Context(line=attr.line, column=attr.column) if attr in attrs else ctx.cls) ctx.api.fail( 'There may not be more than one field with the KW_ONLY type', context, ) found_kw_sentinel = found_kw_sentinel or self._is_kw_only_type( attr.type) return all_attrs
def transform(self) -> None: """Apply all the necessary transformations to the underlying dataclass so as to ensure it is fully type checked according to the rules in PEP 557. """ ctx = self._ctx info = self._ctx.cls.info attributes = self.collect_attributes() if attributes is None: # Some definitions are not ready, defer() should be already called. return for attr in attributes: if attr.type is None: ctx.api.defer() return decorator_arguments = { 'init': _get_decorator_bool_argument(self._ctx, 'init', True), 'eq': _get_decorator_bool_argument(self._ctx, 'eq', True), 'order': _get_decorator_bool_argument(self._ctx, 'order', False), 'frozen': _get_decorator_bool_argument(self._ctx, 'frozen', False), 'slots': _get_decorator_bool_argument(self._ctx, 'slots', False), 'match_args': _get_decorator_bool_argument(self._ctx, 'match_args', True), } py_version = self._ctx.api.options.python_version # If there are no attributes, it may be that the semantic analyzer has not # processed them yet. In order to work around this, we can simply skip generating # __init__ if there are no attributes, because if the user truly did not define any, # then the object default __init__ with an empty signature will be present anyway. if (decorator_arguments['init'] and ('__init__' not in info.names or info.names['__init__'].plugin_generated) and attributes): args = [ attr.to_argument() for attr in attributes if attr.is_in_init and not self._is_kw_only_type(attr.type) ] if info.fallback_to_any: # Make positional args optional since we don't know their order. # This will at least allow us to typecheck them if they are called # as kwargs for arg in args: if arg.kind == ARG_POS: arg.kind = ARG_OPT nameless_var = Var('') args = [ Argument(nameless_var, AnyType(TypeOfAny.explicit), None, ARG_STAR), *args, Argument(nameless_var, AnyType(TypeOfAny.explicit), None, ARG_STAR2), ] add_method( ctx, '__init__', args=args, return_type=NoneType(), ) if (decorator_arguments['eq'] and info.get('__eq__') is None or decorator_arguments['order']): # Type variable for self types in generated methods. obj_type = ctx.api.named_type('builtins.object') self_tvar_expr = TypeVarExpr(SELF_TVAR_NAME, info.fullname + '.' + SELF_TVAR_NAME, [], obj_type) info.names[SELF_TVAR_NAME] = SymbolTableNode(MDEF, self_tvar_expr) # Add <, >, <=, >=, but only if the class has an eq method. if decorator_arguments['order']: if not decorator_arguments['eq']: ctx.api.fail('eq must be True if order is True', ctx.cls) for method_name in ['__lt__', '__gt__', '__le__', '__ge__']: # Like for __eq__ and __ne__, we want "other" to match # the self type. obj_type = ctx.api.named_type('builtins.object') order_tvar_def = TypeVarType( SELF_TVAR_NAME, info.fullname + '.' + SELF_TVAR_NAME, -1, [], obj_type) order_return_type = ctx.api.named_type('builtins.bool') order_args = [ Argument(Var('other', order_tvar_def), order_tvar_def, None, ARG_POS) ] existing_method = info.get(method_name) if existing_method is not None and not existing_method.plugin_generated: assert existing_method.node ctx.api.fail( 'You may not have a custom %s method when order=True' % method_name, existing_method.node, ) add_method( ctx, method_name, args=order_args, return_type=order_return_type, self_type=order_tvar_def, tvar_def=order_tvar_def, ) if decorator_arguments['frozen']: self._freeze(attributes) else: self._propertize_callables(attributes) if decorator_arguments['slots']: self.add_slots(info, attributes, correct_version=py_version >= (3, 10)) self.reset_init_only_vars(info, attributes) if (decorator_arguments['match_args'] and ('__match_args__' not in info.names or info.names['__match_args__'].plugin_generated) and attributes): str_type = ctx.api.named_type("builtins.str") literals: List[Type] = [ LiteralType(attr.name, str_type) for attr in attributes if attr.is_in_init ] match_args_type = TupleType(literals, ctx.api.named_type("builtins.tuple")) add_attribute_to_class(ctx.api, ctx.cls, "__match_args__", match_args_type, final=True) self._add_dataclass_fields_magic_attribute() info.metadata['dataclass'] = { 'attributes': [attr.serialize() for attr in attributes], 'frozen': decorator_arguments['frozen'], }
def transform(self) -> None: """Apply all the necessary transformations to the underlying dataclass so as to ensure it is fully type checked according to the rules in PEP 557. """ ctx = self._ctx info = self._ctx.cls.info attributes = self.collect_attributes() if attributes is None: # Some definitions are not ready, defer() should be already called. return for attr in attributes: if attr.type is None: ctx.api.defer() return decorator_arguments = { "init": _get_decorator_bool_argument(self._ctx, "init", True), "eq": _get_decorator_bool_argument(self._ctx, "eq", True), "order": _get_decorator_bool_argument(self._ctx, "order", False), "frozen": _get_decorator_bool_argument(self._ctx, "frozen", False), } # If there are no attributes, it may be that the semantic analyzer has not # processed them yet. In order to work around this, we can simply skip # generating __init__ if there are no attributes, because if the user # truly did not define any, then the object default __init__ with an # empty signature will be present anyway. if (decorator_arguments["init"] and ("__init__" not in info.names or info.names["__init__"].plugin_generated) and attributes): add_method( ctx, "__init__", args=[ attr.to_argument() for attr in attributes if attr.is_in_init ], return_type=NoneType(), ) if (decorator_arguments["eq"] and info.get("__eq__") is None or decorator_arguments["order"]): # Type variable for self types in generated methods. obj_type = ctx.api.named_type("__builtins__.object") self_tvar_expr = TypeVarExpr(SELF_TVAR_NAME, info.fullname + "." + SELF_TVAR_NAME, [], obj_type) info.names[SELF_TVAR_NAME] = SymbolTableNode(MDEF, self_tvar_expr) # Add <, >, <=, >=, but only if the class has an eq method. if decorator_arguments["order"]: if not decorator_arguments["eq"]: ctx.api.fail("eq must be True if order is True", ctx.cls) for method_name in ["__lt__", "__gt__", "__le__", "__ge__"]: # Like for __eq__ and __ne__, we want "other" to match # the self type. obj_type = ctx.api.named_type("__builtins__.object") order_tvar_def = TypeVarDef( SELF_TVAR_NAME, info.fullname + "." + SELF_TVAR_NAME, -1, [], obj_type, ) order_other_type = TypeVarType(order_tvar_def) order_return_type = ctx.api.named_type("__builtins__.bool") order_args = [ Argument(Var("other", order_other_type), order_other_type, None, ARG_POS) ] existing_method = info.get(method_name) if existing_method is not None and not existing_method.plugin_generated: assert existing_method.node ctx.api.fail( "You may not have a custom %s method when order=True" % method_name, existing_method.node, ) add_method( ctx, method_name, args=order_args, return_type=order_return_type, self_type=order_other_type, tvar_def=order_tvar_def, ) if decorator_arguments["frozen"]: self._freeze(attributes) self.reset_init_only_vars(info, attributes) info.metadata["dataclass"] = { "attributes": [attr.serialize() for attr in attributes], "frozen": decorator_arguments["frozen"], }
def transform(self) -> None: """Apply all the necessary transformations to the underlying dataclass so as to ensure it is fully type checked according to the rules in PEP 557. """ ctx = self._ctx info = self._ctx.cls.info attributes = self.collect_attributes() if ctx.api.options.new_semantic_analyzer: # Check if attribute types are ready. for attr in attributes: if info[attr.name].type is None: ctx.api.defer() return decorator_arguments = { 'init': _get_decorator_bool_argument(self._ctx, 'init', True), 'eq': _get_decorator_bool_argument(self._ctx, 'eq', True), 'order': _get_decorator_bool_argument(self._ctx, 'order', False), 'frozen': _get_decorator_bool_argument(self._ctx, 'frozen', False), } if decorator_arguments['init']: add_method( ctx, '__init__', args=[attr.to_argument(info) for attr in attributes if attr.is_in_init], return_type=NoneType(), ) if (decorator_arguments['eq'] and info.get('__eq__') is None or decorator_arguments['order']): # Type variable for self types in generated methods. obj_type = ctx.api.named_type('__builtins__.object') self_tvar_expr = TypeVarExpr(SELF_TVAR_NAME, info.fullname() + '.' + SELF_TVAR_NAME, [], obj_type) info.names[SELF_TVAR_NAME] = SymbolTableNode(MDEF, self_tvar_expr) # Add an eq method, but only if the class doesn't already have one. if decorator_arguments['eq'] and info.get('__eq__') is None: for method_name in ['__eq__', '__ne__']: # The TVar is used to enforce that "other" must have # the same type as self (covariant). Note the # "self_type" parameter to add_method. obj_type = ctx.api.named_type('__builtins__.object') cmp_tvar_def = TypeVarDef(SELF_TVAR_NAME, info.fullname() + '.' + SELF_TVAR_NAME, -1, [], obj_type) cmp_other_type = TypeVarType(cmp_tvar_def) cmp_return_type = ctx.api.named_type('__builtins__.bool') add_method( ctx, method_name, args=[Argument(Var('other', cmp_other_type), cmp_other_type, None, ARG_POS)], return_type=cmp_return_type, self_type=cmp_other_type, tvar_def=cmp_tvar_def, ) # Add <, >, <=, >=, but only if the class has an eq method. if decorator_arguments['order']: if not decorator_arguments['eq']: ctx.api.fail('eq must be True if order is True', ctx.cls) for method_name in ['__lt__', '__gt__', '__le__', '__ge__']: # Like for __eq__ and __ne__, we want "other" to match # the self type. obj_type = ctx.api.named_type('__builtins__.object') order_tvar_def = TypeVarDef(SELF_TVAR_NAME, info.fullname() + '.' + SELF_TVAR_NAME, -1, [], obj_type) order_other_type = TypeVarType(order_tvar_def) order_return_type = ctx.api.named_type('__builtins__.bool') order_args = [ Argument(Var('other', order_other_type), order_other_type, None, ARG_POS) ] existing_method = info.get(method_name) if existing_method is not None and not existing_method.plugin_generated: assert existing_method.node ctx.api.fail( 'You may not have a custom %s method when order=True' % method_name, existing_method.node, ) add_method( ctx, method_name, args=order_args, return_type=order_return_type, self_type=order_other_type, tvar_def=order_tvar_def, ) if decorator_arguments['frozen']: self._freeze(attributes) self.reset_init_only_vars(info, attributes) info.metadata['dataclass'] = { 'attributes': OrderedDict((attr.name, attr.serialize()) for attr in attributes), 'frozen': decorator_arguments['frozen'], }
def attr_class_maker_callback(ctx: 'mypy.plugin.ClassDefContext', auto_attribs_default: Optional[bool] = False, frozen_default: bool = False) -> bool: """Add necessary dunder methods to classes decorated with attr.s. attrs is a package that lets you define classes without writing dull boilerplate code. At a quick glance, the decorator searches the class body for assignments of `attr.ib`s (or annotated variables if auto_attribs=True), then depending on how the decorator is called, it will add an __init__ or all the __cmp__ methods. For frozen=True it will turn the attrs into properties. See http://www.attrs.org/en/stable/how-does-it-work.html for information on how attrs works. If this returns False, some required metadata was not ready yet and we need another pass. """ info = ctx.cls.info init = _get_decorator_bool_argument(ctx, 'init', True) frozen = _get_frozen(ctx, frozen_default) order = _determine_eq_order(ctx) slots = _get_decorator_bool_argument(ctx, 'slots', False) auto_attribs = _get_decorator_optional_bool_argument( ctx, 'auto_attribs', auto_attribs_default) kw_only = _get_decorator_bool_argument(ctx, 'kw_only', False) match_args = _get_decorator_bool_argument(ctx, 'match_args', True) early_fail = False if ctx.api.options.python_version[0] < 3: if auto_attribs: ctx.api.fail("auto_attribs is not supported in Python 2", ctx.reason) early_fail = True if not info.defn.base_type_exprs: # Note: This will not catch subclassing old-style classes. ctx.api.fail("attrs only works with new-style classes", info.defn) early_fail = True if kw_only: ctx.api.fail(KW_ONLY_PYTHON_2_UNSUPPORTED, ctx.reason) early_fail = True if early_fail: _add_empty_metadata(info) return True for super_info in ctx.cls.info.mro[1:-1]: if 'attrs_tag' in super_info.metadata and 'attrs' not in super_info.metadata: # Super class is not ready yet. Request another pass. return False attributes = _analyze_class(ctx, auto_attribs, kw_only) # Check if attribute types are ready. for attr in attributes: node = info.get(attr.name) if node is None: # This name is likely blocked by some semantic analysis error that # should have been reported already. _add_empty_metadata(info) return True _add_attrs_magic_attribute(ctx, [(attr.name, info[attr.name].type) for attr in attributes]) if slots: _add_slots(ctx, attributes) if match_args and ctx.api.options.python_version[:2] >= (3, 10): # `.__match_args__` is only added for python3.10+, but the argument # exists for earlier versions as well. _add_match_args(ctx, attributes) # Save the attributes so that subclasses can reuse them. ctx.cls.info.metadata['attrs'] = { 'attributes': [attr.serialize() for attr in attributes], 'frozen': frozen, } adder = MethodAdder(ctx) if init: _add_init(ctx, attributes, adder) if order: _add_order(ctx, adder) if frozen: _make_frozen(ctx, attributes) return True