properties.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879
  1. # orm/properties.py
  2. # Copyright (C) 2005-2024 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. """MapperProperty implementations.
  8. This is a private module which defines the behavior of individual ORM-
  9. mapped attributes.
  10. """
  11. from __future__ import annotations
  12. from typing import Any
  13. from typing import cast
  14. from typing import Dict
  15. from typing import List
  16. from typing import Optional
  17. from typing import Sequence
  18. from typing import Set
  19. from typing import Tuple
  20. from typing import Type
  21. from typing import TYPE_CHECKING
  22. from typing import TypeVar
  23. from typing import Union
  24. from . import attributes
  25. from . import strategy_options
  26. from .base import _DeclarativeMapped
  27. from .base import class_mapper
  28. from .descriptor_props import CompositeProperty
  29. from .descriptor_props import ConcreteInheritedProperty
  30. from .descriptor_props import SynonymProperty
  31. from .interfaces import _AttributeOptions
  32. from .interfaces import _DEFAULT_ATTRIBUTE_OPTIONS
  33. from .interfaces import _IntrospectsAnnotations
  34. from .interfaces import _MapsColumns
  35. from .interfaces import MapperProperty
  36. from .interfaces import PropComparator
  37. from .interfaces import StrategizedProperty
  38. from .relationships import RelationshipProperty
  39. from .util import de_stringify_annotation
  40. from .util import de_stringify_union_elements
  41. from .. import exc as sa_exc
  42. from .. import ForeignKey
  43. from .. import log
  44. from .. import util
  45. from ..sql import coercions
  46. from ..sql import roles
  47. from ..sql.base import _NoArg
  48. from ..sql.schema import Column
  49. from ..sql.schema import SchemaConst
  50. from ..sql.type_api import TypeEngine
  51. from ..util.typing import de_optionalize_union_types
  52. from ..util.typing import is_fwd_ref
  53. from ..util.typing import is_optional_union
  54. from ..util.typing import is_pep593
  55. from ..util.typing import is_union
  56. from ..util.typing import Self
  57. from ..util.typing import typing_get_args
  58. if TYPE_CHECKING:
  59. from ._typing import _IdentityKeyType
  60. from ._typing import _InstanceDict
  61. from ._typing import _ORMColumnExprArgument
  62. from ._typing import _RegistryType
  63. from .base import Mapped
  64. from .decl_base import _ClassScanMapperConfig
  65. from .mapper import Mapper
  66. from .session import Session
  67. from .state import _InstallLoaderCallableProto
  68. from .state import InstanceState
  69. from ..sql._typing import _InfoType
  70. from ..sql.elements import ColumnElement
  71. from ..sql.elements import NamedColumn
  72. from ..sql.operators import OperatorType
  73. from ..util.typing import _AnnotationScanType
  74. from ..util.typing import RODescriptorReference
  75. _T = TypeVar("_T", bound=Any)
  76. _PT = TypeVar("_PT", bound=Any)
  77. _NC = TypeVar("_NC", bound="NamedColumn[Any]")
  78. __all__ = [
  79. "ColumnProperty",
  80. "CompositeProperty",
  81. "ConcreteInheritedProperty",
  82. "RelationshipProperty",
  83. "SynonymProperty",
  84. ]
  85. @log.class_logger
  86. class ColumnProperty(
  87. _MapsColumns[_T],
  88. StrategizedProperty[_T],
  89. _IntrospectsAnnotations,
  90. log.Identified,
  91. ):
  92. """Describes an object attribute that corresponds to a table column
  93. or other column expression.
  94. Public constructor is the :func:`_orm.column_property` function.
  95. """
  96. strategy_wildcard_key = strategy_options._COLUMN_TOKEN
  97. inherit_cache = True
  98. """:meta private:"""
  99. _links_to_entity = False
  100. columns: List[NamedColumn[Any]]
  101. _is_polymorphic_discriminator: bool
  102. _mapped_by_synonym: Optional[str]
  103. comparator_factory: Type[PropComparator[_T]]
  104. __slots__ = (
  105. "columns",
  106. "group",
  107. "deferred",
  108. "instrument",
  109. "comparator_factory",
  110. "active_history",
  111. "expire_on_flush",
  112. "_creation_order",
  113. "_is_polymorphic_discriminator",
  114. "_mapped_by_synonym",
  115. "_deferred_column_loader",
  116. "_raise_column_loader",
  117. "_renders_in_subqueries",
  118. "raiseload",
  119. )
  120. def __init__(
  121. self,
  122. column: _ORMColumnExprArgument[_T],
  123. *additional_columns: _ORMColumnExprArgument[Any],
  124. attribute_options: Optional[_AttributeOptions] = None,
  125. group: Optional[str] = None,
  126. deferred: bool = False,
  127. raiseload: bool = False,
  128. comparator_factory: Optional[Type[PropComparator[_T]]] = None,
  129. active_history: bool = False,
  130. expire_on_flush: bool = True,
  131. info: Optional[_InfoType] = None,
  132. doc: Optional[str] = None,
  133. _instrument: bool = True,
  134. _assume_readonly_dc_attributes: bool = False,
  135. ):
  136. super().__init__(
  137. attribute_options=attribute_options,
  138. _assume_readonly_dc_attributes=_assume_readonly_dc_attributes,
  139. )
  140. columns = (column,) + additional_columns
  141. self.columns = [
  142. coercions.expect(roles.LabeledColumnExprRole, c) for c in columns
  143. ]
  144. self.group = group
  145. self.deferred = deferred
  146. self.raiseload = raiseload
  147. self.instrument = _instrument
  148. self.comparator_factory = (
  149. comparator_factory
  150. if comparator_factory is not None
  151. else self.__class__.Comparator
  152. )
  153. self.active_history = active_history
  154. self.expire_on_flush = expire_on_flush
  155. if info is not None:
  156. self.info.update(info)
  157. if doc is not None:
  158. self.doc = doc
  159. else:
  160. for col in reversed(self.columns):
  161. doc = getattr(col, "doc", None)
  162. if doc is not None:
  163. self.doc = doc
  164. break
  165. else:
  166. self.doc = None
  167. util.set_creation_order(self)
  168. self.strategy_key = (
  169. ("deferred", self.deferred),
  170. ("instrument", self.instrument),
  171. )
  172. if self.raiseload:
  173. self.strategy_key += (("raiseload", True),)
  174. def declarative_scan(
  175. self,
  176. decl_scan: _ClassScanMapperConfig,
  177. registry: _RegistryType,
  178. cls: Type[Any],
  179. originating_module: Optional[str],
  180. key: str,
  181. mapped_container: Optional[Type[Mapped[Any]]],
  182. annotation: Optional[_AnnotationScanType],
  183. extracted_mapped_annotation: Optional[_AnnotationScanType],
  184. is_dataclass_field: bool,
  185. ) -> None:
  186. column = self.columns[0]
  187. if column.key is None:
  188. column.key = key
  189. if column.name is None:
  190. column.name = key
  191. @property
  192. def mapper_property_to_assign(self) -> Optional[MapperProperty[_T]]:
  193. return self
  194. @property
  195. def columns_to_assign(self) -> List[Tuple[Column[Any], int]]:
  196. # mypy doesn't care about the isinstance here
  197. return [
  198. (c, 0) # type: ignore
  199. for c in self.columns
  200. if isinstance(c, Column) and c.table is None
  201. ]
  202. def _memoized_attr__renders_in_subqueries(self) -> bool:
  203. if ("query_expression", True) in self.strategy_key:
  204. return self.strategy._have_default_expression # type: ignore
  205. return ("deferred", True) not in self.strategy_key or (
  206. self not in self.parent._readonly_props # type: ignore
  207. )
  208. @util.preload_module("sqlalchemy.orm.state", "sqlalchemy.orm.strategies")
  209. def _memoized_attr__deferred_column_loader(
  210. self,
  211. ) -> _InstallLoaderCallableProto[Any]:
  212. state = util.preloaded.orm_state
  213. strategies = util.preloaded.orm_strategies
  214. return state.InstanceState._instance_level_callable_processor(
  215. self.parent.class_manager,
  216. strategies.LoadDeferredColumns(self.key),
  217. self.key,
  218. )
  219. @util.preload_module("sqlalchemy.orm.state", "sqlalchemy.orm.strategies")
  220. def _memoized_attr__raise_column_loader(
  221. self,
  222. ) -> _InstallLoaderCallableProto[Any]:
  223. state = util.preloaded.orm_state
  224. strategies = util.preloaded.orm_strategies
  225. return state.InstanceState._instance_level_callable_processor(
  226. self.parent.class_manager,
  227. strategies.LoadDeferredColumns(self.key, True),
  228. self.key,
  229. )
  230. def __clause_element__(self) -> roles.ColumnsClauseRole:
  231. """Allow the ColumnProperty to work in expression before it is turned
  232. into an instrumented attribute.
  233. """
  234. return self.expression
  235. @property
  236. def expression(self) -> roles.ColumnsClauseRole:
  237. """Return the primary column or expression for this ColumnProperty.
  238. E.g.::
  239. class File(Base):
  240. # ...
  241. name = Column(String(64))
  242. extension = Column(String(8))
  243. filename = column_property(name + '.' + extension)
  244. path = column_property('C:/' + filename.expression)
  245. .. seealso::
  246. :ref:`mapper_column_property_sql_expressions_composed`
  247. """
  248. return self.columns[0]
  249. def instrument_class(self, mapper: Mapper[Any]) -> None:
  250. if not self.instrument:
  251. return
  252. attributes.register_descriptor(
  253. mapper.class_,
  254. self.key,
  255. comparator=self.comparator_factory(self, mapper),
  256. parententity=mapper,
  257. doc=self.doc,
  258. )
  259. def do_init(self) -> None:
  260. super().do_init()
  261. if len(self.columns) > 1 and set(self.parent.primary_key).issuperset(
  262. self.columns
  263. ):
  264. util.warn(
  265. (
  266. "On mapper %s, primary key column '%s' is being combined "
  267. "with distinct primary key column '%s' in attribute '%s'. "
  268. "Use explicit properties to give each column its own "
  269. "mapped attribute name."
  270. )
  271. % (self.parent, self.columns[1], self.columns[0], self.key)
  272. )
  273. def copy(self) -> ColumnProperty[_T]:
  274. return ColumnProperty(
  275. *self.columns,
  276. deferred=self.deferred,
  277. group=self.group,
  278. active_history=self.active_history,
  279. )
  280. def merge(
  281. self,
  282. session: Session,
  283. source_state: InstanceState[Any],
  284. source_dict: _InstanceDict,
  285. dest_state: InstanceState[Any],
  286. dest_dict: _InstanceDict,
  287. load: bool,
  288. _recursive: Dict[Any, object],
  289. _resolve_conflict_map: Dict[_IdentityKeyType[Any], object],
  290. ) -> None:
  291. if not self.instrument:
  292. return
  293. elif self.key in source_dict:
  294. value = source_dict[self.key]
  295. if not load:
  296. dest_dict[self.key] = value
  297. else:
  298. impl = dest_state.get_impl(self.key)
  299. impl.set(dest_state, dest_dict, value, None)
  300. elif dest_state.has_identity and self.key not in dest_dict:
  301. dest_state._expire_attributes(
  302. dest_dict, [self.key], no_loader=True
  303. )
  304. class Comparator(util.MemoizedSlots, PropComparator[_PT]):
  305. """Produce boolean, comparison, and other operators for
  306. :class:`.ColumnProperty` attributes.
  307. See the documentation for :class:`.PropComparator` for a brief
  308. overview.
  309. .. seealso::
  310. :class:`.PropComparator`
  311. :class:`.ColumnOperators`
  312. :ref:`types_operators`
  313. :attr:`.TypeEngine.comparator_factory`
  314. """
  315. if not TYPE_CHECKING:
  316. # prevent pylance from being clever about slots
  317. __slots__ = "__clause_element__", "info", "expressions"
  318. prop: RODescriptorReference[ColumnProperty[_PT]]
  319. expressions: Sequence[NamedColumn[Any]]
  320. """The full sequence of columns referenced by this
  321. attribute, adjusted for any aliasing in progress.
  322. .. versionadded:: 1.3.17
  323. .. seealso::
  324. :ref:`maptojoin` - usage example
  325. """
  326. def _orm_annotate_column(self, column: _NC) -> _NC:
  327. """annotate and possibly adapt a column to be returned
  328. as the mapped-attribute exposed version of the column.
  329. The column in this context needs to act as much like the
  330. column in an ORM mapped context as possible, so includes
  331. annotations to give hints to various ORM functions as to
  332. the source entity of this column. It also adapts it
  333. to the mapper's with_polymorphic selectable if one is
  334. present.
  335. """
  336. pe = self._parententity
  337. annotations: Dict[str, Any] = {
  338. "entity_namespace": pe,
  339. "parententity": pe,
  340. "parentmapper": pe,
  341. "proxy_key": self.prop.key,
  342. }
  343. col = column
  344. # for a mapper with polymorphic_on and an adapter, return
  345. # the column against the polymorphic selectable.
  346. # see also orm.util._orm_downgrade_polymorphic_columns
  347. # for the reverse operation.
  348. if self._parentmapper._polymorphic_adapter:
  349. mapper_local_col = col
  350. col = self._parentmapper._polymorphic_adapter.traverse(col)
  351. # this is a clue to the ORM Query etc. that this column
  352. # was adapted to the mapper's polymorphic_adapter. the
  353. # ORM uses this hint to know which column its adapting.
  354. annotations["adapt_column"] = mapper_local_col
  355. return col._annotate(annotations)._set_propagate_attrs(
  356. {"compile_state_plugin": "orm", "plugin_subject": pe}
  357. )
  358. if TYPE_CHECKING:
  359. def __clause_element__(self) -> NamedColumn[_PT]:
  360. ...
  361. def _memoized_method___clause_element__(
  362. self,
  363. ) -> NamedColumn[_PT]:
  364. if self.adapter:
  365. return self.adapter(self.prop.columns[0], self.prop.key)
  366. else:
  367. return self._orm_annotate_column(self.prop.columns[0])
  368. def _memoized_attr_info(self) -> _InfoType:
  369. """The .info dictionary for this attribute."""
  370. ce = self.__clause_element__()
  371. try:
  372. return ce.info # type: ignore
  373. except AttributeError:
  374. return self.prop.info
  375. def _memoized_attr_expressions(self) -> Sequence[NamedColumn[Any]]:
  376. """The full sequence of columns referenced by this
  377. attribute, adjusted for any aliasing in progress.
  378. .. versionadded:: 1.3.17
  379. """
  380. if self.adapter:
  381. return [
  382. self.adapter(col, self.prop.key)
  383. for col in self.prop.columns
  384. ]
  385. else:
  386. return [
  387. self._orm_annotate_column(col) for col in self.prop.columns
  388. ]
  389. def _fallback_getattr(self, key: str) -> Any:
  390. """proxy attribute access down to the mapped column.
  391. this allows user-defined comparison methods to be accessed.
  392. """
  393. return getattr(self.__clause_element__(), key)
  394. def operate(
  395. self, op: OperatorType, *other: Any, **kwargs: Any
  396. ) -> ColumnElement[Any]:
  397. return op(self.__clause_element__(), *other, **kwargs) # type: ignore[no-any-return] # noqa: E501
  398. def reverse_operate(
  399. self, op: OperatorType, other: Any, **kwargs: Any
  400. ) -> ColumnElement[Any]:
  401. col = self.__clause_element__()
  402. return op(col._bind_param(op, other), col, **kwargs) # type: ignore[no-any-return] # noqa: E501
  403. def __str__(self) -> str:
  404. if not self.parent or not self.key:
  405. return object.__repr__(self)
  406. return str(self.parent.class_.__name__) + "." + self.key
  407. class MappedSQLExpression(ColumnProperty[_T], _DeclarativeMapped[_T]):
  408. """Declarative front-end for the :class:`.ColumnProperty` class.
  409. Public constructor is the :func:`_orm.column_property` function.
  410. .. versionchanged:: 2.0 Added :class:`_orm.MappedSQLExpression` as
  411. a Declarative compatible subclass for :class:`_orm.ColumnProperty`.
  412. .. seealso::
  413. :class:`.MappedColumn`
  414. """
  415. inherit_cache = True
  416. """:meta private:"""
  417. class MappedColumn(
  418. _IntrospectsAnnotations,
  419. _MapsColumns[_T],
  420. _DeclarativeMapped[_T],
  421. ):
  422. """Maps a single :class:`_schema.Column` on a class.
  423. :class:`_orm.MappedColumn` is a specialization of the
  424. :class:`_orm.ColumnProperty` class and is oriented towards declarative
  425. configuration.
  426. To construct :class:`_orm.MappedColumn` objects, use the
  427. :func:`_orm.mapped_column` constructor function.
  428. .. versionadded:: 2.0
  429. """
  430. __slots__ = (
  431. "column",
  432. "_creation_order",
  433. "_sort_order",
  434. "foreign_keys",
  435. "_has_nullable",
  436. "_has_insert_default",
  437. "deferred",
  438. "deferred_group",
  439. "deferred_raiseload",
  440. "active_history",
  441. "_attribute_options",
  442. "_has_dataclass_arguments",
  443. "_use_existing_column",
  444. )
  445. deferred: Union[_NoArg, bool]
  446. deferred_raiseload: bool
  447. deferred_group: Optional[str]
  448. column: Column[_T]
  449. foreign_keys: Optional[Set[ForeignKey]]
  450. _attribute_options: _AttributeOptions
  451. def __init__(self, *arg: Any, **kw: Any):
  452. self._attribute_options = attr_opts = kw.pop(
  453. "attribute_options", _DEFAULT_ATTRIBUTE_OPTIONS
  454. )
  455. self._use_existing_column = kw.pop("use_existing_column", False)
  456. self._has_dataclass_arguments = (
  457. attr_opts is not None
  458. and attr_opts != _DEFAULT_ATTRIBUTE_OPTIONS
  459. and any(
  460. attr_opts[i] is not _NoArg.NO_ARG
  461. for i, attr in enumerate(attr_opts._fields)
  462. if attr != "dataclasses_default"
  463. )
  464. )
  465. insert_default = kw.pop("insert_default", _NoArg.NO_ARG)
  466. self._has_insert_default = insert_default is not _NoArg.NO_ARG
  467. if self._has_insert_default:
  468. kw["default"] = insert_default
  469. elif attr_opts.dataclasses_default is not _NoArg.NO_ARG:
  470. kw["default"] = attr_opts.dataclasses_default
  471. self.deferred_group = kw.pop("deferred_group", None)
  472. self.deferred_raiseload = kw.pop("deferred_raiseload", None)
  473. self.deferred = kw.pop("deferred", _NoArg.NO_ARG)
  474. self.active_history = kw.pop("active_history", False)
  475. self._sort_order = kw.pop("sort_order", _NoArg.NO_ARG)
  476. self.column = cast("Column[_T]", Column(*arg, **kw))
  477. self.foreign_keys = self.column.foreign_keys
  478. self._has_nullable = "nullable" in kw and kw.get("nullable") not in (
  479. None,
  480. SchemaConst.NULL_UNSPECIFIED,
  481. )
  482. util.set_creation_order(self)
  483. def _copy(self, **kw: Any) -> Self:
  484. new = self.__class__.__new__(self.__class__)
  485. new.column = self.column._copy(**kw)
  486. new.deferred = self.deferred
  487. new.deferred_group = self.deferred_group
  488. new.deferred_raiseload = self.deferred_raiseload
  489. new.foreign_keys = new.column.foreign_keys
  490. new.active_history = self.active_history
  491. new._has_nullable = self._has_nullable
  492. new._attribute_options = self._attribute_options
  493. new._has_insert_default = self._has_insert_default
  494. new._has_dataclass_arguments = self._has_dataclass_arguments
  495. new._use_existing_column = self._use_existing_column
  496. new._sort_order = self._sort_order
  497. util.set_creation_order(new)
  498. return new
  499. @property
  500. def name(self) -> str:
  501. return self.column.name
  502. @property
  503. def mapper_property_to_assign(self) -> Optional[MapperProperty[_T]]:
  504. effective_deferred = self.deferred
  505. if effective_deferred is _NoArg.NO_ARG:
  506. effective_deferred = bool(
  507. self.deferred_group or self.deferred_raiseload
  508. )
  509. if effective_deferred or self.active_history:
  510. return ColumnProperty(
  511. self.column,
  512. deferred=effective_deferred,
  513. group=self.deferred_group,
  514. raiseload=self.deferred_raiseload,
  515. attribute_options=self._attribute_options,
  516. active_history=self.active_history,
  517. )
  518. else:
  519. return None
  520. @property
  521. def columns_to_assign(self) -> List[Tuple[Column[Any], int]]:
  522. return [
  523. (
  524. self.column,
  525. self._sort_order
  526. if self._sort_order is not _NoArg.NO_ARG
  527. else 0,
  528. )
  529. ]
  530. def __clause_element__(self) -> Column[_T]:
  531. return self.column
  532. def operate(
  533. self, op: OperatorType, *other: Any, **kwargs: Any
  534. ) -> ColumnElement[Any]:
  535. return op(self.__clause_element__(), *other, **kwargs) # type: ignore[no-any-return] # noqa: E501
  536. def reverse_operate(
  537. self, op: OperatorType, other: Any, **kwargs: Any
  538. ) -> ColumnElement[Any]:
  539. col = self.__clause_element__()
  540. return op(col._bind_param(op, other), col, **kwargs) # type: ignore[no-any-return] # noqa: E501
  541. def found_in_pep593_annotated(self) -> Any:
  542. # return a blank mapped_column(). This mapped_column()'s
  543. # Column will be merged into it in _init_column_for_annotation().
  544. return MappedColumn()
  545. def declarative_scan(
  546. self,
  547. decl_scan: _ClassScanMapperConfig,
  548. registry: _RegistryType,
  549. cls: Type[Any],
  550. originating_module: Optional[str],
  551. key: str,
  552. mapped_container: Optional[Type[Mapped[Any]]],
  553. annotation: Optional[_AnnotationScanType],
  554. extracted_mapped_annotation: Optional[_AnnotationScanType],
  555. is_dataclass_field: bool,
  556. ) -> None:
  557. column = self.column
  558. if (
  559. self._use_existing_column
  560. and decl_scan.inherits
  561. and decl_scan.single
  562. ):
  563. if decl_scan.is_deferred:
  564. raise sa_exc.ArgumentError(
  565. "Can't use use_existing_column with deferred mappers"
  566. )
  567. supercls_mapper = class_mapper(decl_scan.inherits, False)
  568. colname = column.name if column.name is not None else key
  569. column = self.column = supercls_mapper.local_table.c.get( # type: ignore # noqa: E501
  570. colname, column
  571. )
  572. if column.key is None:
  573. column.key = key
  574. if column.name is None:
  575. column.name = key
  576. sqltype = column.type
  577. if extracted_mapped_annotation is None:
  578. if sqltype._isnull and not self.column.foreign_keys:
  579. self._raise_for_required(key, cls)
  580. else:
  581. return
  582. self._init_column_for_annotation(
  583. cls,
  584. registry,
  585. extracted_mapped_annotation,
  586. originating_module,
  587. )
  588. @util.preload_module("sqlalchemy.orm.decl_base")
  589. def declarative_scan_for_composite(
  590. self,
  591. registry: _RegistryType,
  592. cls: Type[Any],
  593. originating_module: Optional[str],
  594. key: str,
  595. param_name: str,
  596. param_annotation: _AnnotationScanType,
  597. ) -> None:
  598. decl_base = util.preloaded.orm_decl_base
  599. decl_base._undefer_column_name(param_name, self.column)
  600. self._init_column_for_annotation(
  601. cls, registry, param_annotation, originating_module
  602. )
  603. def _init_column_for_annotation(
  604. self,
  605. cls: Type[Any],
  606. registry: _RegistryType,
  607. argument: _AnnotationScanType,
  608. originating_module: Optional[str],
  609. ) -> None:
  610. sqltype = self.column.type
  611. if isinstance(argument, str) or is_fwd_ref(
  612. argument, check_generic=True
  613. ):
  614. assert originating_module is not None
  615. argument = de_stringify_annotation(
  616. cls, argument, originating_module, include_generic=True
  617. )
  618. if is_union(argument):
  619. assert originating_module is not None
  620. argument = de_stringify_union_elements(
  621. cls, argument, originating_module
  622. )
  623. nullable = is_optional_union(argument)
  624. if not self._has_nullable:
  625. self.column.nullable = nullable
  626. our_type = de_optionalize_union_types(argument)
  627. use_args_from = None
  628. if is_pep593(our_type):
  629. our_type_is_pep593 = True
  630. pep_593_components = typing_get_args(our_type)
  631. raw_pep_593_type = pep_593_components[0]
  632. if is_optional_union(raw_pep_593_type):
  633. raw_pep_593_type = de_optionalize_union_types(raw_pep_593_type)
  634. nullable = True
  635. if not self._has_nullable:
  636. self.column.nullable = nullable
  637. for elem in pep_593_components[1:]:
  638. if isinstance(elem, MappedColumn):
  639. use_args_from = elem
  640. break
  641. else:
  642. our_type_is_pep593 = False
  643. raw_pep_593_type = None
  644. if use_args_from is not None:
  645. if (
  646. not self._has_insert_default
  647. and use_args_from.column.default is not None
  648. ):
  649. self.column.default = None
  650. use_args_from.column._merge(self.column)
  651. sqltype = self.column.type
  652. if (
  653. use_args_from.deferred is not _NoArg.NO_ARG
  654. and self.deferred is _NoArg.NO_ARG
  655. ):
  656. self.deferred = use_args_from.deferred
  657. if (
  658. use_args_from.deferred_group is not None
  659. and self.deferred_group is None
  660. ):
  661. self.deferred_group = use_args_from.deferred_group
  662. if (
  663. use_args_from.deferred_raiseload is not None
  664. and self.deferred_raiseload is None
  665. ):
  666. self.deferred_raiseload = use_args_from.deferred_raiseload
  667. if (
  668. use_args_from._use_existing_column
  669. and not self._use_existing_column
  670. ):
  671. self._use_existing_column = True
  672. if use_args_from.active_history:
  673. self.active_history = use_args_from.active_history
  674. if (
  675. use_args_from._sort_order is not None
  676. and self._sort_order is _NoArg.NO_ARG
  677. ):
  678. self._sort_order = use_args_from._sort_order
  679. if (
  680. use_args_from.column.key is not None
  681. or use_args_from.column.name is not None
  682. ):
  683. util.warn_deprecated(
  684. "Can't use the 'key' or 'name' arguments in "
  685. "Annotated with mapped_column(); this will be ignored",
  686. "2.0.22",
  687. )
  688. if use_args_from._has_dataclass_arguments:
  689. for idx, arg in enumerate(
  690. use_args_from._attribute_options._fields
  691. ):
  692. if (
  693. use_args_from._attribute_options[idx]
  694. is not _NoArg.NO_ARG
  695. ):
  696. arg = arg.replace("dataclasses_", "")
  697. util.warn_deprecated(
  698. f"Argument '{arg}' is a dataclass argument and "
  699. "cannot be specified within a mapped_column() "
  700. "bundled inside of an Annotated object",
  701. "2.0.22",
  702. )
  703. if sqltype._isnull and not self.column.foreign_keys:
  704. new_sqltype = None
  705. if our_type_is_pep593:
  706. checks = [our_type, raw_pep_593_type]
  707. else:
  708. checks = [our_type]
  709. for check_type in checks:
  710. new_sqltype = registry._resolve_type(check_type)
  711. if new_sqltype is not None:
  712. break
  713. else:
  714. if isinstance(our_type, TypeEngine) or (
  715. isinstance(our_type, type)
  716. and issubclass(our_type, TypeEngine)
  717. ):
  718. raise sa_exc.ArgumentError(
  719. f"The type provided inside the {self.column.key!r} "
  720. "attribute Mapped annotation is the SQLAlchemy type "
  721. f"{our_type}. Expected a Python type instead"
  722. )
  723. else:
  724. raise sa_exc.ArgumentError(
  725. "Could not locate SQLAlchemy Core type for Python "
  726. f"type {our_type} inside the {self.column.key!r} "
  727. "attribute Mapped annotation"
  728. )
  729. self.column._set_type(new_sqltype)