You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

535 lines
17KB

  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: http://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: NameExpr,
  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 = _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. "__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. first_prop_arg = stmt.rvalue.args[0]
  249. if isinstance(first_prop_arg, CallExpr):
  250. type_id = names._type_id_for_callee(first_prop_arg.callee)
  251. # look for column_property() / deferred() etc with Column as first
  252. # argument
  253. if type_id is names.COLUMN:
  254. return _infer_type_from_decl_column(
  255. api,
  256. stmt,
  257. node,
  258. left_hand_explicit_type,
  259. right_hand_expression=first_prop_arg,
  260. )
  261. return _infer_type_from_left_hand_type_only(
  262. api, node, left_hand_explicit_type
  263. )
  264. def _infer_type_from_decl_column(
  265. api: SemanticAnalyzerPluginInterface,
  266. stmt: AssignmentStmt,
  267. node: Var,
  268. left_hand_explicit_type: Optional[ProperType],
  269. right_hand_expression: Optional[CallExpr] = None,
  270. ) -> Optional[ProperType]:
  271. """Infer the type of mapping from a Column.
  272. E.g.::
  273. @reg.mapped
  274. class MyClass:
  275. # ...
  276. a = Column(Integer)
  277. b = Column("b", String)
  278. c: Mapped[int] = Column(Integer)
  279. d: bool = Column(Boolean)
  280. Will resolve in MyPy as::
  281. @reg.mapped
  282. class MyClass:
  283. # ...
  284. a : Mapped[int]
  285. b : Mapped[str]
  286. c: Mapped[int]
  287. d: Mapped[bool]
  288. """
  289. assert isinstance(node, Var)
  290. callee = None
  291. if right_hand_expression is None:
  292. if not isinstance(stmt.rvalue, CallExpr):
  293. return None
  294. right_hand_expression = stmt.rvalue
  295. for column_arg in right_hand_expression.args[0:2]:
  296. if isinstance(column_arg, CallExpr):
  297. if isinstance(column_arg.callee, RefExpr):
  298. # x = Column(String(50))
  299. callee = column_arg.callee
  300. type_args: Sequence[Expression] = column_arg.args
  301. break
  302. elif isinstance(column_arg, (NameExpr, MemberExpr)):
  303. if isinstance(column_arg.node, TypeInfo):
  304. # x = Column(String)
  305. callee = column_arg
  306. type_args = ()
  307. break
  308. else:
  309. # x = Column(some_name, String), go to next argument
  310. continue
  311. elif isinstance(column_arg, (StrExpr,)):
  312. # x = Column("name", String), go to next argument
  313. continue
  314. else:
  315. assert False
  316. if callee is None:
  317. return None
  318. if isinstance(callee.node, TypeInfo) and names._mro_has_id(
  319. callee.node.mro, names.TYPEENGINE
  320. ):
  321. python_type_for_type = _extract_python_type_from_typeengine(
  322. api, callee.node, type_args
  323. )
  324. if left_hand_explicit_type is not None:
  325. return _infer_type_from_left_and_inferred_right(
  326. api, node, left_hand_explicit_type, python_type_for_type
  327. )
  328. else:
  329. return UnionType([python_type_for_type, NoneType()])
  330. else:
  331. # it's not TypeEngine, it's typically implicitly typed
  332. # like ForeignKey. we can't infer from the right side.
  333. return _infer_type_from_left_hand_type_only(
  334. api, node, left_hand_explicit_type
  335. )
  336. def _infer_type_from_left_and_inferred_right(
  337. api: SemanticAnalyzerPluginInterface,
  338. node: Var,
  339. left_hand_explicit_type: ProperType,
  340. python_type_for_type: ProperType,
  341. orig_left_hand_type: Optional[ProperType] = None,
  342. orig_python_type_for_type: Optional[ProperType] = None,
  343. ) -> Optional[ProperType]:
  344. """Validate type when a left hand annotation is present and we also
  345. could infer the right hand side::
  346. attrname: SomeType = Column(SomeDBType)
  347. """
  348. if orig_left_hand_type is None:
  349. orig_left_hand_type = left_hand_explicit_type
  350. if orig_python_type_for_type is None:
  351. orig_python_type_for_type = python_type_for_type
  352. if not is_subtype(left_hand_explicit_type, python_type_for_type):
  353. effective_type = api.named_type(
  354. "__sa_Mapped", [orig_python_type_for_type]
  355. )
  356. msg = (
  357. "Left hand assignment '{}: {}' not compatible "
  358. "with ORM mapped expression of type {}"
  359. )
  360. util.fail(
  361. api,
  362. msg.format(
  363. node.name,
  364. format_type(orig_left_hand_type),
  365. format_type(effective_type),
  366. ),
  367. node,
  368. )
  369. return orig_left_hand_type
  370. def _infer_collection_type_from_left_and_inferred_right(
  371. api: SemanticAnalyzerPluginInterface,
  372. node: Var,
  373. left_hand_explicit_type: Instance,
  374. python_type_for_type: Instance,
  375. ) -> Optional[ProperType]:
  376. orig_left_hand_type = left_hand_explicit_type
  377. orig_python_type_for_type = python_type_for_type
  378. if left_hand_explicit_type.args:
  379. left_hand_arg = get_proper_type(left_hand_explicit_type.args[0])
  380. python_type_arg = get_proper_type(python_type_for_type.args[0])
  381. else:
  382. left_hand_arg = left_hand_explicit_type
  383. python_type_arg = python_type_for_type
  384. assert isinstance(left_hand_arg, (Instance, UnionType))
  385. assert isinstance(python_type_arg, (Instance, UnionType))
  386. return _infer_type_from_left_and_inferred_right(
  387. api,
  388. node,
  389. left_hand_arg,
  390. python_type_arg,
  391. orig_left_hand_type=orig_left_hand_type,
  392. orig_python_type_for_type=orig_python_type_for_type,
  393. )
  394. def _infer_type_from_left_hand_type_only(
  395. api: SemanticAnalyzerPluginInterface,
  396. node: Var,
  397. left_hand_explicit_type: Optional[ProperType],
  398. ) -> Optional[ProperType]:
  399. """Determine the type based on explicit annotation only.
  400. if no annotation were present, note that we need one there to know
  401. the type.
  402. """
  403. if left_hand_explicit_type is None:
  404. msg = (
  405. "Can't infer type from ORM mapped expression "
  406. "assigned to attribute '{}'; please specify a "
  407. "Python type or "
  408. "Mapped[<python type>] on the left hand side."
  409. )
  410. util.fail(api, msg.format(node.name), node)
  411. return api.named_type("__sa_Mapped", [AnyType(TypeOfAny.special_form)])
  412. else:
  413. # use type from the left hand side
  414. return left_hand_explicit_type
  415. def _extract_python_type_from_typeengine(
  416. api: SemanticAnalyzerPluginInterface,
  417. node: TypeInfo,
  418. type_args: Sequence[Expression],
  419. ) -> ProperType:
  420. if node.fullname == "sqlalchemy.sql.sqltypes.Enum" and type_args:
  421. first_arg = type_args[0]
  422. if isinstance(first_arg, NameExpr) and isinstance(
  423. first_arg.node, TypeInfo
  424. ):
  425. for base_ in first_arg.node.mro:
  426. if base_.fullname == "enum.Enum":
  427. return Instance(first_arg.node, [])
  428. # TODO: support other pep-435 types here
  429. else:
  430. return api.named_type("__builtins__.str", [])
  431. assert node.has_base("sqlalchemy.sql.type_api.TypeEngine"), (
  432. "could not extract Python type from node: %s" % node
  433. )
  434. type_engine_sym = api.lookup_fully_qualified_or_none(
  435. "sqlalchemy.sql.type_api.TypeEngine"
  436. )
  437. assert type_engine_sym is not None and isinstance(
  438. type_engine_sym.node, TypeInfo
  439. )
  440. type_engine = map_instance_to_supertype(
  441. Instance(node, []),
  442. type_engine_sym.node,
  443. )
  444. return get_proper_type(type_engine.args[-1])