Skip to content

Commit 65d3d67

Browse files
author
Mehdi
committed
wip
1 parent 7bd2ace commit 65d3d67

File tree

1 file changed

+32
-5
lines changed

1 file changed

+32
-5
lines changed

sqlmypy.py

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
Plugin, FunctionContext, ClassDefContext, DynamicClassDefContext,
44
SemanticAnalyzerPluginInterface
55
)
6-
from mypy.plugins.common import add_method
6+
from mypy.plugins.common import add_method, _get_argument
77
from mypy.nodes import (
88
NameExpr, Expression, StrExpr, TypeInfo, ClassDef, Block, SymbolTable, SymbolTableNode, GDEF,
9-
Argument, Var, ARG_STAR2, MDEF, TupleExpr, RefExpr
9+
Argument, Var, ARG_STAR2, MDEF, TupleExpr, RefExpr, AssignmentStmt, CallExpr, MemberExpr
1010
)
1111
from mypy.types import (
1212
UnionType, NoneTyp, Instance, Type, AnyType, TypeOfAny, UninhabitedType, CallableType
@@ -22,6 +22,7 @@
2222

2323
COLUMN_NAME = 'sqlalchemy.sql.schema.Column' # type: Final
2424
RELATIONSHIP_NAME = 'sqlalchemy.orm.relationships.RelationshipProperty' # type: Final
25+
FOREIGN_KEY_NAME = 'sqlalchemy.sql.schema.ForeignKey' # type: Final
2526

2627

2728
def is_declarative(info: TypeInfo) -> bool:
@@ -60,17 +61,17 @@ def get_function_hook(self, fullname: str) -> Optional[Callable[[FunctionContext
6061
return model_hook
6162
return None
6263

63-
def get_dynamic_class_hook(self, fullname: str) -> CB[DynamicClassDefContext]:
64+
def get_dynamic_class_hook(self, fullname: str):
6465
if fullname == 'sqlalchemy.ext.declarative.api.declarative_base':
6566
return decl_info_hook
6667
return None
6768

68-
def get_class_decorator_hook(self, fullname: str) -> CB[ClassDefContext]:
69+
def get_class_decorator_hook(self, fullname: str):
6970
if fullname == 'sqlalchemy.ext.declarative.api.as_declarative':
7071
return decl_deco_hook
7172
return None
7273

73-
def get_base_class_hook(self, fullname: str) -> CB[ClassDefContext]:
74+
def get_base_class_hook(self, fullname: str):
7475
sym = self.lookup_fully_qualified(fullname)
7576
if sym and isinstance(sym.node, TypeInfo):
7677
if is_declarative(sym.node):
@@ -105,6 +106,32 @@ def add_model_init_hook(ctx: ClassDefContext) -> None:
105106
add_method(ctx, '__init__', [kw_arg], NoneTyp())
106107
ctx.cls.info.metadata.setdefault('sqlalchemy', {})['generated_init'] = True
107108

109+
for stmt in ctx.cls.defs.body:
110+
if not (isinstance(stmt, AssignmentStmt) and len(stmt.lvalues) == 1 and isinstance(stmt.lvalues[0], NameExpr)):
111+
continue
112+
113+
if stmt.lvalues[0].name == "__tablename__" and isinstance(stmt.rvalue, StrExpr):
114+
ctx.cls.info.metadata.setdefault('sqlalchemy', {})['tablename'] = stmt.rvalue.value
115+
116+
if isinstance(stmt.rvalue, CallExpr) and stmt.rvalue.callee.fullname == COLUMN_NAME:
117+
colname = stmt.lvalues[0].name
118+
has_explicit_colname = stmt.rvalue
119+
ctx.cls.info.metadata.setdefault('sqlalchemy', {}).setdefault('columns', []).append(colname)
120+
for arg in stmt.rvalue.args:
121+
if isinstance(arg, CallExpr) and arg.callee.fullname == FOREIGN_KEY_NAME and len(arg.args) >= 1:
122+
fk = arg.args[0]
123+
if isinstance(fk, StrExpr):
124+
*_, parent_table, parent_col = fk.value.split(".")
125+
ctx.cls.info.metadata.setdefault('sqlalchemy', {}).setdefault('foreign_keys', {})[colname] = {
126+
"column": parent_col,
127+
"table": parent_table
128+
}
129+
elif isinstance(fk, MemberExpr):
130+
ctx.cls.info.metadata.setdefault('sqlalchemy', {}).setdefault('foreign_keys', {})[colname] = {
131+
"column": fk.name,
132+
"model": fk.expr.fullname
133+
}
134+
108135
# Also add a selection of auto-generated attributes.
109136
sym = ctx.api.lookup_fully_qualified_or_none('sqlalchemy.sql.schema.Table')
110137
if sym:

0 commit comments

Comments
 (0)