From: Mike Bayer Date: Wed, 22 Dec 2021 21:21:33 +0000 (-0500) Subject: use fully qualified, locatable names for all use of api.named_type() X-Git-Tag: rel_2_0_0b1~587 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=aded8b11d9eccbd1f2b645a94338e34a3d234bc9;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git use fully qualified, locatable names for all use of api.named_type() Fixed mypy regression where the release of mypy 0.930 added additional internal checks to the format of "named types", requiring that they be fully qualified and locatable. This broke the mypy plugin for SQLAlchemy, raising an assertion error, as there was use of symbols such as ``__builtins__`` and other un-locatable or unqualified names that previously had not raised any assertions. Fixes: #7496 Change-Id: I037680606a1d51158ef6503508ec76c5d5adc946 --- diff --git a/doc/build/changelog/unreleased_14/7496.rst b/doc/build/changelog/unreleased_14/7496.rst new file mode 100644 index 0000000000..cc5875fcc1 --- /dev/null +++ b/doc/build/changelog/unreleased_14/7496.rst @@ -0,0 +1,11 @@ +.. change:: + :tags: bug, mypy + :tickets: 7496 + + Fixed mypy regression where the release of mypy 0.930 added additional + internal checks to the format of "named types", requiring that they be + fully qualified and locatable. This broke the mypy plugin for SQLAlchemy, + raising an assertion error, as there was use of symbols such as + ``__builtins__`` and other un-locatable or unqualified names that + previously had not raised any assertions. + diff --git a/lib/sqlalchemy/ext/mypy/apply.py b/lib/sqlalchemy/ext/mypy/apply.py index cf5b4fda25..b3af0560c6 100644 --- a/lib/sqlalchemy/ext/mypy/apply.py +++ b/lib/sqlalchemy/ext/mypy/apply.py @@ -36,6 +36,7 @@ from mypy.types import UnionType from . import infer from . import util +from .names import NAMED_TYPE_SQLA_MAPPED def apply_mypy_mapped_attr( @@ -134,7 +135,7 @@ def re_apply_declarative_assignments( and isinstance(stmt.rvalue.callee.expr, NameExpr) and stmt.rvalue.callee.expr.node is not None and stmt.rvalue.callee.expr.node.fullname - == "sqlalchemy.orm.attributes.Mapped" + == NAMED_TYPE_SQLA_MAPPED and stmt.rvalue.callee.name == "_empty_constructor" and isinstance(stmt.rvalue.args[0], CallExpr) and isinstance(stmt.rvalue.args[0].callee, RefExpr) @@ -165,7 +166,7 @@ def re_apply_declarative_assignments( if python_type_for_type is not None: left_node.type = api.named_type( - "__sa_Mapped", [python_type_for_type] + NAMED_TYPE_SQLA_MAPPED, [python_type_for_type] ) if update_cls_metadata: @@ -202,12 +203,12 @@ def apply_type_to_mapped_statement( if left_hand_explicit_type is not None: left_node.type = api.named_type( - "__sa_Mapped", [left_hand_explicit_type] + NAMED_TYPE_SQLA_MAPPED, [left_hand_explicit_type] ) else: lvalue.is_inferred_def = False left_node.type = api.named_type( - "__sa_Mapped", + NAMED_TYPE_SQLA_MAPPED, [] if python_type_for_type is None else [python_type_for_type], ) diff --git a/lib/sqlalchemy/ext/mypy/decl_class.py b/lib/sqlalchemy/ext/mypy/decl_class.py index 0d7462d5bd..c33c30e257 100644 --- a/lib/sqlalchemy/ext/mypy/decl_class.py +++ b/lib/sqlalchemy/ext/mypy/decl_class.py @@ -327,7 +327,7 @@ def _scan_declarative_decorator_stmt( ) left_node.node.type = api.named_type( - "__sa_Mapped", [left_hand_explicit_type] + names.NAMED_TYPE_SQLA_MAPPED, [left_hand_explicit_type] ) # this will ignore the rvalue entirely diff --git a/lib/sqlalchemy/ext/mypy/infer.py b/lib/sqlalchemy/ext/mypy/infer.py index 6d243b6ec1..3cd946e04d 100644 --- a/lib/sqlalchemy/ext/mypy/infer.py +++ b/lib/sqlalchemy/ext/mypy/infer.py @@ -147,7 +147,7 @@ def _infer_type_from_relationship( type_is_a_collection = True if python_type_for_type is not None: python_type_for_type = api.named_type( - "__builtins__.list", [python_type_for_type] + names.NAMED_TYPE_BUILTINS_LIST, [python_type_for_type] ) elif ( uselist_arg is None or api.parse_bool(uselist_arg) is True @@ -438,7 +438,7 @@ def _infer_type_from_left_and_inferred_right( if not is_subtype(left_hand_explicit_type, python_type_for_type): effective_type = api.named_type( - "__sa_Mapped", [orig_python_type_for_type] + names.NAMED_TYPE_SQLA_MAPPED, [orig_python_type_for_type] ) msg = ( @@ -507,7 +507,9 @@ def infer_type_from_left_hand_type_only( ) util.fail(api, msg.format(node.name), node) - return api.named_type("__sa_Mapped", [AnyType(TypeOfAny.special_form)]) + return api.named_type( + names.NAMED_TYPE_SQLA_MAPPED, [AnyType(TypeOfAny.special_form)] + ) else: # use type from the left hand side @@ -529,7 +531,7 @@ def extract_python_type_from_typeengine( return Instance(first_arg.node, []) # TODO: support other pep-435 types here else: - return api.named_type("__builtins__.str", []) + 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 diff --git a/lib/sqlalchemy/ext/mypy/names.py b/lib/sqlalchemy/ext/mypy/names.py index 3dbfcc7703..8ec15a6d43 100644 --- a/lib/sqlalchemy/ext/mypy/names.py +++ b/lib/sqlalchemy/ext/mypy/names.py @@ -47,6 +47,12 @@ AS_DECLARATIVE_BASE: int = util.symbol("AS_DECLARATIVE_BASE") # type: ignore DECLARATIVE_MIXIN: int = util.symbol("DECLARATIVE_MIXIN") # type: ignore QUERY_EXPRESSION: int = util.symbol("QUERY_EXPRESSION") # type: ignore +# names that must succeed with mypy.api.named_type +NAMED_TYPE_BUILTINS_OBJECT = "builtins.object" +NAMED_TYPE_BUILTINS_STR = "builtins.str" +NAMED_TYPE_BUILTINS_LIST = "builtins.list" +NAMED_TYPE_SQLA_MAPPED = "sqlalchemy.orm.attributes.Mapped" + _lookup: Dict[str, Tuple[int, Set[str]]] = { "Column": ( COLUMN, diff --git a/lib/sqlalchemy/ext/mypy/plugin.py b/lib/sqlalchemy/ext/mypy/plugin.py index 356b0d9489..8687012a1e 100644 --- a/lib/sqlalchemy/ext/mypy/plugin.py +++ b/lib/sqlalchemy/ext/mypy/plugin.py @@ -142,7 +142,7 @@ def _dynamic_class_hook(ctx: DynamicClassDefContext) -> None: ) info.bases = [Instance(cls_arg.node, [])] else: - obj = ctx.api.named_type("__builtins__.object") + obj = ctx.api.named_type(names.NAMED_TYPE_BUILTINS_OBJECT) info.bases = [obj] @@ -152,7 +152,7 @@ def _dynamic_class_hook(ctx: DynamicClassDefContext) -> None: util.fail( ctx.api, "Not able to calculate MRO for declarative base", ctx.call ) - obj = ctx.api.named_type("__builtins__.object") + obj = ctx.api.named_type(names.NAMED_TYPE_BUILTINS_OBJECT) info.bases = [obj] info.fallback_to_any = True