infer.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590
  1. # ext/mypy/infer.py
  2. # Copyright (C) 2021-2025 the SQLAlchemy authors and contributors
  3. # <see AUTHORS file>
  4. #
  5. # This module is part of SQLAlchemy and is released under
  6. # the MIT License: https://www.opensource.org/licenses/mit-license.php
  7. from __future__ import annotations
  8. from typing import Optional
  9. from typing import Sequence
  10. from mypy.maptype import map_instance_to_supertype
  11. from mypy.nodes import AssignmentStmt
  12. from mypy.nodes import CallExpr
  13. from mypy.nodes import Expression
  14. from mypy.nodes import FuncDef
  15. from mypy.nodes import LambdaExpr
  16. from mypy.nodes import MemberExpr
  17. from mypy.nodes import NameExpr
  18. from mypy.nodes import RefExpr
  19. from mypy.nodes import StrExpr
  20. from mypy.nodes import TypeInfo
  21. from mypy.nodes import Var
  22. from mypy.plugin import SemanticAnalyzerPluginInterface
  23. from mypy.subtypes import is_subtype
  24. from mypy.types import AnyType
  25. from mypy.types import CallableType
  26. from mypy.types import get_proper_type
  27. from mypy.types import Instance
  28. from mypy.types import NoneType
  29. from mypy.types import ProperType
  30. from mypy.types import TypeOfAny
  31. from mypy.types import UnionType
  32. from . import names
  33. from . import util
  34. def infer_type_from_right_hand_nameexpr(
  35. api: SemanticAnalyzerPluginInterface,
  36. stmt: AssignmentStmt,
  37. node: Var,
  38. left_hand_explicit_type: Optional[ProperType],
  39. infer_from_right_side: RefExpr,
  40. ) -> Optional[ProperType]:
  41. type_id = names.type_id_for_callee(infer_from_right_side)
  42. if type_id is None:
  43. return None
  44. elif type_id is names.MAPPED:
  45. python_type_for_type = _infer_type_from_mapped(
  46. api, stmt, node, left_hand_explicit_type, infer_from_right_side
  47. )
  48. elif type_id is names.COLUMN:
  49. python_type_for_type = _infer_type_from_decl_column(
  50. api, stmt, node, left_hand_explicit_type
  51. )
  52. elif type_id is names.RELATIONSHIP:
  53. python_type_for_type = _infer_type_from_relationship(
  54. api, stmt, node, left_hand_explicit_type
  55. )
  56. elif type_id is names.COLUMN_PROPERTY:
  57. python_type_for_type = _infer_type_from_decl_column_property(
  58. api, stmt, node, left_hand_explicit_type
  59. )
  60. elif type_id is names.SYNONYM_PROPERTY:
  61. python_type_for_type = infer_type_from_left_hand_type_only(
  62. api, node, left_hand_explicit_type
  63. )
  64. elif type_id is names.COMPOSITE_PROPERTY:
  65. python_type_for_type = _infer_type_from_decl_composite_property(
  66. api, stmt, node, left_hand_explicit_type
  67. )
  68. else:
  69. return None
  70. return python_type_for_type
  71. def _infer_type_from_relationship(
  72. api: SemanticAnalyzerPluginInterface,
  73. stmt: AssignmentStmt,
  74. node: Var,
  75. left_hand_explicit_type: Optional[ProperType],
  76. ) -> Optional[ProperType]:
  77. """Infer the type of mapping from a relationship.
  78. E.g.::
  79. @reg.mapped
  80. class MyClass:
  81. # ...
  82. addresses = relationship(Address, uselist=True)
  83. order: Mapped["Order"] = relationship("Order")
  84. Will resolve in mypy as::
  85. @reg.mapped
  86. class MyClass:
  87. # ...
  88. addresses: Mapped[List[Address]]
  89. order: Mapped["Order"]
  90. """
  91. assert isinstance(stmt.rvalue, CallExpr)
  92. target_cls_arg = stmt.rvalue.args[0]
  93. python_type_for_type: Optional[ProperType] = None
  94. if isinstance(target_cls_arg, NameExpr) and isinstance(
  95. target_cls_arg.node, TypeInfo
  96. ):
  97. # type
  98. related_object_type = target_cls_arg.node
  99. python_type_for_type = Instance(related_object_type, [])
  100. # other cases not covered - an error message directs the user
  101. # to set an explicit type annotation
  102. #
  103. # node.type == str, it's a string
  104. # if isinstance(target_cls_arg, NameExpr) and isinstance(
  105. # target_cls_arg.node, Var
  106. # )
  107. # points to a type
  108. # isinstance(target_cls_arg, NameExpr) and isinstance(
  109. # target_cls_arg.node, TypeAlias
  110. # )
  111. # string expression
  112. # isinstance(target_cls_arg, StrExpr)
  113. uselist_arg = util.get_callexpr_kwarg(stmt.rvalue, "uselist")
  114. collection_cls_arg: Optional[Expression] = util.get_callexpr_kwarg(
  115. stmt.rvalue, "collection_class"
  116. )
  117. type_is_a_collection = False
  118. # this can be used to determine Optional for a many-to-one
  119. # in the same way nullable=False could be used, if we start supporting
  120. # that.
  121. # innerjoin_arg = util.get_callexpr_kwarg(stmt.rvalue, "innerjoin")
  122. if (
  123. uselist_arg is not None
  124. and api.parse_bool(uselist_arg) is True
  125. and collection_cls_arg is None
  126. ):
  127. type_is_a_collection = True
  128. if python_type_for_type is not None:
  129. python_type_for_type = api.named_type(
  130. names.NAMED_TYPE_BUILTINS_LIST, [python_type_for_type]
  131. )
  132. elif (
  133. uselist_arg is None or api.parse_bool(uselist_arg) is True
  134. ) and collection_cls_arg is not None:
  135. type_is_a_collection = True
  136. if isinstance(collection_cls_arg, CallExpr):
  137. collection_cls_arg = collection_cls_arg.callee
  138. if isinstance(collection_cls_arg, NameExpr) and isinstance(
  139. collection_cls_arg.node, TypeInfo
  140. ):
  141. if python_type_for_type is not None:
  142. # this can still be overridden by the left hand side
  143. # within _infer_Type_from_left_and_inferred_right
  144. python_type_for_type = Instance(
  145. collection_cls_arg.node, [python_type_for_type]
  146. )
  147. elif (
  148. isinstance(collection_cls_arg, NameExpr)
  149. and isinstance(collection_cls_arg.node, FuncDef)
  150. and collection_cls_arg.node.type is not None
  151. ):
  152. if python_type_for_type is not None:
  153. # this can still be overridden by the left hand side
  154. # within _infer_Type_from_left_and_inferred_right
  155. # TODO: handle mypy.types.Overloaded
  156. if isinstance(collection_cls_arg.node.type, CallableType):
  157. rt = get_proper_type(collection_cls_arg.node.type.ret_type)
  158. if isinstance(rt, CallableType):
  159. callable_ret_type = get_proper_type(rt.ret_type)
  160. if isinstance(callable_ret_type, Instance):
  161. python_type_for_type = Instance(
  162. callable_ret_type.type,
  163. [python_type_for_type],
  164. )
  165. else:
  166. util.fail(
  167. api,
  168. "Expected Python collection type for "
  169. "collection_class parameter",
  170. stmt.rvalue,
  171. )
  172. python_type_for_type = None
  173. elif uselist_arg is not None and api.parse_bool(uselist_arg) is False:
  174. if collection_cls_arg is not None:
  175. util.fail(
  176. api,
  177. "Sending uselist=False and collection_class at the same time "
  178. "does not make sense",
  179. stmt.rvalue,
  180. )
  181. if python_type_for_type is not None:
  182. python_type_for_type = UnionType(
  183. [python_type_for_type, NoneType()]
  184. )
  185. else:
  186. if left_hand_explicit_type is None:
  187. msg = (
  188. "Can't infer scalar or collection for ORM mapped expression "
  189. "assigned to attribute '{}' if both 'uselist' and "
  190. "'collection_class' arguments are absent from the "
  191. "relationship(); please specify a "
  192. "type annotation on the left hand side."
  193. )
  194. util.fail(api, msg.format(node.name), node)
  195. if python_type_for_type is None:
  196. return infer_type_from_left_hand_type_only(
  197. api, node, left_hand_explicit_type
  198. )
  199. elif left_hand_explicit_type is not None:
  200. if type_is_a_collection:
  201. assert isinstance(left_hand_explicit_type, Instance)
  202. assert isinstance(python_type_for_type, Instance)
  203. return _infer_collection_type_from_left_and_inferred_right(
  204. api, node, left_hand_explicit_type, python_type_for_type
  205. )
  206. else:
  207. return _infer_type_from_left_and_inferred_right(
  208. api,
  209. node,
  210. left_hand_explicit_type,
  211. python_type_for_type,
  212. )
  213. else:
  214. return python_type_for_type
  215. def _infer_type_from_decl_composite_property(
  216. api: SemanticAnalyzerPluginInterface,
  217. stmt: AssignmentStmt,
  218. node: Var,
  219. left_hand_explicit_type: Optional[ProperType],
  220. ) -> Optional[ProperType]:
  221. """Infer the type of mapping from a Composite."""
  222. assert isinstance(stmt.rvalue, CallExpr)
  223. target_cls_arg = stmt.rvalue.args[0]
  224. python_type_for_type = None
  225. if isinstance(target_cls_arg, NameExpr) and isinstance(
  226. target_cls_arg.node, TypeInfo
  227. ):
  228. related_object_type = target_cls_arg.node
  229. python_type_for_type = Instance(related_object_type, [])
  230. else:
  231. python_type_for_type = None
  232. if python_type_for_type is None:
  233. return infer_type_from_left_hand_type_only(
  234. api, node, left_hand_explicit_type
  235. )
  236. elif left_hand_explicit_type is not None:
  237. return _infer_type_from_left_and_inferred_right(
  238. api, node, left_hand_explicit_type, python_type_for_type
  239. )
  240. else:
  241. return python_type_for_type
  242. def _infer_type_from_mapped(
  243. api: SemanticAnalyzerPluginInterface,
  244. stmt: AssignmentStmt,
  245. node: Var,
  246. left_hand_explicit_type: Optional[ProperType],
  247. infer_from_right_side: RefExpr,
  248. ) -> Optional[ProperType]:
  249. """Infer the type of mapping from a right side expression
  250. that returns Mapped.
  251. """
  252. assert isinstance(stmt.rvalue, CallExpr)
  253. # (Pdb) print(stmt.rvalue.callee)
  254. # NameExpr(query_expression [sqlalchemy.orm._orm_constructors.query_expression]) # noqa: E501
  255. # (Pdb) stmt.rvalue.callee.node
  256. # <mypy.nodes.FuncDef object at 0x7f8d92fb5940>
  257. # (Pdb) stmt.rvalue.callee.node.type
  258. # def [_T] (default_expr: sqlalchemy.sql.elements.ColumnElement[_T`-1] =) -> sqlalchemy.orm.base.Mapped[_T`-1] # noqa: E501
  259. # sqlalchemy.orm.base.Mapped[_T`-1]
  260. # the_mapped_type = stmt.rvalue.callee.node.type.ret_type
  261. # TODO: look at generic ref and either use that,
  262. # or reconcile w/ what's present, etc.
  263. the_mapped_type = util.type_for_callee(infer_from_right_side) # noqa
  264. return infer_type_from_left_hand_type_only(
  265. api, node, left_hand_explicit_type
  266. )
  267. def _infer_type_from_decl_column_property(
  268. api: SemanticAnalyzerPluginInterface,
  269. stmt: AssignmentStmt,
  270. node: Var,
  271. left_hand_explicit_type: Optional[ProperType],
  272. ) -> Optional[ProperType]:
  273. """Infer the type of mapping from a ColumnProperty.
  274. This includes mappings against ``column_property()`` as well as the
  275. ``deferred()`` function.
  276. """
  277. assert isinstance(stmt.rvalue, CallExpr)
  278. if stmt.rvalue.args:
  279. first_prop_arg = stmt.rvalue.args[0]
  280. if isinstance(first_prop_arg, CallExpr):
  281. type_id = names.type_id_for_callee(first_prop_arg.callee)
  282. # look for column_property() / deferred() etc with Column as first
  283. # argument
  284. if type_id is names.COLUMN:
  285. return _infer_type_from_decl_column(
  286. api,
  287. stmt,
  288. node,
  289. left_hand_explicit_type,
  290. right_hand_expression=first_prop_arg,
  291. )
  292. if isinstance(stmt.rvalue, CallExpr):
  293. type_id = names.type_id_for_callee(stmt.rvalue.callee)
  294. # this is probably not strictly necessary as we have to use the left
  295. # hand type for query expression in any case. any other no-arg
  296. # column prop objects would go here also
  297. if type_id is names.QUERY_EXPRESSION:
  298. return _infer_type_from_decl_column(
  299. api,
  300. stmt,
  301. node,
  302. left_hand_explicit_type,
  303. )
  304. return infer_type_from_left_hand_type_only(
  305. api, node, left_hand_explicit_type
  306. )
  307. def _infer_type_from_decl_column(
  308. api: SemanticAnalyzerPluginInterface,
  309. stmt: AssignmentStmt,
  310. node: Var,
  311. left_hand_explicit_type: Optional[ProperType],
  312. right_hand_expression: Optional[CallExpr] = None,
  313. ) -> Optional[ProperType]:
  314. """Infer the type of mapping from a Column.
  315. E.g.::
  316. @reg.mapped
  317. class MyClass:
  318. # ...
  319. a = Column(Integer)
  320. b = Column("b", String)
  321. c: Mapped[int] = Column(Integer)
  322. d: bool = Column(Boolean)
  323. Will resolve in MyPy as::
  324. @reg.mapped
  325. class MyClass:
  326. # ...
  327. a: Mapped[int]
  328. b: Mapped[str]
  329. c: Mapped[int]
  330. d: Mapped[bool]
  331. """
  332. assert isinstance(node, Var)
  333. callee = None
  334. if right_hand_expression is None:
  335. if not isinstance(stmt.rvalue, CallExpr):
  336. return None
  337. right_hand_expression = stmt.rvalue
  338. for column_arg in right_hand_expression.args[0:2]:
  339. if isinstance(column_arg, CallExpr):
  340. if isinstance(column_arg.callee, RefExpr):
  341. # x = Column(String(50))
  342. callee = column_arg.callee
  343. type_args: Sequence[Expression] = column_arg.args
  344. break
  345. elif isinstance(column_arg, (NameExpr, MemberExpr)):
  346. if isinstance(column_arg.node, TypeInfo):
  347. # x = Column(String)
  348. callee = column_arg
  349. type_args = ()
  350. break
  351. else:
  352. # x = Column(some_name, String), go to next argument
  353. continue
  354. elif isinstance(column_arg, (StrExpr,)):
  355. # x = Column("name", String), go to next argument
  356. continue
  357. elif isinstance(column_arg, (LambdaExpr,)):
  358. # x = Column("name", String, default=lambda: uuid.uuid4())
  359. # go to next argument
  360. continue
  361. else:
  362. assert False
  363. if callee is None:
  364. return None
  365. if isinstance(callee.node, TypeInfo) and names.mro_has_id(
  366. callee.node.mro, names.TYPEENGINE
  367. ):
  368. python_type_for_type = extract_python_type_from_typeengine(
  369. api, callee.node, type_args
  370. )
  371. if left_hand_explicit_type is not None:
  372. return _infer_type_from_left_and_inferred_right(
  373. api, node, left_hand_explicit_type, python_type_for_type
  374. )
  375. else:
  376. return UnionType([python_type_for_type, NoneType()])
  377. else:
  378. # it's not TypeEngine, it's typically implicitly typed
  379. # like ForeignKey. we can't infer from the right side.
  380. return infer_type_from_left_hand_type_only(
  381. api, node, left_hand_explicit_type
  382. )
  383. def _infer_type_from_left_and_inferred_right(
  384. api: SemanticAnalyzerPluginInterface,
  385. node: Var,
  386. left_hand_explicit_type: ProperType,
  387. python_type_for_type: ProperType,
  388. orig_left_hand_type: Optional[ProperType] = None,
  389. orig_python_type_for_type: Optional[ProperType] = None,
  390. ) -> Optional[ProperType]:
  391. """Validate type when a left hand annotation is present and we also
  392. could infer the right hand side::
  393. attrname: SomeType = Column(SomeDBType)
  394. """
  395. if orig_left_hand_type is None:
  396. orig_left_hand_type = left_hand_explicit_type
  397. if orig_python_type_for_type is None:
  398. orig_python_type_for_type = python_type_for_type
  399. if not is_subtype(left_hand_explicit_type, python_type_for_type):
  400. effective_type = api.named_type(
  401. names.NAMED_TYPE_SQLA_MAPPED, [orig_python_type_for_type]
  402. )
  403. msg = (
  404. "Left hand assignment '{}: {}' not compatible "
  405. "with ORM mapped expression of type {}"
  406. )
  407. util.fail(
  408. api,
  409. msg.format(
  410. node.name,
  411. util.format_type(orig_left_hand_type, api.options),
  412. util.format_type(effective_type, api.options),
  413. ),
  414. node,
  415. )
  416. return orig_left_hand_type
  417. def _infer_collection_type_from_left_and_inferred_right(
  418. api: SemanticAnalyzerPluginInterface,
  419. node: Var,
  420. left_hand_explicit_type: Instance,
  421. python_type_for_type: Instance,
  422. ) -> Optional[ProperType]:
  423. orig_left_hand_type = left_hand_explicit_type
  424. orig_python_type_for_type = python_type_for_type
  425. if left_hand_explicit_type.args:
  426. left_hand_arg = get_proper_type(left_hand_explicit_type.args[0])
  427. python_type_arg = get_proper_type(python_type_for_type.args[0])
  428. else:
  429. left_hand_arg = left_hand_explicit_type
  430. python_type_arg = python_type_for_type
  431. assert isinstance(left_hand_arg, (Instance, UnionType))
  432. assert isinstance(python_type_arg, (Instance, UnionType))
  433. return _infer_type_from_left_and_inferred_right(
  434. api,
  435. node,
  436. left_hand_arg,
  437. python_type_arg,
  438. orig_left_hand_type=orig_left_hand_type,
  439. orig_python_type_for_type=orig_python_type_for_type,
  440. )
  441. def infer_type_from_left_hand_type_only(
  442. api: SemanticAnalyzerPluginInterface,
  443. node: Var,
  444. left_hand_explicit_type: Optional[ProperType],
  445. ) -> Optional[ProperType]:
  446. """Determine the type based on explicit annotation only.
  447. if no annotation were present, note that we need one there to know
  448. the type.
  449. """
  450. if left_hand_explicit_type is None:
  451. msg = (
  452. "Can't infer type from ORM mapped expression "
  453. "assigned to attribute '{}'; please specify a "
  454. "Python type or "
  455. "Mapped[<python type>] on the left hand side."
  456. )
  457. util.fail(api, msg.format(node.name), node)
  458. return api.named_type(
  459. names.NAMED_TYPE_SQLA_MAPPED, [AnyType(TypeOfAny.special_form)]
  460. )
  461. else:
  462. # use type from the left hand side
  463. return left_hand_explicit_type
  464. def extract_python_type_from_typeengine(
  465. api: SemanticAnalyzerPluginInterface,
  466. node: TypeInfo,
  467. type_args: Sequence[Expression],
  468. ) -> ProperType:
  469. if node.fullname == "sqlalchemy.sql.sqltypes.Enum" and type_args:
  470. first_arg = type_args[0]
  471. if isinstance(first_arg, RefExpr) and isinstance(
  472. first_arg.node, TypeInfo
  473. ):
  474. for base_ in first_arg.node.mro:
  475. if base_.fullname == "enum.Enum":
  476. return Instance(first_arg.node, [])
  477. # TODO: support other pep-435 types here
  478. else:
  479. return api.named_type(names.NAMED_TYPE_BUILTINS_STR, [])
  480. assert node.has_base("sqlalchemy.sql.type_api.TypeEngine"), (
  481. "could not extract Python type from node: %s" % node
  482. )
  483. type_engine_sym = api.lookup_fully_qualified_or_none(
  484. "sqlalchemy.sql.type_api.TypeEngine"
  485. )
  486. assert type_engine_sym is not None and isinstance(
  487. type_engine_sym.node, TypeInfo
  488. )
  489. type_engine = map_instance_to_supertype(
  490. Instance(node, []),
  491. type_engine_sym.node,
  492. )
  493. return get_proper_type(type_engine.args[-1])