def _astroid_bootstrapping(): """astroid bootstrapping the builtins module""" # this boot strapping is necessary since we need the Const nodes to # inspect_build builtins, and then we can proxy Const builder = InspectBuilder() astroid_builtin = builder.inspect_build(builtins) # pylint: disable=redefined-outer-name for cls, node_cls in node_classes.CONST_CLS.items(): if cls is TYPE_NONE: proxy = build_class("NoneType") proxy.parent = astroid_builtin elif cls is TYPE_NOTIMPLEMENTED: proxy = build_class("NotImplementedType") proxy.parent = astroid_builtin elif cls is TYPE_ELLIPSIS: proxy = build_class("Ellipsis") proxy.parent = astroid_builtin else: proxy = astroid_builtin.getattr(cls.__name__)[0] if cls in (dict, list, set, tuple): node_cls._proxied = proxy else: _CONST_PROXY[cls] = proxy # Set the builtin module as parent for some builtins. nodes.Const._proxied = property(_set_proxied) _GeneratorType = nodes.ClassDef(types.GeneratorType.__name__, types.GeneratorType.__doc__) _GeneratorType.parent = astroid_builtin bases.Generator._proxied = _GeneratorType builder.object_build(bases.Generator._proxied, types.GeneratorType) if hasattr(types, "AsyncGeneratorType"): # pylint: disable=no-member; AsyncGeneratorType _AsyncGeneratorType = nodes.ClassDef(types.AsyncGeneratorType.__name__, types.AsyncGeneratorType.__doc__) _AsyncGeneratorType.parent = astroid_builtin bases.AsyncGenerator._proxied = _AsyncGeneratorType builder.object_build(bases.AsyncGenerator._proxied, types.AsyncGeneratorType) builtin_types = ( types.GetSetDescriptorType, types.GeneratorType, types.MemberDescriptorType, TYPE_NONE, TYPE_NOTIMPLEMENTED, types.FunctionType, types.MethodType, types.BuiltinFunctionType, types.ModuleType, types.TracebackType, ) for _type in builtin_types: if _type.__name__ not in astroid_builtin: cls = nodes.ClassDef(_type.__name__, _type.__doc__) cls.parent = astroid_builtin builder.object_build(cls, _type) astroid_builtin[_type.__name__] = cls
def transform_model(cls) -> None: """ Anything that uses the ModelMeta needs _meta and id. Also keep track of relationships and make them in the related model class. """ if cls.name != 'Model': appname = 'models' for mcls in cls.get_children(): if isinstance(mcls, ClassDef): for attr in mcls.get_children(): if isinstance(attr, Assign): if attr.targets[0].name == 'app': appname = attr.value.value mname = '{}.{}'.format(appname, cls.name) MODELS[mname] = cls for relname, relval in FUTURE_RELATIONS.get(mname, []): cls.locals[relname] = relval for attr in cls.get_children(): if isinstance(attr, Assign): try: attrname = attr.value.func.attrname except AttributeError: pass else: if attrname in ['ForeignKeyField', 'ManyToManyField']: tomodel = attr.value.args[0].value relname = '' if attr.value.keywords: for keyword in attr.value.keywords: if keyword.arg == 'related_name': relname = keyword.value.value if relname: # Injected model attributes need to also have the relation manager if attrname == 'ManyToManyField': relval = [ attr.value.func, MANAGER.ast_from_module_name('tortoise.fields') .lookup('ManyToManyRelationManager')[1][0] ] else: relval = [ attr.value.func, MANAGER.ast_from_module_name('tortoise.fields') .lookup('RelationQueryContainer')[1][0] ] if tomodel in MODELS: MODELS[tomodel].locals[relname] = relval else: FUTURE_RELATIONS.setdefault(tomodel, []).append((relname, relval)) cls.locals['_meta'] = [ MANAGER.ast_from_module_name('tortoise.models').lookup('MetaInfo')[1][0].instantiate_class() ] if 'id' not in cls.locals: cls.locals['id'] = [nodes.ClassDef('id', None)]
def visit_classdef(self, node, parent, newstyle=True): """visit a ClassDef node to become astroid""" node, doc = self._get_doc(node) newnode = nodes.ClassDef(node.name, doc, node.lineno, node.col_offset, parent) metaclass = None for keyword in node.keywords: if keyword.arg == "metaclass": metaclass = self.visit(keyword, newnode).value break if node.decorator_list: decorators = self.visit_decorators(node, newnode) else: decorators = None newnode.postinit( [self.visit(child, newnode) for child in node.bases], [self.visit(child, newnode) for child in node.body], decorators, newstyle, metaclass, [ self.visit(kwd, newnode) for kwd in node.keywords if kwd.arg != "metaclass" ], ) return newnode
def build_class(name, basenames=(), doc=None): """create and initialize an astroid ClassDef node""" node = nodes.ClassDef(name, doc) for base in basenames: basenode = nodes.Name(name=base) node.bases.append(basenode) basenode.parent = node return node
def infer_func_form(node, base_type, context=None, enum=False): """Specific inference function for namedtuple or Python 3 enum. """ # node is a Call node, class name as first argument and generated class # attributes as second argument # namedtuple or enums list of attributes can be a list of strings or a # whitespace-separate string try: name, names = _find_func_form_arguments(node, context) try: attributes = names.value.replace(',', ' ').split() except AttributeError: if not enum: attributes = [_infer_first(const, context).value for const in names.elts] else: # Enums supports either iterator of (name, value) pairs # or mappings. # TODO: support only list, tuples and mappings. if hasattr(names, 'items') and isinstance(names.items, list): attributes = [_infer_first(const[0], context).value for const in names.items if isinstance(const[0], nodes.Const)] elif hasattr(names, 'elts'): # Enums can support either ["a", "b", "c"] # or [("a", 1), ("b", 2), ...], but they can't # be mixed. if all(isinstance(const, nodes.Tuple) for const in names.elts): attributes = [_infer_first(const.elts[0], context).value for const in names.elts if isinstance(const, nodes.Tuple)] else: attributes = [_infer_first(const, context).value for const in names.elts] else: raise AttributeError if not attributes: raise AttributeError except (AttributeError, exceptions.InferenceError): raise UseInferenceDefault() # If we can't infer the name of the class, don't crash, up to this point # we know it is a namedtuple anyway. name = name or 'Uninferable' # we want to return a Class node instance with proper attributes set class_node = nodes.ClassDef(name, 'docstring') class_node.parent = node.parent # set base class=tuple class_node.bases.append(base_type) # XXX add __init__(*attributes) method for attr in attributes: fake_node = nodes.EmptyNode() fake_node.parent = class_node fake_node.attrname = attr class_node.instance_attrs[attr] = [fake_node] return class_node, name, attributes
def infer_typing_alias( node: nodes.Call, ctx: context.InferenceContext = None ) -> typing.Optional[node_classes.NodeNG]: """ Infers the call to _alias function :param node: call node :param context: inference context """ if not isinstance(node, nodes.Call): return None res = next(node.args[0].infer(context=ctx)) if res != astroid.Uninferable and isinstance(res, nodes.ClassDef): class_def = nodes.ClassDef( name=f"{res.name}_typing", lineno=0, col_offset=0, parent=res.parent, ) class_def.postinit( bases=[res], body=res.body, decorators=res.decorators, metaclass=create_typing_metaclass(), ) return class_def if len(node.args) == 2 and isinstance(node.args[0], nodes.Attribute): class_def = nodes.ClassDef( name=node.args[0].attrname, lineno=0, col_offset=0, parent=node.parent, ) class_def.postinit(bases=[], body=[], decorators=None, metaclass=create_typing_metaclass()) return class_def return None
def infer_typedDict( # pylint: disable=invalid-name node: nodes.FunctionDef, ctx: context.InferenceContext = None ) -> typing.Iterator[nodes.ClassDef]: """Replace TypedDict FunctionDef with ClassDef.""" class_def = nodes.ClassDef( name="TypedDict", lineno=node.lineno, col_offset=node.col_offset, parent=node.parent, ) return iter([class_def])
def infer_pattern_match(node: nodes.Call, ctx: context.InferenceContext = None): """Infer re.Pattern and re.Match as classes. For PY39+ add `__class_getitem__`.""" class_def = nodes.ClassDef( name=node.parent.targets[0].name, lineno=node.lineno, col_offset=node.col_offset, parent=node.parent, ) if PY39: func_to_add = astroid.extract_node(CLASS_GETITEM_TEMPLATE) class_def.locals["__class_getitem__"] = [func_to_add] return iter([class_def])
def build_class(name: str, basenames: Iterable[str] = (), doc: str | None = None) -> nodes.ClassDef: """Create and initialize an astroid ClassDef node.""" node = nodes.ClassDef(name) node.postinit( bases=[nodes.Name(name=base, parent=node) for base in basenames], body=[], decorators=None, doc_node=nodes.Const(value=doc) if doc else None, ) return node
def infer_typedDict( # pylint: disable=invalid-name node: nodes.FunctionDef, ctx: context.InferenceContext = None ) -> None: """Replace TypedDict FunctionDef with ClassDef.""" class_def = nodes.ClassDef( name="TypedDict", doc=node.doc, lineno=node.lineno, col_offset=node.col_offset, parent=node.parent, ) class_def.postinit(bases=[], body=[], decorators=None) node.root().locals["TypedDict"] = [class_def]
def infer_tuple_alias( node: nodes.Call, ctx: context.InferenceContext = None ) -> typing.Iterator[nodes.ClassDef]: """Infer call to tuple alias as new subscriptable class typing.Tuple.""" res = next(node.args[0].infer(context=ctx)) class_def = nodes.ClassDef( name="Tuple", parent=node.parent, ) class_def.postinit(bases=[res], body=[], decorators=None) func_to_add = astroid.extract_node(CLASS_GETITEM_TEMPLATE) class_def.locals["__class_getitem__"] = [func_to_add] return iter([class_def])
def visit_classdef(self, node, parent, assign_ctx=None): """visit a Class node to become astroid""" newnode = new.ClassDef(node.name, None) _lineno_parent(node, newnode, parent) _init_set_doc(node, newnode) newnode.bases = [self.visit(child, newnode, assign_ctx) for child in node.bases] newnode.body = [self.visit(child, newnode, assign_ctx) for child in node.body] if node.decorator_list: newnode.decorators = self.visit_decorators(node, newnode, assign_ctx) newnode.parent.frame().set_local(newnode.name, newnode) return newnode
def infer_namespace(node, context=None): callsite = arguments.CallSite.from_call(node, context=context) if not callsite.keyword_arguments: # Cannot make sense of it. raise UseInferenceDefault() class_node = nodes.ClassDef("Namespace", "docstring") class_node.parent = node.parent for attr in set(callsite.keyword_arguments): fake_node = nodes.EmptyNode() fake_node.parent = class_node fake_node.attrname = attr class_node.instance_attrs[attr] = [fake_node] return iter((class_node.instantiate_class(), ))
def infer_typing_alias( node: nodes.Call, ctx: context.InferenceContext = None ) -> typing.Iterator[nodes.ClassDef]: """ Infers the call to _alias function Insert ClassDef, with same name as aliased class, in mro to simulate _GenericAlias. :param node: call node :param context: inference context """ if ( not isinstance(node.parent, nodes.Assign) or not len(node.parent.targets) == 1 or not isinstance(node.parent.targets[0], nodes.AssignName) ): return None res = next(node.args[0].infer(context=ctx)) assign_name = node.parent.targets[0] class_def = nodes.ClassDef( name=assign_name.name, lineno=assign_name.lineno, col_offset=assign_name.col_offset, parent=node.parent, ) if res != astroid.Uninferable and isinstance(res, nodes.ClassDef): # Only add `res` as base if it's a `ClassDef` # This isn't the case for `typing.Pattern` and `typing.Match` class_def.postinit(bases=[res], body=[], decorators=None) maybe_type_var = node.args[1] if ( not PY39 and not ( isinstance(maybe_type_var, node_classes.Tuple) and not maybe_type_var.elts ) or PY39 and isinstance(maybe_type_var, nodes.Const) and maybe_type_var.value > 0 ): # If typing alias is subscriptable, add `__class_getitem__` to ClassDef func_to_add = astroid.extract_node(CLASS_GETITEM_TEMPLATE) class_def.locals["__class_getitem__"] = [func_to_add] else: # If not, make sure that `__class_getitem__` access is forbidden. # This is an issue in cases where the aliased class implements it, # but the typing alias isn't subscriptable. E.g., `typing.ByteString` for PY39+ _forbid_class_getitem_access(class_def) return iter([class_def])
def create_typing_metaclass(): # Needs to mock the __getitem__ class method so that # MutableSet[T] is acceptable func_to_add = extract_node(GET_ITEM_TEMPLATE) abc_meta = next(extract_node(ABC_METACLASS_TEMPLATE).infer()) typing_meta = nodes.ClassDef( name="ABCMeta_typing", lineno=abc_meta.lineno, col_offset=abc_meta.col_offset, parent=abc_meta.parent, ) typing_meta.postinit( bases=[extract_node(ABC_METACLASS_TEMPLATE)], body=[], decorators=None ) typing_meta.locals["__getitem__"] = [func_to_add] return typing_meta
def _astroid_bootstrapping(): """astroid bootstrapping the builtins module""" # this boot strapping is necessary since we need the Const nodes to # inspect_build builtins, and then we can proxy Const builder = InspectBuilder() astroid_builtin = builder.inspect_build(builtins) for cls, node_cls in node_classes.CONST_CLS.items(): if cls is TYPE_NONE: proxy = build_class("NoneType") proxy.parent = astroid_builtin elif cls is TYPE_NOTIMPLEMENTED: proxy = build_class("NotImplementedType") proxy.parent = astroid_builtin elif cls is TYPE_ELLIPSIS: proxy = build_class("Ellipsis") proxy.parent = astroid_builtin else: proxy = astroid_builtin.getattr(cls.__name__)[0] assert isinstance(proxy, nodes.ClassDef) if cls in (dict, list, set, tuple): node_cls._proxied = proxy else: _CONST_PROXY[cls] = proxy # Set the builtin module as parent for some builtins. nodes.Const._proxied = property(_set_proxied) _GeneratorType = nodes.ClassDef(types.GeneratorType.__name__) _GeneratorType.parent = astroid_builtin generator_doc_node = (nodes.Const(value=types.GeneratorType.__doc__) if types.GeneratorType.__doc__ else None) _GeneratorType.postinit( bases=[], body=[], decorators=None, doc_node=generator_doc_node, ) bases.Generator._proxied = _GeneratorType builder.object_build(bases.Generator._proxied, types.GeneratorType) if hasattr(types, "AsyncGeneratorType"): _AsyncGeneratorType = nodes.ClassDef(types.AsyncGeneratorType.__name__) _AsyncGeneratorType.parent = astroid_builtin async_generator_doc_node = (nodes.Const( value=types.AsyncGeneratorType.__doc__) if types.AsyncGeneratorType.__doc__ else None) _AsyncGeneratorType.postinit( bases=[], body=[], decorators=None, doc_node=async_generator_doc_node, ) bases.AsyncGenerator._proxied = _AsyncGeneratorType builder.object_build(bases.AsyncGenerator._proxied, types.AsyncGeneratorType) builtin_types = ( types.GetSetDescriptorType, types.GeneratorType, types.MemberDescriptorType, TYPE_NONE, TYPE_NOTIMPLEMENTED, types.FunctionType, types.MethodType, types.BuiltinFunctionType, types.ModuleType, types.TracebackType, ) for _type in builtin_types: if _type.__name__ not in astroid_builtin: klass = nodes.ClassDef(_type.__name__) klass.parent = astroid_builtin klass.postinit( bases=[], body=[], decorators=None, doc_node=nodes.Const( value=_type.__doc__) if _type.__doc__ else None, ) builder.object_build(klass, _type) astroid_builtin[_type.__name__] = klass
node_cls._proxied = proxy else: _CONST_PROXY[cls] = proxy _astroid_bootstrapping() # TODO : find a nicer way to handle this situation; def _set_proxied(const): return _CONST_PROXY[const.value.__class__] nodes.Const._proxied = property(_set_proxied) _GeneratorType = nodes.ClassDef(types.GeneratorType.__name__, types.GeneratorType.__doc__) _GeneratorType.parent = MANAGER.astroid_cache[builtins.__name__] bases.Generator._proxied = _GeneratorType Astroid_BUILDER.object_build(bases.Generator._proxied, types.GeneratorType) if hasattr(types, "AsyncGeneratorType"): # pylint: disable=no-member; AsyncGeneratorType _AsyncGeneratorType = nodes.ClassDef(types.AsyncGeneratorType.__name__, types.AsyncGeneratorType.__doc__) _AsyncGeneratorType.parent = MANAGER.astroid_cache[builtins.__name__] bases.AsyncGenerator._proxied = _AsyncGeneratorType Astroid_BUILDER.object_build(bases.AsyncGenerator._proxied, types.AsyncGeneratorType) # pylint: enable=no-member _builtins = MANAGER.astroid_cache[builtins.__name__]
def infer_func_form(node, base_type, context=None, enum=False): """Specific inference function for namedtuple or Python 3 enum. """ def infer_first(node): if node is util.Uninferable: raise UseInferenceDefault try: value = next(node.infer(context=context)) if value is util.Uninferable: raise UseInferenceDefault() else: return value except StopIteration: raise InferenceError() # node is a Call node, class name as first argument and generated class # attributes as second argument if len(node.args) != 2: # something weird here, go back to class implementation raise UseInferenceDefault() # namedtuple or enums list of attributes can be a list of strings or a # whitespace-separate string try: name = infer_first(node.args[0]).value names = infer_first(node.args[1]) try: attributes = names.value.replace(',', ' ').split() except AttributeError: if not enum: attributes = [infer_first(const).value for const in names.elts] else: # Enums supports either iterator of (name, value) pairs # or mappings. # TODO: support only list, tuples and mappings. if hasattr(names, 'items') and isinstance(names.items, list): attributes = [ infer_first(const[0]).value for const in names.items if isinstance(const[0], nodes.Const) ] elif hasattr(names, 'elts'): # Enums can support either ["a", "b", "c"] # or [("a", 1), ("b", 2), ...], but they can't # be mixed. if all( isinstance(const, nodes.Tuple) for const in names.elts): attributes = [ infer_first(const.elts[0]).value for const in names.elts if isinstance(const, nodes.Tuple) ] else: attributes = [ infer_first(const).value for const in names.elts ] else: raise AttributeError if not attributes: raise AttributeError except (AttributeError, exceptions.InferenceError): raise UseInferenceDefault() # we want to return a Class node instance with proper attributes set class_node = nodes.ClassDef(name, 'docstring') class_node.parent = node.parent # set base class=tuple class_node.bases.append(base_type) # XXX add __init__(*attributes) method for attr in attributes: fake_node = nodes.EmptyNode() fake_node.parent = class_node fake_node.attrname = attr class_node.instance_attrs[attr] = [fake_node] return class_node, name, attributes
def transform_model(cls: ClassDef) -> None: """ Anything that uses the ModelMeta needs _meta and id. Also keep track of relationships and make them in the related model class. """ if cls.name != "Model": appname = "models" for mcls in cls.get_children(): if isinstance(mcls, ClassDef): for attr in mcls.get_children(): if isinstance(attr, Assign) and attr.targets[0].name == "app": appname = attr.value.value mname = f"{appname}.{cls.name}" MODELS[mname] = cls for relname, relval in FUTURE_RELATIONS.get(mname, []): cls.locals[relname] = relval for attr in cls.get_children(): if isinstance(attr, (Assign, AnnAssign)): try: attrname = attr.value.func.attrname except AttributeError: pass else: if attrname in [ "OneToOneField", "ForeignKeyField", "ManyToManyField" ]: tomodel = attr.value.args[0].value relname = "" if attr.value.keywords: for keyword in attr.value.keywords: if keyword.arg == "related_name": relname = keyword.value.value if not relname: relname = cls.name.lower() + "s" # Injected model attributes need to also have the relation manager if attrname == "ManyToManyField": relval = [ # attr.value.func, MANAGER.ast_from_module_name( "tortoise.fields.relational").lookup( "ManyToManyFieldInstance")[1][0], MANAGER.ast_from_module_name( "tortoise.fields.relational").lookup( "ManyToManyRelation")[1][0], ] elif attrname == "ForeignKeyField": relval = [ MANAGER.ast_from_module_name( "tortoise.fields.relational").lookup( "ForeignKeyFieldInstance")[1][0], MANAGER.ast_from_module_name( "tortoise.fields.relational").lookup( "ReverseRelation")[1][0], ] elif attrname == "OneToOneField": relval = [ MANAGER.ast_from_module_name( "tortoise.fields.relational").lookup( "OneToOneFieldInstance")[1][0], MANAGER.ast_from_module_name( "tortoise.fields.relational").lookup( "OneToOneRelation")[1][0], ] if tomodel in MODELS: MODELS[tomodel].locals[relname] = relval else: FUTURE_RELATIONS.setdefault(tomodel, []).append( (relname, relval)) cls.locals["_meta"] = [ MANAGER.ast_from_module_name("tortoise.models").lookup("MetaInfo")[1] [0].instantiate_class() ] if "id" not in cls.locals: cls.locals["id"] = [nodes.ClassDef("id", None)]
from six.moves import builtins astroid_builtin = Astroid_BUILDER.inspect_build(builtins) for cls, node_cls in node_classes.CONST_CLS.items(): if cls is type(None): proxy = build_class('NoneType') proxy.parent = astroid_builtin elif cls is type(NotImplemented): proxy = build_class('NotImplementedType') proxy.parent = astroid_builtin else: proxy = astroid_builtin.getattr(cls.__name__)[0] if cls in (dict, list, set, tuple): node_cls._proxied = proxy else: _CONST_PROXY[cls] = proxy _astroid_bootstrapping() # TODO : find a nicer way to handle this situation; # However __proxied introduced an # infinite recursion (see https://bugs.launchpad.net/pylint/+bug/456870) def _set_proxied(const): return _CONST_PROXY[const.value.__class__] nodes.Const._proxied = property(_set_proxied) _GeneratorType = nodes.ClassDef(types.GeneratorType.__name__, types.GeneratorType.__doc__) _GeneratorType.parent = MANAGER.astroid_cache[six.moves.builtins.__name__] bases.Generator._proxied = _GeneratorType Astroid_BUILDER.object_build(bases.Generator._proxied, types.GeneratorType)
def infer_enum(node, context=None): """ Specific inference function for enum Call node. """ enum_meta = nodes.ClassDef("EnumMeta", 'docstring') class_node = infer_func_form(node, enum_meta, context=context, enum=True)[0] return iter([class_node])
def infer_func_form(node, base_type, context=None, enum=False): """Specific inference function for namedtuple or Python 3 enum.""" # node is a Call node, class name as first argument and generated class # attributes as second argument # namedtuple or enums list of attributes can be a list of strings or a # whitespace-separate string try: name, names = _find_func_form_arguments(node, context) try: attributes = names.value.replace(",", " ").split() except AttributeError as exc: if not enum: attributes = [ _infer_first(const, context).value for const in names.elts ] else: # Enums supports either iterator of (name, value) pairs # or mappings. if hasattr(names, "items") and isinstance(names.items, list): attributes = [ _infer_first(const[0], context).value for const in names.items if isinstance(const[0], nodes.Const) ] elif hasattr(names, "elts"): # Enums can support either ["a", "b", "c"] # or [("a", 1), ("b", 2), ...], but they can't # be mixed. if all(isinstance(const, nodes.Tuple) for const in names.elts): attributes = [ _infer_first(const.elts[0], context).value for const in names.elts if isinstance(const, nodes.Tuple) ] else: attributes = [ _infer_first(const, context).value for const in names.elts ] else: raise AttributeError from exc if not attributes: raise AttributeError from exc except (AttributeError, InferenceError) as exc: raise UseInferenceDefault from exc if not enum: # namedtuple maps sys.intern(str()) over over field_names attributes = [str(attr) for attr in attributes] # XXX this should succeed *unless* __str__/__repr__ is incorrect or throws # in which case we should not have inferred these values and raised earlier attributes = [attr for attr in attributes if " " not in attr] # If we can't infer the name of the class, don't crash, up to this point # we know it is a namedtuple anyway. name = name or "Uninferable" # we want to return a Class node instance with proper attributes set class_node = nodes.ClassDef(name, "docstring") class_node.parent = node.parent # set base class=tuple class_node.bases.append(base_type) # XXX add __init__(*attributes) method for attr in attributes: fake_node = nodes.EmptyNode() fake_node.parent = class_node fake_node.attrname = attr class_node.instance_attrs[attr] = [fake_node] return class_node, name, attributes
def infer_func_form( node: nodes.Call, base_type: list[nodes.NodeNG], context: InferenceContext | None = None, enum: bool = False, ) -> tuple[nodes.ClassDef, str, list[str]]: """Specific inference function for namedtuple or Python 3 enum.""" # node is a Call node, class name as first argument and generated class # attributes as second argument # namedtuple or enums list of attributes can be a list of strings or a # whitespace-separate string try: name, names = _find_func_form_arguments(node, context) try: attributes: list[str] = names.value.replace(",", " ").split() except AttributeError as exc: # Handle attributes of NamedTuples if not enum: attributes = [] fields = _get_namedtuple_fields(node) if fields: fields_node = extract_node(fields) attributes = [ _infer_first(const, context).value for const in fields_node.elts ] # Handle attributes of Enums else: # Enums supports either iterator of (name, value) pairs # or mappings. if hasattr(names, "items") and isinstance(names.items, list): attributes = [ _infer_first(const[0], context).value for const in names.items if isinstance(const[0], nodes.Const) ] elif hasattr(names, "elts"): # Enums can support either ["a", "b", "c"] # or [("a", 1), ("b", 2), ...], but they can't # be mixed. if all( isinstance(const, nodes.Tuple) for const in names.elts): attributes = [ _infer_first(const.elts[0], context).value for const in names.elts if isinstance(const, nodes.Tuple) ] else: attributes = [ _infer_first(const, context).value for const in names.elts ] else: raise AttributeError from exc if not attributes: raise AttributeError from exc except (AttributeError, InferenceError) as exc: raise UseInferenceDefault from exc if not enum: # namedtuple maps sys.intern(str()) over over field_names attributes = [str(attr) for attr in attributes] # XXX this should succeed *unless* __str__/__repr__ is incorrect or throws # in which case we should not have inferred these values and raised earlier attributes = [attr for attr in attributes if " " not in attr] # If we can't infer the name of the class, don't crash, up to this point # we know it is a namedtuple anyway. name = name or "Uninferable" # we want to return a Class node instance with proper attributes set class_node = nodes.ClassDef(name) # A typical ClassDef automatically adds its name to the parent scope, # but doing so causes problems, so defer setting parent until after init # see: https://github.com/PyCQA/pylint/issues/5982 class_node.parent = node.parent class_node.postinit( # set base class=tuple bases=base_type, body=[], decorators=None, ) # XXX add __init__(*attributes) method for attr in attributes: fake_node = nodes.EmptyNode() fake_node.parent = class_node fake_node.attrname = attr class_node.instance_attrs[attr] = [fake_node] return class_node, name, attributes