예제 #1
0
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])
예제 #2
0
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
예제 #5
0
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])
예제 #6
0
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
예제 #7
0
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])