Skip to content

Commit f6c1105

Browse files
rafalesilevkivskyi
authored andcommitted
Better mixins support (#80)
This addresses #75 by looking at all columns available in mro, not just topmost model. It doesn't address the rest of the "mixins" story such as `declared_attr`.
1 parent 638b850 commit f6c1105

File tree

2 files changed

+48
-6
lines changed

2 files changed

+48
-6
lines changed

sqlmypy.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -209,12 +209,13 @@ def model_hook(ctx: FunctionContext) -> Type:
209209
# Collect column names and types defined in the model
210210
# TODO: cache this?
211211
expected_types = {} # type: Dict[str, Type]
212-
for name, sym in model.names.items():
213-
if isinstance(sym.node, Var) and isinstance(sym.node.type, Instance):
214-
tp = sym.node.type
215-
if tp.type.fullname() in (COLUMN_NAME, RELATIONSHIP_NAME):
216-
assert len(tp.args) == 1
217-
expected_types[name] = tp.args[0]
212+
for cls in model.mro[::-1]:
213+
for name, sym in cls.names.items():
214+
if isinstance(sym.node, Var) and isinstance(sym.node.type, Instance):
215+
tp = sym.node.type
216+
if tp.type.fullname() in (COLUMN_NAME, RELATIONSHIP_NAME):
217+
assert len(tp.args) == 1
218+
expected_types[name] = tp.args[0]
218219

219220
assert len(ctx.arg_names) == 1 # only **kwargs in generated __init__
220221
assert len(ctx.arg_types) == 1

test/test-data/sqlalchemy-plugin-features.test

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,47 @@ class Base:
5050
...
5151
[out]
5252

53+
[case testModelInitMixin]
54+
55+
from sqlalchemy import Column, Integer, String, DateTime
56+
from sqlalchemy.ext.declarative import declarative_base
57+
58+
Base = declarative_base()
59+
60+
class HasId:
61+
id = Column(Integer, primary_key=True)
62+
63+
class User(Base, HasId):
64+
__tablename__ = 'users'
65+
66+
name = Column(String, nullable=False)
67+
68+
user = User(id=123, name="John Doe")
69+
reveal_type(user.id) # N: Revealed type is 'builtins.int*'
70+
[out]
71+
72+
[case testModelInitProperMro]
73+
74+
from sqlalchemy import Column, Integer, String, DateTime
75+
from sqlalchemy.ext.declarative import declarative_base
76+
77+
Base = declarative_base()
78+
79+
class Defaults:
80+
id = Column(Integer, primary_key=True)
81+
82+
class User(Base, Defaults):
83+
__tablename__ = 'users'
84+
85+
# By default mypy will complain about Column[str] not being compatible with Column[int].
86+
# Adding "ignore" should allow us to override column type.
87+
id = Column(String, primary_key=True) # type: ignore
88+
name = Column(String, nullable=False)
89+
90+
User(id='stringish-id')
91+
User(id=123) # E: Incompatible type for "id" of "User" (got "int", expected "str")
92+
[out]
93+
5394
[case testModelInitRelationship]
5495
from typing import TYPE_CHECKING, List
5596

0 commit comments

Comments
 (0)