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