summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/ext/mypy/plugin.py
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2021-03-26 19:45:29 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2021-04-05 19:23:52 -0400
commit606096ae01c71298da4d3fda3f62c730d9985105 (patch)
tree8e745f10d60683edbf1da49a557bebc9994fb7ba /lib/sqlalchemy/ext/mypy/plugin.py
parent4e0f90424afe03547c21ec3b6365755bb5288075 (diff)
downloadsqlalchemy-606096ae01c71298da4d3fda3f62c730d9985105.tar.gz
Adjust for mypy incremental behaviors
Applied a series of refactorings and fixes to accommodate for Mypy "incremental" mode across multiple files, which previously was not taken into account. In this mode the Mypy plugin has to accommodate Python datatypes expressed in other files coming in with less information than they have on a direct run. Additionally, a new decorator :func:`_orm.declarative_mixin` is added, which is necessary for the Mypy plugin to be able to definifitely identify a Declarative mixin class that is otherwise not used inside a particular Python file. discussion: With incremental / deserialized mypy runs, it appears that when we look at a base class that comes from another file, cls.info is set to a special undefined node that matches CLASSDEF_NO_INFO, and we otherwise can't touch it without crashing. Additionally, sometimes cls.defs.body is present but empty. However, it appears that both of these cases can be sidestepped, first by doing a lookup() for the type name where we get a SymbolTableNode that then has the TypeInfo we wanted when we tried touching cls.info, and then however we got the TypeInfo, if cls.defs.body is empty we can just look in the names to get at the symbols for that class; we just can't access AssignmentStmts, but that's fine because we just need the information for classes we aren't actually type checking. This work also revealed there's no easy way to detect a mixin class so we just create a new decorator to mark that. will make code look better in any case. Fixes: #6147 Change-Id: Ia8fac8acfeec931d8f280491cffc5c6cb4a1204e
Diffstat (limited to 'lib/sqlalchemy/ext/mypy/plugin.py')
-rw-r--r--lib/sqlalchemy/ext/mypy/plugin.py31
1 files changed, 20 insertions, 11 deletions
diff --git a/lib/sqlalchemy/ext/mypy/plugin.py b/lib/sqlalchemy/ext/mypy/plugin.py
index 9ca1cb2da..a0aa5bf04 100644
--- a/lib/sqlalchemy/ext/mypy/plugin.py
+++ b/lib/sqlalchemy/ext/mypy/plugin.py
@@ -55,6 +55,7 @@ class CustomPlugin(Plugin):
# subclasses. but then you can just check it here from the "base"
# and get the same effect.
sym = self.lookup_fully_qualified(fullname)
+
if (
sym
and isinstance(sym.node, TypeInfo)
@@ -70,17 +71,18 @@ class CustomPlugin(Plugin):
) -> Optional[Callable[[ClassDefContext], None]]:
sym = self.lookup_fully_qualified(fullname)
- if (
- sym is not None
- and names._type_id_for_named_node(sym.node)
- is names.MAPPED_DECORATOR
- ):
- return _cls_decorator_hook
- elif sym is not None and names._type_id_for_named_node(sym.node) in (
- names.AS_DECLARATIVE,
- names.AS_DECLARATIVE_BASE,
- ):
- return _base_cls_decorator_hook
+
+ if sym is not None:
+ type_id = names._type_id_for_named_node(sym.node)
+ if type_id is names.MAPPED_DECORATOR:
+ return _cls_decorator_hook
+ elif type_id in (
+ names.AS_DECLARATIVE,
+ names.AS_DECLARATIVE_BASE,
+ ):
+ return _base_cls_decorator_hook
+ elif type_id is names.DECLARATIVE_MIXIN:
+ return _declarative_mixin_hook
return None
@@ -192,6 +194,13 @@ def _base_cls_hook(ctx: ClassDefContext) -> None:
decl_class._scan_declarative_assignments_and_apply_types(ctx.cls, ctx.api)
+def _declarative_mixin_hook(ctx: ClassDefContext) -> None:
+ _add_globals(ctx)
+ decl_class._scan_declarative_assignments_and_apply_types(
+ ctx.cls, ctx.api, is_mixin_scan=True
+ )
+
+
def _cls_decorator_hook(ctx: ClassDefContext) -> None:
_add_globals(ctx)
assert isinstance(ctx.reason, nodes.MemberExpr)