You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					552 lines
				
				17 KiB
			
		
		
			
		
	
	
					552 lines
				
				17 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								# ext/mypy/infer.py
							 | 
						||
| 
								 | 
							
								# Copyright (C) 2021 the SQLAlchemy authors and contributors
							 | 
						||
| 
								 | 
							
								# <see AUTHORS file>
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# This module is part of SQLAlchemy and is released under
							 | 
						||
| 
								 | 
							
								# the MIT License: https://www.opensource.org/licenses/mit-license.php
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from typing import Optional
							 | 
						||
| 
								 | 
							
								from typing import Sequence
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from mypy.maptype import map_instance_to_supertype
							 | 
						||
| 
								 | 
							
								from mypy.messages import format_type
							 | 
						||
| 
								 | 
							
								from mypy.nodes import AssignmentStmt
							 | 
						||
| 
								 | 
							
								from mypy.nodes import CallExpr
							 | 
						||
| 
								 | 
							
								from mypy.nodes import Expression
							 | 
						||
| 
								 | 
							
								from mypy.nodes import FuncDef
							 | 
						||
| 
								 | 
							
								from mypy.nodes import MemberExpr
							 | 
						||
| 
								 | 
							
								from mypy.nodes import NameExpr
							 | 
						||
| 
								 | 
							
								from mypy.nodes import RefExpr
							 | 
						||
| 
								 | 
							
								from mypy.nodes import StrExpr
							 | 
						||
| 
								 | 
							
								from mypy.nodes import TypeInfo
							 | 
						||
| 
								 | 
							
								from mypy.nodes import Var
							 | 
						||
| 
								 | 
							
								from mypy.plugin import SemanticAnalyzerPluginInterface
							 | 
						||
| 
								 | 
							
								from mypy.subtypes import is_subtype
							 | 
						||
| 
								 | 
							
								from mypy.types import AnyType
							 | 
						||
| 
								 | 
							
								from mypy.types import CallableType
							 | 
						||
| 
								 | 
							
								from mypy.types import get_proper_type
							 | 
						||
| 
								 | 
							
								from mypy.types import Instance
							 | 
						||
| 
								 | 
							
								from mypy.types import NoneType
							 | 
						||
| 
								 | 
							
								from mypy.types import ProperType
							 | 
						||
| 
								 | 
							
								from mypy.types import TypeOfAny
							 | 
						||
| 
								 | 
							
								from mypy.types import UnionType
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from . import names
							 | 
						||
| 
								 | 
							
								from . import util
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def infer_type_from_right_hand_nameexpr(
							 | 
						||
| 
								 | 
							
								    api: SemanticAnalyzerPluginInterface,
							 | 
						||
| 
								 | 
							
								    stmt: AssignmentStmt,
							 | 
						||
| 
								 | 
							
								    node: Var,
							 | 
						||
| 
								 | 
							
								    left_hand_explicit_type: Optional[ProperType],
							 | 
						||
| 
								 | 
							
								    infer_from_right_side: RefExpr,
							 | 
						||
| 
								 | 
							
								) -> Optional[ProperType]:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    type_id = names.type_id_for_callee(infer_from_right_side)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if type_id is None:
							 | 
						||
| 
								 | 
							
								        return None
							 | 
						||
| 
								 | 
							
								    elif type_id is names.COLUMN:
							 | 
						||
