diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2021-11-14 20:02:10 -0500 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2021-11-17 17:48:00 -0500 |
| commit | 836902bc8438a800d2c9cf1452da31d3ca967f3b (patch) | |
| tree | a1a071e18c4d181e894a38b3cd0ca0493400ed7e /lib/sqlalchemy | |
| parent | 6206f0ff74e95c9339dc0f0e26caab55e9bcda45 (diff) | |
| download | sqlalchemy-836902bc8438a800d2c9cf1452da31d3ca967f3b.tar.gz | |
handle dunder names in @declared_attr separately
Fixed Mypy crash which would occur when using Mypy plugin against code
which made use of :class:`_orm.declared_attr` methods for non-mapped names
like ``__mapper_args__``, ``__table_args__``, or other dunder names, as the
plugin would try to interpret these as mapped attributes which would then
be later mis-handled. As part of this change, the decorated function is
still converted by the plugin into a generic assignment statement (e.g.
``__mapper_args__: Any``) so that the argument signature can continue to be
annotated in the same way one would for any other ``@classmethod`` without
Mypy complaining about the wrong argument type for a method that isn't
explicitly ``@classmethod``.
Fixes: #7321
Change-Id: I55656e867876677c5c55143449db371344be8600
Diffstat (limited to 'lib/sqlalchemy')
| -rw-r--r-- | lib/sqlalchemy/ext/mypy/decl_class.py | 15 | ||||
| -rw-r--r-- | lib/sqlalchemy/ext/mypy/util.py | 5 |
2 files changed, 19 insertions, 1 deletions
diff --git a/lib/sqlalchemy/ext/mypy/decl_class.py b/lib/sqlalchemy/ext/mypy/decl_class.py index b85ec0f69..0d7462d5b 100644 --- a/lib/sqlalchemy/ext/mypy/decl_class.py +++ b/lib/sqlalchemy/ext/mypy/decl_class.py @@ -241,7 +241,20 @@ def _scan_declarative_decorator_stmt( left_hand_explicit_type: Optional[ProperType] = None - if isinstance(stmt.func.type, CallableType): + if util.name_is_dunder(stmt.name): + # for dunder names like __table_args__, __tablename__, + # __mapper_args__ etc., rewrite these as simple assignment + # statements; otherwise mypy doesn't like if the decorated + # function has an annotation like ``cls: Type[Foo]`` because + # it isn't @classmethod + any_ = AnyType(TypeOfAny.special_form) + left_node = NameExpr(stmt.var.name) + left_node.node = stmt.var + new_stmt = AssignmentStmt([left_node], TempNode(any_)) + new_stmt.type = left_node.node.type + cls.defs.body[dec_index] = new_stmt + return + elif isinstance(stmt.func.type, CallableType): func_type = stmt.func.type.ret_type if isinstance(func_type, UnboundType): type_id = names.type_id_for_unbound_type(func_type, cls, api) diff --git a/lib/sqlalchemy/ext/mypy/util.py b/lib/sqlalchemy/ext/mypy/util.py index a3825f175..4d55cb728 100644 --- a/lib/sqlalchemy/ext/mypy/util.py +++ b/lib/sqlalchemy/ext/mypy/util.py @@ -1,3 +1,4 @@ +import re from typing import Any from typing import Iterable from typing import Iterator @@ -82,6 +83,10 @@ class SQLAlchemyAttribute: return cls(typ=typ, info=info, **data) +def name_is_dunder(name): + return bool(re.match(r"^__.+?__$", name)) + + def _set_info_metadata(info: TypeInfo, key: str, data: Any) -> None: info.metadata.setdefault("sqlalchemy", {})[key] = data |
