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