| 
								 | 
							
								        python_type_for_type = _infer_type_from_decl_column(
							 | 
						||
| 
								 | 
							
								            api, stmt, node, left_hand_explicit_type
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								    elif type_id is names.RELATIONSHIP:
							 | 
						||
| 
								 | 
							
								        python_type_for_type = _infer_type_from_relationship(
							 | 
						||
| 
								 | 
							
								            api, stmt, node, left_hand_explicit_type
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								    elif type_id is names.COLUMN_PROPERTY:
							 | 
						||
| 
								 | 
							
								        python_type_for_type = _infer_type_from_decl_column_property(
							 | 
						||
| 
								 | 
							
								            api, stmt, node, left_hand_explicit_type
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								    elif type_id is names.SYNONYM_PROPERTY:
							 | 
						||
| 
								 | 
							
								        python_type_for_type = infer_type_from_left_hand_type_only(
							 | 
						||
| 
								 | 
							
								            api, node, left_hand_explicit_type
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								    elif type_id is names.COMPOSITE_PROPERTY:
							 | 
						||
| 
								 | 
							
								        python_type_for_type = _infer_type_from_decl_composite_property(
							 | 
						||
| 
								 | 
							
								            api, stmt, node, left_hand_explicit_type
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        return None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return python_type_for_type
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _infer_type_from_relationship(
							 | 
						||
| 
								 | 
							
								    api: SemanticAnalyzerPluginInterface,
							 | 
						||
| 
								 | 
							
								    stmt: AssignmentStmt,
							 | 
						||
| 
								 | 
							
								    node: Var,
							 | 
						||
| 
								 | 
							
								    left_hand_explicit_type: Optional[ProperType],
							 | 
						||
| 
								 | 
							
								) -> Optional[ProperType]:
							 | 
						||
| 
								 | 
							
								    """Infer the type of mapping from a relationship.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    E.g.::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        @reg.mapped
							 | 
						||
| 
								 | 
							
								        class MyClass:
							 | 
						||
| 
								 | 
							
								            # ...
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            addresses = relationship(Address, uselist=True)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            order: Mapped["Order"] = relationship("Order")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Will resolve in mypy as::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        @reg.mapped
							 | 
						||
| 
								 | 
							
								        class MyClass:
							 | 
						||
| 
								 | 
							
								            # ...
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            addresses: Mapped[List[Address]]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            order: Mapped["Order"]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    assert isinstance(stmt.rvalue, CallExpr)
							 | 
						||
| 
								 | 
							
								    target_cls_arg = stmt.rvalue.args[0]
							 | 
						||
| 
								 | 
							
								    python_type_for_type: Optional[ProperType] = None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if isinstance(target_cls_arg, NameExpr) and isinstance(
							 | 
						||
| 
								 | 
							
								        target_cls_arg.node, TypeInfo
							 | 
						||
| 
								 | 
							
								    ):
							 | 
						||
| 
								 | 
							
								        # type
							 | 
						||
| 
								 | 
							
								        related_object_type = target_cls_arg.node
							 | 
						||
| 
								 | 
							
								        python_type_for_type = Instance(related_object_type, [])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # other cases not covered - an error message directs the user
							 | 
						||
| 
								 | 
							
								    # to set an explicit type annotation
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # node.type == str, it's a string
							 | 
						||
| 
								 | 
							
								    # if isinstance(target_cls_arg, NameExpr) and isinstance(
							 | 
						||
| 
								 | 
							
								    #     target_cls_arg.node, Var
							 | 
						||
| 
								 | 
							
								    # )
							 | 
						||
| 
								 | 
							
								    # points to a type
							 | 
						||
| 
								 | 
							
								    # isinstance(target_cls_arg, NameExpr) and isinstance(
							 | 
						||
| 
								 | 
							
								    #     target_cls_arg.node, TypeAlias
							 | 
						||
| 
								 | 
							
								    # )
							 | 
						||
| 
								 | 
							
								    # string expression
							 | 
						||
| 
								 | 
							
								    # isinstance(target_cls_arg, StrExpr)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    uselist_arg = util.get_callexpr_kwarg(stmt.rvalue, "uselist")
							 | 
						||
| 
								 | 
							
								    collection_cls_arg: Optional[Expression] = util.get_callexpr_kwarg(
							 | 
						||
| 
								 | 
							
								        stmt.rvalue, "collection_class"
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								    type_is_a_collection = False
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # this can be used to determine Optional for a many-to-one
							 | 
						||
| 
								 | 
							
								    # in the same way nullable=False could be used, if we start supporting
							 | 
						||
| 
								 | 
							
								    # that.
							 | 
						||
| 
								 | 
							
								    # innerjoin_arg = util.get_callexpr_kwarg(stmt.rvalue, "innerjoin")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (
							 | 
						||
| 
								 | 
							
								        uselist_arg is not None
							 | 
						||
| 
								 | 
							
								        and api.parse_bool(uselist_arg) is True
							 | 
						||
| 
								 | 
							
								        and collection_cls_arg is None
							 | 
						||
| 
								 | 
							
								    ):
							 | 
						||
| 
								 | 
							
								        type_is_a_collection = True
							 | 
						||
| 
								 | 
							
								        if python_type_for_type is not None:
							 | 
						||
| 
								 | 
							
								            python_type_for_type = api.named_type(
							 | 
						||
| 
								 | 
							
								                names.NAMED_TYPE_BUILTINS_LIST, [python_type_for_type]
							 | 
						||
| 
								 | 
							
								            )
							 | 
						||
| 
								 | 
							
								    elif (
							 | 
						||
| 
								 | 
							
								        uselist_arg is None or api.parse_bool(uselist_arg) is True
							 | 
						||
| 
								 | 
							
								    ) and collection_cls_arg is not None:
							 | 
						||
| 
								 | 
							
								        type_is_a_collection = True
							 | 
						||
| 
								 | 
							
								        if isinstance(collection_cls_arg, CallExpr):
							 | 
						||
| 
								 | 
							
								            collection_cls_arg = collection_cls_arg.callee
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if isinstance(collection_cls_arg, NameExpr) and isinstance(
							 | 
						||
| 
								 | 
							
								            collection_cls_arg.node, TypeInfo
							 | 
						||
| 
								 | 
							
								        ):
							 | 
						||
| 
								 | 
							
								            if python_type_for_type is not None:
							 | 
						||
| 
								 | 
							
								                # this can still be overridden by the left hand side
							 | 
						||
| 
								 | 
							
								                # within _infer_Type_from_left_and_inferred_right
							 | 
						||
| 
								 | 
							
								                python_type_for_type = Instance(
							 | 
						||
| 
								 | 
							
								                    collection_cls_arg.node, [python_type_for_type]
							 | 
						||
| 
								 | 
							
								                )
							 | 
						||
| 
								 | 
							
								        elif (
							 | 
						||
| 
								 | 
							
								            isinstance(collection_cls_arg, NameExpr)
							 | 
						||
| 
								 | 
							
								            and isinstance(collection_cls_arg.node, FuncDef)
							 | 
						||
| 
								 | 
							
								            and collection_cls_arg.node.type is not None
							 | 
						||
| 
								 | 
							
								        ):
							 | 
						||
| 
								 | 
							
								            if python_type_for_type is not None:
							 | 
						||
| 
								 | 
							
								                # this can still be overridden by the left hand side
							 | 
						||
| 
								 | 
							
								                # within _infer_Type_from_left_and_inferred_right
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                # TODO: handle mypy.types.Overloaded
							 | 
						||
| 
								 | 
							
								                if isinstance(collection_cls_arg.node.type, CallableType):
							 | 
						||
| 
								 | 
							
								                    rt = get_proper_type(collection_cls_arg.node.type.ret_type)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                    if isinstance(rt, CallableType):
							 | 
						||
| 
								 | 
							
								                        callable_ret_type = get_proper_type(rt.ret_type)
							 | 
						||
| 
								 | 
							
								                        if isinstance(callable_ret_type, Instance):
							 | 
						||
| 
								 | 
							
								                            python_type_for_type = Instance(
							 | 
						||
| 
								 | 
							
								                                callable_ret_type.type,
							 | 
						||
| 
								 | 
							
								                                [python_type_for_type],
							 | 
						||
| 
								 | 
							
								                            )
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            util.fail(
							 | 
						||
| 
								 | 
							
								                api,
							 | 
						||
| 
								 | 
							
								                "Expected Python collection type for "
							 | 
						||
| 
								 | 
							
								                "collection_class parameter",
							 | 
						||
| 
								 | 
							
								                stmt.rvalue,
							 | 
						||
| 
								 | 
							
								            )
							 | 
						||
| 
								 | 
							
								            python_type_for_type = None
							 | 
						||
| 
								 | 
							
								    elif uselist_arg is not None and api.parse_bool(uselist_arg) is False:
							 | 
						||
| 
								 | 
							
								        if collection_cls_arg is not None:
							 | 
						||
| 
								 | 
							
								            util.fail(
							 | 
						||
| 
								 | 
							
								                api,
							 | 
						||
| 
								 | 
							
								                "Sending uselist=False and collection_class at the same time "
							 | 
						||
| 
								 | 
							
								                "does not make sense",
							 | 
						||
| 
								 | 
							
								                stmt.rvalue,
							 | 
						||
| 
								 | 
							
								            )
							 | 
						||
| 
								 | 
							
								        if python_type_for_type is not None:
							 | 
						||
| 
								 | 
							
								            python_type_for_type = UnionType(
							 | 
						||
| 
								 | 
							
								                [python_type_for_type, NoneType()]
							 | 
						||
| 
								 | 
							
								            )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        if left_hand_explicit_type is None:
							 | 
						||
| 
								 | 
							
								            msg = (
							 | 
						||
| 
								 | 
							
								                "Can't infer scalar or collection for ORM mapped expression "
							 | 
						||
| 
								 | 
							
								                "assigned to attribute '{}' if both 'uselist' and "
							 | 
						||
| 
								 | 
							
								                "'collection_class' arguments are absent from the "
							 | 
						||
| 
								 | 
							
								                "relationship(); please specify a "
							 | 
						||
| 
								 | 
							
								                "type annotation on the left hand side."
							 | 
						||
| 
								 | 
							
								            )
							 | 
						||
| 
								 | 
							
								            util.fail(api, msg.format(node.name), node)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if python_type_for_type is None:
							 | 
						||
| 
								 | 
							
								        return infer_type_from_left_hand_type_only(
							 | 
						||
| 
								 | 
							
								            api, node, left_hand_explicit_type
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								    elif left_hand_explicit_type is not None:
							 | 
						||
| 
								 | 
							
								        if type_is_a_collection:
							 | 
						||
| 
								 | 
							
								            assert isinstance(left_hand_explicit_type, Instance)
							 | 
						||
| 
								 | 
							
								            assert isinstance(python_type_for_type, Instance)
							 | 
						||
| 
								 | 
							
								            return _infer_collection_type_from_left_and_inferred_right(
							 | 
						||
| 
								 | 
							
								                api, node, left_hand_explicit_type, python_type_for_type
							 | 
						||
| 
								 | 
							
								            )
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            return _infer_type_from_left_and_inferred_right(
							 | 
						||
| 
								 | 
							
								                api,
							 | 
						||
| 
								 | 
							
								                node,
							 | 
						||
| 
								 | 
							
								                left_hand_explicit_type,
							 | 
						||
| 
								 | 
							
								                python_type_for_type,
							 | 
						||
| 
								 | 
							
								            )
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        return python_type_for_type
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _infer_type_from_decl_composite_property(
							 | 
						||
| 
								 | 
							
								    api: SemanticAnalyzerPluginInterface,
							 | 
						||
| 
								 | 
							
								    stmt: AssignmentStmt,
							 | 
						||
| 
								 | 
							
								    node: Var,
							 | 
						||
| 
								 | 
							
								    left_hand_explicit_type: Optional[ProperType],
							 | 
						||
| 
								 | 
							
								) -> Optional[ProperType]:
							 | 
						||
| 
								 | 
							
								    """Infer the type of mapping from a CompositeProperty."""
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    assert isinstance(stmt.rvalue, CallExpr)
							 | 
						||
| 
								 | 
							
								    target_cls_arg = stmt.rvalue.args[0]
							 | 
						||
| 
								 | 
							
								    python_type_for_type = None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if isinstance(target_cls_arg, NameExpr) and isinstance(
							 | 
						||
| 
								 | 
							
								        target_cls_arg.node, TypeInfo
							 | 
						||
| 
								 | 
							
								    ):
							 | 
						||
| 
								 | 
							
								        related_object_type = target_cls_arg.node
							 | 
						||
| 
								 | 
							
								        python_type_for_type = Instance(related_object_type, [])
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        python_type_for_type = None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if python_type_for_type is None:
							 | 
						||
| 
								 | 
							
								        return infer_type_from_left_hand_type_only(
							 | 
						||
| 
								 | 
							
								            api, node, left_hand_explicit_type
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								    elif left_hand_explicit_type is not None:
							 | 
						||
| 
								 | 
							
								        return _infer_type_from_left_and_inferred_right(
							 | 
						||
| 
								 | 
							
								            api, node, left_hand_explicit_type, python_type_for_type
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        return python_type_for_type
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _infer_type_from_decl_column_property(
							 | 
						||
| 
								 | 
							
								    api: SemanticAnalyzerPluginInterface,
							 | 
						||
| 
								 | 
							
								    stmt: AssignmentStmt,
							 | 
						||
| 
								 | 
							
								    node: Var,
							 | 
						||
| 
								 | 
							
								    left_hand_explicit_type: Optional[ProperType],
							 | 
						||
| 
								 | 
							
								) -> Optional[ProperType]:
							 | 
						||
| 
								 | 
							
								    """Infer the type of mapping from a ColumnProperty.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    This includes mappings against ``column_property()`` as well as the
							 | 
						||
| 
								 | 
							
								    ``deferred()`` function.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    assert isinstance(stmt.rvalue, CallExpr)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if stmt.rvalue.args:
							 | 
						||
| 
								 | 
							
								        first_prop_arg = stmt.rvalue.args[0]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if isinstance(first_prop_arg, CallExpr):
							 | 
						||
| 
								 | 
							
								            type_id = names.type_id_for_callee(first_prop_arg.callee)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            # look for column_property() / deferred() etc with Column as first
							 | 
						||
| 
								 | 
							
								            # argument
							 | 
						||
| 
								 | 
							
								            if type_id is names.COLUMN:
							 | 
						||
| 
								 | 
							
								                return _infer_type_from_decl_column(
							 | 
						||
| 
								 | 
							
								                    api,
							 | 
						||
| 
								 | 
							
								                    stmt,
							 | 
						||
| 
								 | 
							
								                    node,
							 | 
						||
| 
								 | 
							
								                    left_hand_explicit_type,
							 | 
						||
| 
								 | 
							
								                    right_hand_expression=first_prop_arg,
							 | 
						||
| 
								 | 
							
								                )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if isinstance(stmt.rvalue, CallExpr):
							 | 
						||
| 
								 | 
							
								        type_id = names.type_id_for_callee(stmt.rvalue.callee)
							 | 
						||
| 
								 | 
							
								        # this is probably not strictly necessary as we have to use the left
							 | 
						||
| 
								 | 
							
								        # hand type for query expression in any case.  any other no-arg
							 | 
						||
| 
								 | 
							
								        # column prop objects would go here also
							 | 
						||
| 
								 | 
							
								        if type_id is names.QUERY_EXPRESSION:
							 | 
						||
| 
								 | 
							
								            return _infer_type_from_decl_column(
							 | 
						||
| 
								 | 
							
								                api,
							 | 
						||
| 
								 | 
							
								                stmt,
							 | 
						||
| 
								 | 
							
								                node,
							 | 
						||
| 
								 | 
							
								                left_hand_explicit_type,
							 | 
						||
| 
								 | 
							
								            )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return infer_type_from_left_hand_type_only(
							 | 
						||
| 
								 | 
							
								        api, node, left_hand_explicit_type
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _infer_type_from_decl_column(
							 | 
						||
| 
								 | 
							
								    api: SemanticAnalyzerPluginInterface,
							 | 
						||
| 
								 | 
							
								    stmt: AssignmentStmt,
							 | 
						||
| 
								 | 
							
								    node: Var,
							 | 
						||
| 
								 | 
							
								    left_hand_explicit_type: Optional[ProperType],
							 | 
						||
| 
								 | 
							
								    right_hand_expression: Optional[CallExpr] = None,
							 | 
						||
| 
								 | 
							
								) -> Optional[ProperType]:
							 | 
						||
| 
								 | 
							
								    """Infer the type of mapping from a Column.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    E.g.::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        @reg.mapped
							 | 
						||
| 
								 | 
							
								        class MyClass:
							 | 
						||
| 
								 | 
							
								            # ...
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            a = Column(Integer)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            b = Column("b", String)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            c: Mapped[int] = Column(Integer)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            d: bool = Column(Boolean)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Will resolve in MyPy as::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        @reg.mapped
							 | 
						||
| 
								 | 
							
								        class MyClass:
							 | 
						||
| 
								 | 
							
								            # ...
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            a : Mapped[int]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            b : Mapped[str]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            c: Mapped[int]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            d: Mapped[bool]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    assert isinstance(node, Var)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    callee = None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if right_hand_expression is None:
							 | 
						||
| 
								 | 
							
								        if not isinstance(stmt.rvalue, CallExpr):
							 | 
						||
| 
								 | 
							
								            return None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        right_hand_expression = stmt.rvalue
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    for column_arg in right_hand_expression.args[0:2]:
							 | 
						||
| 
								 | 
							
								        if isinstance(column_arg, CallExpr):
							 | 
						||
| 
								 | 
							
								            if isinstance(column_arg.callee, RefExpr):
							 | 
						||
| 
								 | 
							
								                # x = Column(String(50))
							 | 
						||
| 
								 | 
							
								                callee = column_arg.callee
							 | 
						||
| 
								 | 
							
								                type_args: Sequence[Expression] = column_arg.args
							 | 
						||
| 
								 | 
							
								                break
							 | 
						||
| 
								 | 
							
								        elif isinstance(column_arg, (NameExpr, MemberExpr)):
							 | 
						||
| 
								 | 
							
								            if isinstance(column_arg.node, TypeInfo):
							 | 
						||
| 
								 | 
							
								                # x = Column(String)
							 | 
						||
| 
								 | 
							
								                callee = column_arg
							 | 
						||
| 
								 | 
							
								                type_args = ()
							 | 
						||
| 
								 | 
							
								                break
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                # x = Column(some_name, String), go to next argument
							 | 
						||
| 
								 | 
							
								                continue
							 | 
						||
| 
								 | 
							
								        elif isinstance(column_arg, (StrExpr,)):
							 | 
						||
| 
								 | 
							
								            # x = Column("name", String), go to next argument
							 | 
						||
| 
								 | 
							
								            continue
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            assert False
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if callee is None:
							 | 
						||
| 
								 | 
							
								        return None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if isinstance(callee.node, TypeInfo) and names.mro_has_id(
							 | 
						||
| 
								 | 
							
								        callee.node.mro, names.TYPEENGINE
							 | 
						||
| 
								 | 
							
								    ):
							 | 
						||
| 
								 | 
							
								        python_type_for_type = extract_python_type_from_typeengine(
							 | 
						||
| 
								 | 
							
								            api, callee.node, type_args
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if left_hand_explicit_type is not None:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            return _infer_type_from_left_and_inferred_right(
							 | 
						||
| 
								 | 
							
								                api, node, left_hand_explicit_type, python_type_for_type
							 | 
						||
| 
								 | 
							
								            )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            return UnionType([python_type_for_type, NoneType()])
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        # it's not TypeEngine, it's typically implicitly typed
							 | 
						||
| 
								 | 
							
								        # like ForeignKey.  we can't infer from the right side.
							 | 
						||
| 
								 | 
							
								        return infer_type_from_left_hand_type_only(
							 | 
						||
| 
								 | 
							
								            api, node, left_hand_explicit_type
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _infer_type_from_left_and_inferred_right(
							 | 
						||
| 
								 | 
							
								    api: SemanticAnalyzerPluginInterface,
							 | 
						||
| 
								 | 
							
								    node: Var,
							 | 
						||
| 
								 | 
							
								    left_hand_explicit_type: ProperType,
							 | 
						||
| 
								 | 
							
								    python_type_for_type: ProperType,
							 | 
						||
| 
								 | 
							
								    orig_left_hand_type: Optional[ProperType] = None,
							 | 
						||
| 
								 | 
							
								    orig_python_type_for_type: Optional[ProperType] = None,
							 | 
						||
| 
								 | 
							
								) -> Optional[ProperType]:
							 | 
						||
| 
								 | 
							
								    """Validate type when a left hand annotation is present and we also
							 | 
						||
| 
								 | 
							
								    could infer the right hand side::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        attrname: SomeType = Column(SomeDBType)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if orig_left_hand_type is None:
							 | 
						||
| 
								 | 
							
								        orig_left_hand_type = left_hand_explicit_type
							 | 
						||
| 
								 | 
							
								    if orig_python_type_for_type is None:
							 | 
						||
| 
								 | 
							
								        orig_python_type_for_type = python_type_for_type
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if not is_subtype(left_hand_explicit_type, python_type_for_type):
							 | 
						||
| 
								 | 
							
								        effective_type = api.named_type(
							 | 
						||
| 
								 | 
							
								            names.NAMED_TYPE_SQLA_MAPPED, [orig_python_type_for_type]
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        msg = (
							 | 
						||
| 
								 | 
							
								            "Left hand assignment '{}: {}' not compatible "
							 | 
						||
| 
								 | 
							
								            "with ORM mapped expression of type {}"
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								        util.fail(
							 | 
						||
| 
								 | 
							
								            api,
							 | 
						||
| 
								 | 
							
								            msg.format(
							 | 
						||
| 
								 | 
							
								                node.name,
							 | 
						||
| 
								 | 
							
								                format_type(orig_left_hand_type),
							 | 
						||
| 
								 | 
							
								                format_type(effective_type),
							 | 
						||
| 
								 | 
							
								            ),
							 | 
						||
| 
								 | 
							
								            node,
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return orig_left_hand_type
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _infer_collection_type_from_left_and_inferred_right(
							 | 
						||
| 
								 | 
							
								    api: SemanticAnalyzerPluginInterface,
							 | 
						||
| 
								 | 
							
								    node: Var,
							 | 
						||
| 
								 | 
							
								    left_hand_explicit_type: Instance,
							 | 
						||
| 
								 | 
							
								    python_type_for_type: Instance,
							 | 
						||
| 
								 | 
							
								) -> Optional[ProperType]:
							 | 
						||
| 
								 | 
							
								    orig_left_hand_type = left_hand_explicit_type
							 | 
						||
| 
								 | 
							
								    orig_python_type_for_type = python_type_for_type
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if left_hand_explicit_type.args:
							 | 
						||
| 
								 | 
							
								        left_hand_arg = get_proper_type(left_hand_explicit_type.args[0])
							 | 
						||
| 
								 | 
							
								        python_type_arg = get_proper_type(python_type_for_type.args[0])
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        left_hand_arg = left_hand_explicit_type
							 | 
						||
| 
								 | 
							
								        python_type_arg = python_type_for_type
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    assert isinstance(left_hand_arg, (Instance, UnionType))
							 | 
						||
| 
								 | 
							
								    assert isinstance(python_type_arg, (Instance, UnionType))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return _infer_type_from_left_and_inferred_right(
							 | 
						||
| 
								 | 
							
								        api,
							 | 
						||
| 
								 | 
							
								        node,
							 | 
						||
| 
								 | 
							
								        left_hand_arg,
							 | 
						||
| 
								 | 
							
								        python_type_arg,
							 | 
						||
| 
								 | 
							
								        orig_left_hand_type=orig_left_hand_type,
							 | 
						||
| 
								 | 
							
								        orig_python_type_for_type=orig_python_type_for_type,
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def infer_type_from_left_hand_type_only(
							 | 
						||
| 
								 | 
							
								    api: SemanticAnalyzerPluginInterface,
							 | 
						||
| 
								 | 
							
								    node: Var,
							 | 
						||
| 
								 | 
							
								    left_hand_explicit_type: Optional[ProperType],
							 | 
						||
| 
								 | 
							
								) -> Optional[ProperType]:
							 | 
						||
| 
								 | 
							
								    """Determine the type based on explicit annotation only.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if no annotation were present, note that we need one there to know
							 | 
						||
| 
								 | 
							
								    the type.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    if left_hand_explicit_type is None:
							 | 
						||
| 
								 | 
							
								        msg = (
							 | 
						||
| 
								 | 
							
								            "Can't infer type from ORM mapped expression "
							 | 
						||
| 
								 | 
							
								            "assigned to attribute '{}'; please specify a "
							 | 
						||
| 
								 | 
							
								            "Python type or "
							 | 
						||
| 
								 | 
							
								            "Mapped[<python type>] on the left hand side."
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								        util.fail(api, msg.format(node.name), node)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return api.named_type(
							 | 
						||
| 
								 | 
							
								            names.NAMED_TYPE_SQLA_MAPPED, [AnyType(TypeOfAny.special_form)]
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        # use type from the left hand side
							 | 
						||
| 
								 | 
							
								        return left_hand_explicit_type
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def extract_python_type_from_typeengine(
							 | 
						||
| 
								 | 
							
								    api: SemanticAnalyzerPluginInterface,
							 | 
						||
| 
								 | 
							
								    node: TypeInfo,
							 | 
						||
| 
								 | 
							
								    type_args: Sequence[Expression],
							 | 
						||
| 
								 | 
							
								) -> ProperType:
							 | 
						||
| 
								 | 
							
								    if node.fullname == "sqlalchemy.sql.sqltypes.Enum" and type_args:
							 | 
						||
| 
								 | 
							
								        first_arg = type_args[0]
							 | 
						||
| 
								 | 
							
								        if isinstance(first_arg, RefExpr) and isinstance(
							 | 
						||
| 
								 | 
							
								            first_arg.node, TypeInfo
							 | 
						||
| 
								 | 
							
								        ):
							 | 
						||
| 
								 | 
							
								            for base_ in first_arg.node.mro:
							 | 
						||
| 
								 | 
							
								                if base_.fullname == "enum.Enum":
							 | 
						||
| 
								 | 
							
								                    return Instance(first_arg.node, [])
							 | 
						||
| 
								 | 
							
								            # TODO: support other pep-435 types here
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            return api.named_type(names.NAMED_TYPE_BUILTINS_STR, [])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    assert node.has_base("sqlalchemy.sql.type_api.TypeEngine"), (
							 | 
						||
| 
								 | 
							
								        "could not extract Python type from node: %s" % node
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    type_engine_sym = api.lookup_fully_qualified_or_none(
							 | 
						||
| 
								 | 
							
								        "sqlalchemy.sql.type_api.TypeEngine"
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    assert type_engine_sym is not None and isinstance(
							 | 
						||
| 
								 | 
							
								        type_engine_sym.node, TypeInfo
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								    type_engine = map_instance_to_supertype(
							 | 
						||
| 
								 | 
							
								        Instance(node, []),
							 | 
						||
| 
								 | 
							
								        type_engine_sym.node,
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								    return get_proper_type(type_engine.args[-1])
							 |