properties.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907
  1. # orm/properties.py
  2. # Copyright (C) 2005-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. """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 exc as orm_exc
  26. from . import strategy_options
  27. from .base import _DeclarativeMapped
  28. from .base import class_mapper
  29. from .descriptor_props import CompositeProperty
  30. from .descriptor_props import ConcreteInheritedProperty
  31. from .descriptor_props import SynonymProperty
  32. from .interfaces import _AttributeOptions
  33. from .interfaces import _DEFAULT_ATTRIBUTE_OPTIONS
  34. from .interfaces import _IntrospectsAnnotations
  35. from .interfaces import _MapsColumns
  36. from .interfaces import MapperProperty
  37. from .interfaces import PropComparator
  38. from .interfaces import StrategizedProperty
  39. from .relationships import RelationshipProperty
  40. from .util import de_stringify_annotation
  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 get_args
  53. from ..util.typing import includes_none
  54. from ..util.typing import is_a_type
  55. from ..util.typing import is_fwd_ref
  56. from ..util.typing import is_pep593
  57. from ..util.typing import is_pep695
  58. from ..util.typing import Self
  59. if TYPE_CHECKING:
  60. from ._typing import _IdentityKeyType
  61. from ._typing import _InstanceDict
  62. from ._typing import _ORMColumnExprArgument
  63. from ._typing import _RegistryType
  64. from .base import Mapped
  65. from .decl_base import _ClassScanMapperConfig
  66. from .mapper import Mapper
  67. from .session import Session
  68. from .state import _InstallLoaderCallableProto
  69. from .state import InstanceState
  70. from ..sql._typing import _InfoType
  71. from ..sql.elements import ColumnElement
  72. from ..sql.elements import NamedColumn
  73. from ..sql.operators import OperatorType
  74. from ..util.typing import _AnnotationScanType
  75. from ..util.typing import RODescriptorReference
  76. _T = TypeVar("_T", bound=Any)
  77. _PT = TypeVar("_PT", bound=Any)
  78. _NC = TypeVar("_NC", bound="NamedColumn[Any]")
  79. __all__ = [
  80. "ColumnProperty",
  81. "CompositeProperty",
  82. "ConcreteInheritedProperty",
  83. "RelationshipProperty",
  84. "SynonymProperty",
  85. ]
  86. @log.class_logger
  87. class ColumnProperty(
  88. _MapsColumns[_T],
  89. StrategizedProperty[_T],
  90. _IntrospectsAnnotations,
  91. log.Identified,
  92. ):
  93. """Describes an object attribute that corresponds to a table column
  94. or other column expression.
  95. Public constructor is the :func:`_orm.column_property` function.
  96. """
  97. strategy_wildcard_key = strategy_options._COLUMN_TOKEN
  98. inherit_cache = True
  99. """:meta private:"""
  100. _links_to_entity = False
  101. columns: List[NamedColumn[Any]]
  102. _is_polymorphic_discriminator: bool
  103. _mapped_by_synonym: Optional[str]
  104. comparator_factory: Type[PropComparator[_T]]
  105. __slots__ = (
  106. "columns",
  107. "group",
  108. "deferred",
  109. "instrument",
  110. "comparator_factory",
  111. "active_history",
  112. "expire_on_flush",
  113. "_creation_order",
  114. "_is_polymorphic_discriminator",
  115. "_mapped_by_synonym",
  116. "_deferred_column_loader",
  117. "_raise_column_loader",
  118. "_renders_in_subqueries",
  119. "raiseload",
  120. )
  121. def __init__(
  122. self,
  123. column: _ORMColumnExprArgument[_T],
  124. *additional_columns: _ORMColumnExprArgument[Any],
  125. attribute_options: Optional[_AttributeOptions] = None,
  126. group: Optional[str] = None,
  127. deferred: bool = False,
  128. raiseload: bool = False,
  129. comparator_factory: Optional[Type[PropComparator[_T]]] = None,
  130. active_history: bool = False,
  131. expire_on_flush: bool = True,
  132. info: Optional[_InfoType] = None,
  133. doc: Optional[str] = None,
  134. _instrument: bool = True,
  135. _assume_readonly_dc_attributes: bool = False,
  136. ):
  137. super().__init__(
  138. attribute_options=attribute_options,
  139. _assume_readonly_dc_attributes=_assume_readonly_dc_attributes,
  140. )
  141. columns = (column,) + additional_columns
  142. self.columns = [
  143. coercions.expect(roles.LabeledColumnExprRole, c) for c in columns
  144. ]
  145. self.group = group
  146. self.deferred = deferred
  147. self.raiseload = raiseload
  148. self.instrument = _instrument
  149. self.comparator_factory = (
  150. comparator_factory
  151. if comparator_factory is not None
  152. else self.__class__.Comparator
  153. )
  154. self.active_history = active_history
  155. self.expire_on_flush = expire_on_flush
  156. if info is not None:
  157. self.info.update(info)
  158. if doc is not None:
  159. self.doc = doc
  160. else:
  161. for col in reversed(self.columns):
  162. doc = getattr(col, "doc", None)
  163. if doc is not None:
  164. self.doc = doc
  165. break
  166. else:
  167. self.doc = None
  168. util.set_creation_order(self)
  169. self.strategy_key = (
  170. ("deferred", self.deferred),
  171. ("instrument", self.instrument),
  172. )
  173. if self.raiseload:
  174. self.strategy_key += (("raiseload", True),)
  175. def declarative_scan(
  176. self,
  177. decl_scan: _ClassScanMapperConfig,
  178. registry: _RegistryType,
  179. cls: Type[Any],
  180. originating_module: Optional[str],
  181. key: str,
  182. mapped_container: Optional[Type[Mapped[Any]]],
  183. annotation: Optional[_AnnotationScanType],
  184. extracted_mapped_annotation: Optional[_AnnotationScanType],
  185. is_dataclass_field: bool,
  186. ) -> None:
  187. column = self.columns[0]
  188. if column.key is None:
  189. column.key = key
  190. if column.name is None:
  191. column.name = key
  192. @property
  193. def mapper_property_to_assign(self) -> Optional[MapperProperty[_T]]:
  194. return self
  195. @property
  196. def columns_to_assign(self) -> List[Tuple[Column[Any], int]]:
  197. # mypy doesn't care about the isinstance here
  198. return [
  199. (c, 0) # type: ignore
  200. for c in self.columns
  201. if isinstance(c, Column) and c.table is None
  202. ]
  203. def _memoized_attr__renders_in_subqueries(self) -> bool:
  204. if ("query_expression", True) in self.strategy_key:
  205. return self.strategy._have_default_expression # type: ignore
  206. return ("deferred", True) not in self.strategy_key or (
  207. self not in self.parent._readonly_props
  208. )
  209. @util.preload_module("sqlalchemy.orm.state", "sqlalchemy.orm.strategies")
  210. def _memoized_attr__deferred_column_loader(
  211. self,
  212. ) -> _InstallLoaderCallableProto[Any]:
  213. state = util.preloaded.orm_state
  214. strategies = util.preloaded.orm_strategies
  215. return state.InstanceState._instance_level_callable_processor(
  216. self.parent.class_manager,
  217. strategies.LoadDeferredColumns(self.key),
  218. self.key,
  219. )
  220. @util.preload_module("sqlalchemy.orm.state", "sqlalchemy.orm.strategies")
  221. def _memoized_attr__raise_column_loader(
  222. self,
  223. ) -> _InstallLoaderCallableProto[Any]:
  224. state = util.preloaded.orm_state
  225. strategies = util.preloaded.orm_strategies
  226. return state.InstanceState._instance_level_callable_processor(
  227. self.parent.class_manager,
  228. strategies.LoadDeferredColumns(self.key, True),
  229. self.key,
  230. )
  231. def __clause_element__(self) -> roles.ColumnsClauseRole:
  232. """Allow the ColumnProperty to work in expression before it is turned
  233. into an instrumented attribute.
  234. """
  235. return self.expression
  236. @property
  237. def expression(self) -> roles.ColumnsClauseRole:
  238. """Return the primary column or expression for this ColumnProperty.
  239. E.g.::
  240. class File(Base):
  241. # ...
  242. name = Column(String(64))
  243. extension = Column(String(8))
  244. filename = column_property(name + "." + extension)
  245. path = column_property("C:/" + filename.expression)
  246. .. seealso::
  247. :ref:`mapper_column_property_sql_expressions_composed`
  248. """
  249. return self.columns[0]
  250. def instrument_class(self, mapper: Mapper[Any]) -> None:
  251. if not self.instrument:
  252. return
  253. attributes.register_descriptor(
  254. mapper.class_,
  255. self.key,
  256. comparator=self.comparator_factory(self, mapper),
  257. parententity=mapper,
  258. doc=self.doc,
  259. )
  260. def do_init(self) -> None:
  261. super().do_init()
  262. if len(self.columns) > 1 and set(self.parent.primary_key).issuperset(
  263. self.columns
  264. ):
  265. util.warn(
  266. (
  267. "On mapper %s, primary key column '%s' is being combined "
  268. "with distinct primary key column '%s' in attribute '%s'. "
  269. "Use explicit properties to give each column its own "
  270. "mapped attribute name."
  271. )
  272. % (self.parent, self.columns[1], self.columns[0], self.key)
  273. )
  274. def copy(self) -> ColumnProperty[_T]:
  275. return ColumnProperty(
  276. *self.columns,
  277. deferred=self.deferred,
  278. group=self.group,
  279. active_history=self.active_history,
  280. )
  281. def merge(
  282. self,
  283. session: Session,
  284. source_state: InstanceState[Any],
  285. source_dict: _InstanceDict,
  286. dest_state: InstanceState[Any],
  287. dest_dict: _InstanceDict,
  288. load: bool,
  289. _recursive: Dict[Any, object],
  290. _resolve_conflict_map: Dict[_IdentityKeyType[Any], object],
  291. ) -> None:
  292. if not self.instrument:
  293. return
  294. elif self.key in source_dict:
  295. value = source_dict[self.key]
  296. if not load:
  297. dest_dict[self.key] = value
  298. else:
  299. impl = dest_state.get_impl(self.key)
  300. impl.set(dest_state, dest_dict, value, None)
  301. elif dest_state.has_identity and self.key not in dest_dict:
  302. dest_state._expire_attributes(
  303. dest_dict, [self.key], no_loader=True
  304. )
  305. class Comparator(util.MemoizedSlots, PropComparator[_PT]):
  306. """Produce boolean, comparison, and other operators for
  307. :class:`.ColumnProperty` attributes.
  308. See the documentation for :class:`.PropComparator` for a brief
  309. overview.
  310. .. seealso::
  311. :class:`.PropComparator`
  312. :class:`.ColumnOperators`
  313. :ref:`types_operators`
  314. :attr:`.TypeEngine.comparator_factory`
  315. """
  316. if not TYPE_CHECKING:
  317. # prevent pylance from being clever about slots
  318. __slots__ = "__clause_element__", "info", "expressions"
  319. prop: RODescriptorReference[ColumnProperty[_PT]]
  320. expressions: Sequence[NamedColumn[Any]]
  321. """The full sequence of columns referenced by this
  322. attribute, adjusted for any aliasing in progress.
  323. .. versionadded:: 1.3.17
  324. .. seealso::
  325. :ref:`maptojoin` - usage example
  326. """
  327. def _orm_annotate_column(self, column: _NC) -> _NC:
  328. """annotate and possibly adapt a column to be returned
  329. as the mapped-attribute exposed version of the column.
  330. The column in this context needs to act as much like the
  331. column in an ORM mapped context as possible, so includes
  332. annotations to give hints to various ORM functions as to
  333. the source entity of this column. It also adapts it
  334. to the mapper's with_polymorphic selectable if one is
  335. present.
  336. """
  337. pe = self._parententity
  338. annotations: Dict[str, Any] = {
  339. "entity_namespace": pe,
  340. "parententity": pe,
  341. "parentmapper": pe,
  342. "proxy_key": self.prop.key,
  343. }
  344. col = column
  345. # for a mapper with polymorphic_on and an adapter, return
  346. # the column against the polymorphic selectable.
  347. # see also orm.util._orm_downgrade_polymorphic_columns
  348. # for the reverse operation.
  349. if self._parentmapper._polymorphic_adapter:
  350. mapper_local_col = col
  351. col = self._parentmapper._polymorphic_adapter.traverse(col)
  352. # this is a clue to the ORM Query etc. that this column
  353. # was adapted to the mapper's polymorphic_adapter. the
  354. # ORM uses this hint to know which column its adapting.
  355. annotations["adapt_column"] = mapper_local_col
  356. return col._annotate(annotations)._set_propagate_attrs(
  357. {"compile_state_plugin": "orm", "plugin_subject": pe}
  358. )
  359. if TYPE_CHECKING:
  360. def __clause_element__(self) -> NamedColumn[_PT]: ...
  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. (
  526. self._sort_order
  527. if self._sort_order is not _NoArg.NO_ARG
  528. else 0
  529. ),
  530. )
  531. ]
  532. def __clause_element__(self) -> Column[_T]:
  533. return self.column
  534. def operate(
  535. self, op: OperatorType, *other: Any, **kwargs: Any
  536. ) -> ColumnElement[Any]:
  537. return op(self.__clause_element__(), *other, **kwargs) # type: ignore[no-any-return] # noqa: E501
  538. def reverse_operate(
  539. self, op: OperatorType, other: Any, **kwargs: Any
  540. ) -> ColumnElement[Any]:
  541. col = self.__clause_element__()
  542. return op(col._bind_param(op, other), col, **kwargs) # type: ignore[no-any-return] # noqa: E501
  543. def found_in_pep593_annotated(self) -> Any:
  544. # return a blank mapped_column(). This mapped_column()'s
  545. # Column will be merged into it in _init_column_for_annotation().
  546. return MappedColumn()
  547. def _adjust_for_existing_column(
  548. self,
  549. decl_scan: _ClassScanMapperConfig,
  550. key: str,
  551. given_column: Column[_T],
  552. ) -> Column[_T]:
  553. if (
  554. self._use_existing_column
  555. and decl_scan.inherits
  556. and decl_scan.single
  557. ):
  558. if decl_scan.is_deferred:
  559. raise sa_exc.ArgumentError(
  560. "Can't use use_existing_column with deferred mappers"
  561. )
  562. supercls_mapper = class_mapper(decl_scan.inherits, False)
  563. colname = (
  564. given_column.name if given_column.name is not None else key
  565. )
  566. given_column = supercls_mapper.local_table.c.get( # type: ignore[assignment] # noqa: E501
  567. colname, given_column
  568. )
  569. return given_column
  570. def declarative_scan(
  571. self,
  572. decl_scan: _ClassScanMapperConfig,
  573. registry: _RegistryType,
  574. cls: Type[Any],
  575. originating_module: Optional[str],
  576. key: str,
  577. mapped_container: Optional[Type[Mapped[Any]]],
  578. annotation: Optional[_AnnotationScanType],
  579. extracted_mapped_annotation: Optional[_AnnotationScanType],
  580. is_dataclass_field: bool,
  581. ) -> None:
  582. column = self.column
  583. column = self.column = self._adjust_for_existing_column(
  584. decl_scan, key, self.column
  585. )
  586. if column.key is None:
  587. column.key = key
  588. if column.name is None:
  589. column.name = key
  590. sqltype = column.type
  591. if extracted_mapped_annotation is None:
  592. if sqltype._isnull and not self.column.foreign_keys:
  593. self._raise_for_required(key, cls)
  594. else:
  595. return
  596. self._init_column_for_annotation(
  597. cls,
  598. decl_scan,
  599. key,
  600. registry,
  601. extracted_mapped_annotation,
  602. originating_module,
  603. )
  604. @util.preload_module("sqlalchemy.orm.decl_base")
  605. def declarative_scan_for_composite(
  606. self,
  607. decl_scan: _ClassScanMapperConfig,
  608. registry: _RegistryType,
  609. cls: Type[Any],
  610. originating_module: Optional[str],
  611. key: str,
  612. param_name: str,
  613. param_annotation: _AnnotationScanType,
  614. ) -> None:
  615. decl_base = util.preloaded.orm_decl_base
  616. decl_base._undefer_column_name(param_name, self.column)
  617. self._init_column_for_annotation(
  618. cls, decl_scan, key, registry, param_annotation, originating_module
  619. )
  620. def _init_column_for_annotation(
  621. self,
  622. cls: Type[Any],
  623. decl_scan: _ClassScanMapperConfig,
  624. key: str,
  625. registry: _RegistryType,
  626. argument: _AnnotationScanType,
  627. originating_module: Optional[str],
  628. ) -> None:
  629. sqltype = self.column.type
  630. if is_fwd_ref(
  631. argument, check_generic=True, check_for_plain_string=True
  632. ):
  633. assert originating_module is not None
  634. argument = de_stringify_annotation(
  635. cls, argument, originating_module, include_generic=True
  636. )
  637. nullable = includes_none(argument)
  638. if not self._has_nullable:
  639. self.column.nullable = nullable
  640. our_type = de_optionalize_union_types(argument)
  641. find_mapped_in: Tuple[Any, ...] = ()
  642. our_type_is_pep593 = False
  643. raw_pep_593_type = None
  644. if is_pep593(our_type):
  645. our_type_is_pep593 = True
  646. pep_593_components = get_args(our_type)
  647. raw_pep_593_type = pep_593_components[0]
  648. if nullable:
  649. raw_pep_593_type = de_optionalize_union_types(raw_pep_593_type)
  650. find_mapped_in = pep_593_components[1:]
  651. elif is_pep695(argument) and is_pep593(argument.__value__):
  652. # do not support nested annotation inside unions ets
  653. find_mapped_in = get_args(argument.__value__)[1:]
  654. use_args_from: Optional[MappedColumn[Any]]
  655. for elem in find_mapped_in:
  656. if isinstance(elem, MappedColumn):
  657. use_args_from = elem
  658. break
  659. else:
  660. use_args_from = None
  661. if use_args_from is not None:
  662. self.column = use_args_from._adjust_for_existing_column(
  663. decl_scan, key, self.column
  664. )
  665. if (
  666. not self._has_insert_default
  667. and use_args_from.column.default is not None
  668. ):
  669. self.column.default = None
  670. use_args_from.column._merge(self.column)
  671. sqltype = self.column.type
  672. if (
  673. use_args_from.deferred is not _NoArg.NO_ARG
  674. and self.deferred is _NoArg.NO_ARG
  675. ):
  676. self.deferred = use_args_from.deferred
  677. if (
  678. use_args_from.deferred_group is not None
  679. and self.deferred_group is None
  680. ):
  681. self.deferred_group = use_args_from.deferred_group
  682. if (
  683. use_args_from.deferred_raiseload is not None
  684. and self.deferred_raiseload is None
  685. ):
  686. self.deferred_raiseload = use_args_from.deferred_raiseload
  687. if (
  688. use_args_from._use_existing_column
  689. and not self._use_existing_column
  690. ):
  691. self._use_existing_column = True
  692. if use_args_from.active_history:
  693. self.active_history = use_args_from.active_history
  694. if (
  695. use_args_from._sort_order is not None
  696. and self._sort_order is _NoArg.NO_ARG
  697. ):
  698. self._sort_order = use_args_from._sort_order
  699. if (
  700. use_args_from.column.key is not None
  701. or use_args_from.column.name is not None
  702. ):
  703. util.warn_deprecated(
  704. "Can't use the 'key' or 'name' arguments in "
  705. "Annotated with mapped_column(); this will be ignored",
  706. "2.0.22",
  707. )
  708. if use_args_from._has_dataclass_arguments:
  709. for idx, arg in enumerate(
  710. use_args_from._attribute_options._fields
  711. ):
  712. if (
  713. use_args_from._attribute_options[idx]
  714. is not _NoArg.NO_ARG
  715. ):
  716. arg = arg.replace("dataclasses_", "")
  717. util.warn_deprecated(
  718. f"Argument '{arg}' is a dataclass argument and "
  719. "cannot be specified within a mapped_column() "
  720. "bundled inside of an Annotated object",
  721. "2.0.22",
  722. )
  723. if sqltype._isnull and not self.column.foreign_keys:
  724. checks: List[Any]
  725. if our_type_is_pep593:
  726. checks = [our_type, raw_pep_593_type]
  727. else:
  728. checks = [our_type]
  729. for check_type in checks:
  730. new_sqltype = registry._resolve_type(check_type)
  731. if new_sqltype is not None:
  732. break
  733. else:
  734. if isinstance(our_type, TypeEngine) or (
  735. isinstance(our_type, type)
  736. and issubclass(our_type, TypeEngine)
  737. ):
  738. raise orm_exc.MappedAnnotationError(
  739. f"The type provided inside the {self.column.key!r} "
  740. "attribute Mapped annotation is the SQLAlchemy type "
  741. f"{our_type}. Expected a Python type instead"
  742. )
  743. elif is_a_type(our_type):
  744. raise orm_exc.MappedAnnotationError(
  745. "Could not locate SQLAlchemy Core type for Python "
  746. f"type {our_type} inside the {self.column.key!r} "
  747. "attribute Mapped annotation"
  748. )
  749. else:
  750. raise orm_exc.MappedAnnotationError(
  751. f"The object provided inside the {self.column.key!r} "
  752. "attribute Mapped annotation is not a Python type, "
  753. f"it's the object {our_type!r}. Expected a Python "
  754. "type."
  755. )
  756. self.column._set_type(new_sqltype)