infer.py 18 KB

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