def infer_typedDict( # pylint: disable=invalid-name node: FunctionDef, ctx: context.InferenceContext = None) -> typing.Iterator[ClassDef]: """Replace TypedDict FunctionDef with ClassDef.""" class_def = ClassDef( name="TypedDict", lineno=node.lineno, col_offset=node.col_offset, parent=node.parent, ) class_def.postinit(bases=[extract_node("dict")], body=[], decorators=None) func_to_add = extract_node("dict") class_def.locals["__call__"] = [func_to_add] return iter([class_def])
def infer_tuple_alias( node: Call, ctx: context.InferenceContext = None) -> typing.Iterator[ClassDef]: """Infer call to tuple alias as new subscriptable class typing.Tuple.""" try: res = next(node.args[0].infer(context=ctx)) except StopIteration as e: raise InferenceError(node=node.args[0], context=context) from e class_def = ClassDef( name="Tuple", parent=node.parent, ) class_def.postinit(bases=[res], body=[], decorators=None) func_to_add = extract_node(CLASS_GETITEM_TEMPLATE) class_def.locals["__class_getitem__"] = [func_to_add] return iter([class_def])
def attr_attributes_transform(node: ClassDef) -> None: """Given that the ClassNode has an attr decorator, rewrite class attributes as instance attributes """ # Astroid can't infer this attribute properly # Prevents https://github.com/PyCQA/pylint/issues/1884 node.locals["__attrs_attrs__"] = [Unknown(parent=node)] for cdef_body_node in node.body: if not isinstance(cdef_body_node, (Assign, AnnAssign)): continue if isinstance(cdef_body_node.value, Call): if cdef_body_node.value.func.as_string() not in ATTRIB_NAMES: continue else: continue targets = (cdef_body_node.targets if hasattr(cdef_body_node, "targets") else [cdef_body_node.target]) for target in targets: rhs_node = Unknown( lineno=cdef_body_node.lineno, col_offset=cdef_body_node.col_offset, parent=cdef_body_node, ) if isinstance(target, AssignName): # Could be a subscript if the code analysed is # i = Optional[str] = "" # See https://github.com/PyCQA/pylint/issues/4439 node.locals[target.name] = [rhs_node] node.instance_attrs[target.name] = [rhs_node]
def _looks_like_subscriptable(node: ClassDef) -> bool: """ Returns True if the node corresponds to a ClassDef of the Collections.abc module that supports subscripting :param node: ClassDef node """ if node.qname().startswith("_collections") or node.qname().startswith( "collections" ): try: node.getattr("__class_getitem__") return True except AttributeInferenceError: pass return False
def infer_typing_alias( node: Call, ctx: context.InferenceContext = None) -> typing.Iterator[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, Assign) or not len(node.parent.targets) == 1 or not isinstance(node.parent.targets[0], AssignName)): return None try: res = next(node.args[0].infer(context=ctx)) except StopIteration as e: raise InferenceError(node=node.args[0], context=context) from e assign_name = node.parent.targets[0] class_def = ClassDef( name=assign_name.name, lineno=assign_name.lineno, col_offset=assign_name.col_offset, parent=node.parent, ) if res != Uninferable and isinstance(res, 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_PLUS and not (isinstance(maybe_type_var, node_classes.Tuple) and not maybe_type_var.elts) or PY39_PLUS and isinstance(maybe_type_var, Const) and maybe_type_var.value > 0): # If typing alias is subscriptable, add `__class_getitem__` to ClassDef func_to_add = 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 _forbid_class_getitem_access(node: ClassDef) -> None: """ Disable the access to __class_getitem__ method for the node in parameters """ def full_raiser(origin_func, attr, *args, **kwargs): """ Raises an AttributeInferenceError in case of access to __class_getitem__ method. Otherwise just call origin_func. """ if attr == "__class_getitem__": raise AttributeInferenceError( "__class_getitem__ access is not allowed") return origin_func(attr, *args, **kwargs) try: node.getattr("__class_getitem__") # If we are here, then we are sure to modify object that do have __class_getitem__ method (which origin is one the # protocol defined in collections module) whereas the typing module consider it should not # We do not want __class_getitem__ to be found in the classdef partial_raiser = partial(full_raiser, node.getattr) node.getattr = partial_raiser except AttributeInferenceError: pass
def infer_old_typedDict( # pylint: disable=invalid-name node: ClassDef, ctx: context.InferenceContext = None) -> typing.Iterator[ClassDef]: func_to_add = extract_node("dict") node.locals["__call__"] = [func_to_add] return iter([node])