def _is_exempt_from_public_methods(node: astroid.ClassDef) -> bool: """Check if a class is exempt from too-few-public-methods""" # If it's a typing.Namedtuple or an Enum for ancestor in node.ancestors(): if ancestor.name == "Enum" and ancestor.root().name == "enum": return True if ancestor.qname() == TYPING_NAMEDTUPLE: return True # Or if it's a dataclass if not node.decorators: return False root_locals = set(node.root().locals) for decorator in node.decorators.nodes: if isinstance(decorator, astroid.Call): decorator = decorator.func if not isinstance(decorator, (astroid.Name, astroid.Attribute)): continue if isinstance(decorator, astroid.Name): name = decorator.name else: name = decorator.attrname if name in DATACLASSES_DECORATORS and ( root_locals.intersection(DATACLASSES_DECORATORS) or DATACLASS_IMPORT in root_locals): return True return False
def instance_has_bool(class_def: astroid.ClassDef) -> bool: try: class_def.getattr("__bool__") return True except astroid.AttributeInferenceError: ... return False
def _set_classdef_environment(self, node: astroid.ClassDef) -> None: """Method to set environment of a ClassDef node.""" node.type_environment = Environment() for name in node.instance_attrs: node.type_environment.locals[ name] = self.type_constraints.fresh_tvar( node.instance_attrs[name][0]) self.type_store.classes[node.name][name] = [ (node.type_environment.locals[name], 'attribute') ] for name in node.locals: if name in ['__module__', '__qualname__']: node.type_environment.locals[name] = str else: node.type_environment.locals[ name] = self.type_constraints.fresh_tvar( node.locals[name][0]) self.type_store.classes[node.name]['__bases'] = [ _node_to_type(base) for base in node.bases ] try: self.type_store.classes[node.name]['__mro'] = [ cls.name for cls in node.mro() ] except astroid.exceptions.DuplicateBasesError: self.type_store.classes[node.name]['__mro'] = [node.name]
def add_attribute_nodes(node: astroid.ClassDef, attr_names): for attr_name in attr_names: rhs_node = astroid.Unknown( lineno=node.lineno, parent=node, ) node.locals[attr_name] = [rhs_node] node.instance_attrs[attr_name] = [rhs_node]
def has_known_bases(klass: astroid.ClassDef, context=None) -> bool: """Return true if all base classes of a class could be inferred.""" try: return klass._all_bases_known except AttributeError: pass for base in klass.bases: result = safe_infer(base, context=context) if (not isinstance(result, astroid.ClassDef) or result is klass or not has_known_bases(result, context=context)): klass._all_bases_known = False return False klass._all_bases_known = True return True
def overrides_a_method(class_node: astroid.ClassDef, name: str) -> bool: """return True if <name> is a method overridden from an ancestor""" for ancestor in class_node.ancestors(): if name in ancestor and isinstance(ancestor[name], astroid.FunctionDef): return True return False
def _is_dataclass(node: astroid.ClassDef) -> bool: """Check if a class definition defines a Python 3.7+ dataclass :param node: The class node to check. :type node: astroid.ClassDef :returns: True if the given node represents a dataclass class. False otherwise. :rtype: bool """ if not node.decorators: return False root_locals = node.root().locals for decorator in node.decorators.nodes: if isinstance(decorator, astroid.Call): decorator = decorator.func if not isinstance(decorator, (astroid.Name, astroid.Attribute)): continue if isinstance(decorator, astroid.Name): name = decorator.name else: name = decorator.attrname if name == DATACLASS_DECORATOR and DATACLASS_DECORATOR in root_locals: return True return False
def make_node_create_uninferable_instance(node: astroid.ClassDef, context=None): def _instantiate_uninferable(*args, **kwargs): return astroid.Uninferable() node.instantiate_class = _instantiate_uninferable return node
def has_conditional_instantiation(node: astroid.ClassDef, context=None): if 'pyomo' not in node.qname(): return try: # check if the class defines a __new__() dunder_new_node: astroid.FunctionDef = node.local_attr('__new__')[0] except astroid.AttributeInferenceError: return False else: # _display(node) # find all return statements; if there's more than one, assume that instances are created conditionally, # and therefore the type of the instantiated object cannot be known with static analysis # to be more accurate, we should check for If nodes as well as maybe the presence of other __new__() calls return_statements = list( dunder_new_node.nodes_of_class(astroid.node_classes.Return)) return len(return_statements) > 1
def transform(cls): """ Mimics Flask-SQLAlchemy's _include_sqlalchemy """ if cls.name == "SQLAlchemy": import sqlalchemy # pylint: disable=import-outside-toplevel import sqlalchemy.orm # pylint: disable=import-outside-toplevel for module in sqlalchemy, sqlalchemy.orm: for key in module.__all__: cls.locals[key] = [ClassDef(key, None)] if cls.name == "scoped_session": from sqlalchemy.orm import Session # pylint: disable=import-outside-toplevel for key in Session.public_methods: cls.locals[key] = [ClassDef(key, None)]
def _is_dataclass_like(node: astroid.ClassDef) -> bool: """Check if a class definition defines a Python data class A list of decorator names are introspected, such as the builtin `dataclass` decorator, as well as the popular `attrs` one from the `attrs` library. :param node: The class node to check. :type node: astroid.ClassDef :returns: `True` if the given node represents a dataclass class, `False` otherwise. :rtype: bool """ if not node.decorators: return False root_locals = set(node.root().locals) for decorator in node.decorators.nodes: if isinstance(decorator, astroid.Call): decorator = decorator.func if not isinstance(decorator, (astroid.Name, astroid.Attribute)): continue if isinstance(decorator, astroid.Name): name = decorator.name else: name = decorator.attrname if name in DATACLASSES_DECORATORS and root_locals.intersection( DATACLASSES_DECORATORS ): return True return False
def _is_enum_subclass(cls: astroid.ClassDef) -> bool: """Return whether cls is a subclass of an Enum.""" try: return any(klass.name in ENUM_BASE_NAMES and getattr(klass.root(), "name", None) == "enum" for klass in cls.mro()) except MroError: return False
def has_known_bases(klass: astroid.ClassDef, context=None) -> bool: """Return true if all base classes of a class could be inferred.""" try: return klass._all_bases_known except AttributeError: pass for base in klass.bases: result = safe_infer(base, context=context) # TODO: check for A->B->A->B pattern in class structure too? if (not isinstance(result, astroid.ClassDef) or result is klass or not has_known_bases(result, context=context)): klass._all_bases_known = False return False klass._all_bases_known = True return True
def unimplemented_abstract_methods( node: astroid.ClassDef, is_abstract_cb: astroid.FunctionDef = None ) -> Dict[str, astroid.node_classes.NodeNG]: """ Get the unimplemented abstract methods for the given *node*. A method can be considered abstract if the callback *is_abstract_cb* returns a ``True`` value. The check defaults to verifying that a method is decorated with abstract methods. The function will work only for new-style classes. For old-style classes, it will simply return an empty dictionary. For the rest of them, it will return a dictionary of abstract method names and their inferred objects. """ if is_abstract_cb is None: is_abstract_cb = partial(decorated_with, qnames=ABC_METHODS) visited = {} # type: Dict[str, astroid.node_classes.NodeNG] try: mro = reversed(node.mro()) except NotImplementedError: # Old style class, it will not have a mro. return {} except astroid.ResolveError: # Probably inconsistent hierarchy, don'try # to figure this out here. return {} for ancestor in mro: for obj in ancestor.values(): inferred = obj if isinstance(obj, astroid.AssignName): inferred = safe_infer(obj) if not inferred: # Might be an abstract function, # but since we don't have enough information # in order to take this decision, we're taking # the *safe* decision instead. if obj.name in visited: del visited[obj.name] continue if not isinstance(inferred, astroid.FunctionDef): if obj.name in visited: del visited[obj.name] if isinstance(inferred, astroid.FunctionDef): # It's critical to use the original name, # since after inferring, an object can be something # else than expected, as in the case of the # following assignment. # # class A: # def keys(self): pass # __iter__ = keys abstract = is_abstract_cb(inferred) if abstract: visited[obj.name] = inferred elif not abstract and obj.name in visited: del visited[obj.name] return visited
def class_is_abstract(node: astroid.ClassDef) -> bool: """return true if the given class node should be considered as an abstract class """ for method in node.methods(): if method.parent.frame() is node: if method.is_abstract(pass_is_abstract=False): return True return False
def _set_classdef_environment(self, node: astroid.ClassDef) -> None: """Method to set environment of a ClassDef node.""" node.type_environment = Environment() for name in node.instance_attrs: node.type_environment.locals[name] = self.type_constraints.fresh_tvar(node.instance_attrs[name][0]) self.type_store.classes[node.name][name] = [(node.type_environment.locals[name], 'attribute')] for name in node.locals: if name in ['__module__', '__qualname__']: node.type_environment.locals[name] = str else: node.type_environment.locals[name] = self.type_constraints.fresh_tvar(node.locals[name][0]) self.type_store.classes[node.name]['__bases'] = [_node_to_type(base) for base in node.bases] try: self.type_store.classes[node.name]['__mro'] = [cls.name for cls in node.mro()] except astroid.exceptions.DuplicateBasesError: self.type_store.classes[node.name]['__mro'] = [node.name]
def class_is_abstract(node: astroid.ClassDef) -> bool: """return true if the given class node should be considered as an abstract class """ # Only check for explicit metaclass=ABCMeta on this specific class meta = node.declared_metaclass() if meta is not None: if meta.name == "ABCMeta" and meta.root().name in ABC_MODULES: return True for ancestor in node.ancestors(): if ancestor.name == "ABC" and ancestor.root().name in ABC_MODULES: # abc.ABC inheritance return True for method in node.methods(): if method.parent.frame() is node: if method.is_abstract(pass_is_abstract=False): return True return False
def visit_classdef(self, node: astroid.ClassDef) -> None: node.inf_type = NoType() # Update type_store for this class. # TODO: include node.instance_attrs as well? for attr in node.locals: attr_inf_type = self.type_constraints.resolve(node.type_environment.lookup_in_env(attr)) attr_inf_type >> ( lambda a: self.type_store.methods[attr].append((a, node.locals[attr][0].type)) if is_callable(a) else None) attr_inf_type >> ( lambda a: self.type_store.classes[node.name][attr].append((a, node.locals[attr][0].type if is_callable(a) else 'attribute')))
def transform(node: NodeNG) -> None: """Make pylint understand FlaskSQLAlchemy proxies and wrappers. Note : it _looks_ like astroid transforms are run in some kind of try/except mechanism which makes some errors fail silently. For example, if you call: ```python from sqlalchemy.orm import Session Session.foo ``` here, you would think it will raise an: `AttributeError: type object 'Session' has no attribute 'foo'` but... no. Instead it stops the transform and continue to the next node, so pylint does not raise an error, so you think your code (and ours) works, but it's not :-( So we need to write tests that fails to make sure our plugin works. """ if node.name == "SQLAlchemy": import sqlalchemy import sqlalchemy.orm for module in sqlalchemy, sqlalchemy.orm: for key in sorted(module.__all__, key=sort_module_keys): if key not in FLASK_SQLALCHEMY_WRAPS: node.locals[key] = [ClassDef(key, None)] else: node.locals[key] = [ ClassDef(key, None), node.locals["Query"] ] elif node.name == "scoped_session": from sqlalchemy.orm import Session for key in sorted(dir(Session), reverse=True): # `query` is in fact a proxy to `query_property` if key == "query": node.locals[key] = [ ClassDef(key, None), node.locals["query_property"] ] else: node.locals[key] = [ClassDef(key, None)]
def visit_classdef(self, node: astroid.ClassDef) -> None: node.inf_type = NoType() self.type_constraints.unify(self.lookup_inf_type(node.parent, node.name), Type[_ForwardRef(node.name)], node) # Update type_store for this class. # TODO: include node.instance_attrs as well? for attr in node.locals: attr_inf_type = self.type_constraints.resolve(node.type_environment.lookup_in_env(attr)) attr_inf_type >> ( lambda a: self.type_store.methods[attr].append((a, node.locals[attr][0].type)) if isinstance(a, CallableMeta) else None) attr_inf_type >> ( lambda a: self.type_store.classes[node.name][attr].append((a, node.locals[attr][0].type if isinstance(a, CallableMeta) else 'attribute')))
def visit_classdef(self, node: astroid.ClassDef) -> None: node.inf_type = TypeInfo(NoType) self.type_constraints.unify(self.lookup_type(node.parent, node.name), _ForwardRef(node.name), node) # Update type_store for this class. # TODO: include node.instance_attrs as well? for attr in node.locals: attr_type = self.type_constraints.resolve( node.type_environment.lookup_in_env(attr)).getValue() self.type_store.classes[node.name][attr].append(attr_type) if isinstance(attr_type, CallableMeta): self.type_store.methods[attr].append(attr_type)
def is_subclass_of(child: astroid.ClassDef, parent: astroid.ClassDef) -> bool: """ Check if first node is a subclass of second node. :param child: Node to check for subclass. :param parent: Node to check for superclass. :returns: True if child is derived from parent. False otherwise. """ if not all(isinstance(node, astroid.ClassDef) for node in (child, parent)): return False for ancestor in child.ancestors(): if astroid.helpers.is_subtype(ancestor, parent): return True return False
def cubicweb_transform(module): # handle objectify_predicate decorator (and its former name until bw compat # is kept). Only look at module level functions, should be enough. for assnodes in module.locals.values(): for node in assnodes: if isinstance(node, FunctionDef) and node.decorators: for decorator in node.decorators.nodes: try: for infered in decorator.infer(): if infered.name in ('objectify_predicate', 'objectify_selector'): turn_function_to_class(node) break else: continue break except InferenceError: continue # add yams base types into 'yams.buildobjs', astng doesn't grasp globals() # magic in there if module.name == 'yams.buildobjs': from yams import BASE_TYPES for etype in BASE_TYPES: module.locals[etype] = [ClassDef(etype, None)] # add data() to uiprops module elif module.name.split('.')[-1] == 'uiprops': fake = AstroidBuilder(MANAGER).string_build(''' def data(string): return u'' ''') module.locals['data'] = fake.locals['data'] # handle lower case with underscores for relation names in schema.py if not module.qname().endswith('.schema'): return schema_locals = module.locals for assnodes in schema_locals.values(): for node in assnodes: if not isinstance(node, ClassDef): continue # XXX can we infer ancestor classes? it would be better to know for sure that # one of the mother classes is yams.buildobjs.RelationDefinition for instance for base in node.basenames: if base in ('RelationDefinition', 'ComputedRelation', 'RelationType'): new_name = node.name.replace('_', '').capitalize() schema_locals[new_name] = schema_locals[node.name] del schema_locals[node.name] node.name = new_name
def is_class_subscriptable_pep585_with_postponed_evaluation_enabled( value: astroid.ClassDef, node: astroid.node_classes.NodeNG) -> bool: """Check if class is subscriptable with PEP 585 and postponed evaluation enabled. """ if not is_postponed_evaluation_enabled(node): return False parent_node = node.parent while True: # Check if any parent node matches condition if isinstance( parent_node, (astroid.AnnAssign, astroid.Arguments, astroid.FunctionDef)): break parent_node = parent_node.parent if isinstance(parent_node, astroid.Module): return False if value.qname() in SUBSCRIPTABLE_CLASSES_PEP585: return True return False
def _is_typing_namedtuple(node: astroid.ClassDef) -> bool: """Check if a class node is a typing.NamedTuple class""" for base in node.ancestors(): if base.qname() == TYPING_NAMEDTUPLE: return True return False