| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092 |
- # orm/descriptor_props.py
- # Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
- # <see AUTHORS file>
- #
- # This module is part of SQLAlchemy and is released under
- # the MIT License: https://www.opensource.org/licenses/mit-license.php
- """Descriptor properties are more "auxiliary" properties
- that exist as configurational elements, but don't participate
- as actively in the load/persist ORM loop.
- """
- from __future__ import annotations
- from dataclasses import is_dataclass
- import inspect
- import itertools
- import operator
- import typing
- from typing import Any
- from typing import Callable
- from typing import Dict
- from typing import List
- from typing import NoReturn
- from typing import Optional
- from typing import Sequence
- from typing import Tuple
- from typing import Type
- from typing import TYPE_CHECKING
- from typing import TypeVar
- from typing import Union
- import weakref
- from . import attributes
- from . import util as orm_util
- from .base import _DeclarativeMapped
- from .base import LoaderCallableStatus
- from .base import Mapped
- from .base import PassiveFlag
- from .base import SQLORMOperations
- from .interfaces import _AttributeOptions
- from .interfaces import _IntrospectsAnnotations
- from .interfaces import _MapsColumns
- from .interfaces import MapperProperty
- from .interfaces import PropComparator
- from .util import _none_set
- from .util import de_stringify_annotation
- from .. import event
- from .. import exc as sa_exc
- from .. import schema
- from .. import sql
- from .. import util
- from ..sql import expression
- from ..sql import operators
- from ..sql.elements import BindParameter
- from ..util.typing import get_args
- from ..util.typing import is_fwd_ref
- from ..util.typing import is_pep593
- if typing.TYPE_CHECKING:
- from ._typing import _InstanceDict
- from ._typing import _RegistryType
- from .attributes import History
- from .attributes import InstrumentedAttribute
- from .attributes import QueryableAttribute
- from .context import ORMCompileState
- from .decl_base import _ClassScanMapperConfig
- from .mapper import Mapper
- from .properties import ColumnProperty
- from .properties import MappedColumn
- from .state import InstanceState
- from ..engine.base import Connection
- from ..engine.row import Row
- from ..sql._typing import _DMLColumnArgument
- from ..sql._typing import _InfoType
- from ..sql.elements import ClauseList
- from ..sql.elements import ColumnElement
- from ..sql.operators import OperatorType
- from ..sql.schema import Column
- from ..sql.selectable import Select
- from ..util.typing import _AnnotationScanType
- from ..util.typing import CallableReference
- from ..util.typing import DescriptorReference
- from ..util.typing import RODescriptorReference
- _T = TypeVar("_T", bound=Any)
- _PT = TypeVar("_PT", bound=Any)
- class DescriptorProperty(MapperProperty[_T]):
- """:class:`.MapperProperty` which proxies access to a
- user-defined descriptor."""
- doc: Optional[str] = None
- uses_objects = False
- _links_to_entity = False
- descriptor: DescriptorReference[Any]
- def _column_strategy_attrs(self) -> Sequence[QueryableAttribute[Any]]:
- raise NotImplementedError(
- "This MapperProperty does not implement column loader strategies"
- )
- def get_history(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
- ) -> History:
- raise NotImplementedError()
- def instrument_class(self, mapper: Mapper[Any]) -> None:
- prop = self
- class _ProxyImpl(attributes.AttributeImpl):
- accepts_scalar_loader = False
- load_on_unexpire = True
- collection = False
- @property
- def uses_objects(self) -> bool: # type: ignore
- return prop.uses_objects
- def __init__(self, key: str):
- self.key = key
- def get_history(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
- ) -> History:
- return prop.get_history(state, dict_, passive)
- if self.descriptor is None:
- desc = getattr(mapper.class_, self.key, None)
- if mapper._is_userland_descriptor(self.key, desc):
- self.descriptor = desc
- if self.descriptor is None:
- def fset(obj: Any, value: Any) -> None:
- setattr(obj, self.name, value)
- def fdel(obj: Any) -> None:
- delattr(obj, self.name)
- def fget(obj: Any) -> Any:
- return getattr(obj, self.name)
- self.descriptor = property(fget=fget, fset=fset, fdel=fdel)
- proxy_attr = attributes.create_proxied_attribute(self.descriptor)(
- self.parent.class_,
- self.key,
- self.descriptor,
- lambda: self._comparator_factory(mapper),
- doc=self.doc,
- original_property=self,
- )
- proxy_attr.impl = _ProxyImpl(self.key)
- mapper.class_manager.instrument_attribute(self.key, proxy_attr)
- _CompositeAttrType = Union[
- str,
- "Column[_T]",
- "MappedColumn[_T]",
- "InstrumentedAttribute[_T]",
- "Mapped[_T]",
- ]
- _CC = TypeVar("_CC", bound=Any)
- _composite_getters: weakref.WeakKeyDictionary[
- Type[Any], Callable[[Any], Tuple[Any, ...]]
- ] = weakref.WeakKeyDictionary()
- class CompositeProperty(
- _MapsColumns[_CC], _IntrospectsAnnotations, DescriptorProperty[_CC]
- ):
- """Defines a "composite" mapped attribute, representing a collection
- of columns as one attribute.
- :class:`.CompositeProperty` is constructed using the :func:`.composite`
- function.
- .. seealso::
- :ref:`mapper_composite`
- """
- composite_class: Union[Type[_CC], Callable[..., _CC]]
- attrs: Tuple[_CompositeAttrType[Any], ...]
- _generated_composite_accessor: CallableReference[
- Optional[Callable[[_CC], Tuple[Any, ...]]]
- ]
- comparator_factory: Type[Comparator[_CC]]
- def __init__(
- self,
- _class_or_attr: Union[
- None, Type[_CC], Callable[..., _CC], _CompositeAttrType[Any]
- ] = None,
- *attrs: _CompositeAttrType[Any],
- attribute_options: Optional[_AttributeOptions] = None,
- active_history: bool = False,
- deferred: bool = False,
- group: Optional[str] = None,
- comparator_factory: Optional[Type[Comparator[_CC]]] = None,
- info: Optional[_InfoType] = None,
- **kwargs: Any,
- ):
- super().__init__(attribute_options=attribute_options)
- if isinstance(_class_or_attr, (Mapped, str, sql.ColumnElement)):
- self.attrs = (_class_or_attr,) + attrs
- # will initialize within declarative_scan
- self.composite_class = None # type: ignore
- else:
- self.composite_class = _class_or_attr # type: ignore
- self.attrs = attrs
- self.active_history = active_history
- self.deferred = deferred
- self.group = group
- self.comparator_factory = (
- comparator_factory
- if comparator_factory is not None
- else self.__class__.Comparator
- )
- self._generated_composite_accessor = None
- if info is not None:
- self.info.update(info)
- util.set_creation_order(self)
- self._create_descriptor()
- self._init_accessor()
- def instrument_class(self, mapper: Mapper[Any]) -> None:
- super().instrument_class(mapper)
- self._setup_event_handlers()
- def _composite_values_from_instance(self, value: _CC) -> Tuple[Any, ...]:
- if self._generated_composite_accessor:
- return self._generated_composite_accessor(value)
- else:
- try:
- accessor = value.__composite_values__
- except AttributeError as ae:
- raise sa_exc.InvalidRequestError(
- f"Composite class {self.composite_class.__name__} is not "
- f"a dataclass and does not define a __composite_values__()"
- " method; can't get state"
- ) from ae
- else:
- return accessor() # type: ignore
- def do_init(self) -> None:
- """Initialization which occurs after the :class:`.Composite`
- has been associated with its parent mapper.
- """
- self._setup_arguments_on_columns()
- _COMPOSITE_FGET = object()
- def _create_descriptor(self) -> None:
- """Create the Python descriptor that will serve as
- the access point on instances of the mapped class.
- """
- def fget(instance: Any) -> Any:
- dict_ = attributes.instance_dict(instance)
- state = attributes.instance_state(instance)
- if self.key not in dict_:
- # key not present. Iterate through related
- # attributes, retrieve their values. This
- # ensures they all load.
- values = [
- getattr(instance, key) for key in self._attribute_keys
- ]
- # current expected behavior here is that the composite is
- # created on access if the object is persistent or if
- # col attributes have non-None. This would be better
- # if the composite were created unconditionally,
- # but that would be a behavioral change.
- if self.key not in dict_ and (
- state.key is not None or not _none_set.issuperset(values)
- ):
- dict_[self.key] = self.composite_class(*values)
- state.manager.dispatch.refresh(
- state, self._COMPOSITE_FGET, [self.key]
- )
- return dict_.get(self.key, None)
- def fset(instance: Any, value: Any) -> None:
- dict_ = attributes.instance_dict(instance)
- state = attributes.instance_state(instance)
- attr = state.manager[self.key]
- if attr.dispatch._active_history:
- previous = fget(instance)
- else:
- previous = dict_.get(self.key, LoaderCallableStatus.NO_VALUE)
- for fn in attr.dispatch.set:
- value = fn(state, value, previous, attr.impl)
- dict_[self.key] = value
- if value is None:
- for key in self._attribute_keys:
- setattr(instance, key, None)
- else:
- for key, value in zip(
- self._attribute_keys,
- self._composite_values_from_instance(value),
- ):
- setattr(instance, key, value)
- def fdel(instance: Any) -> None:
- state = attributes.instance_state(instance)
- dict_ = attributes.instance_dict(instance)
- attr = state.manager[self.key]
- if attr.dispatch._active_history:
- previous = fget(instance)
- dict_.pop(self.key, None)
- else:
- previous = dict_.pop(self.key, LoaderCallableStatus.NO_VALUE)
- attr = state.manager[self.key]
- attr.dispatch.remove(state, previous, attr.impl)
- for key in self._attribute_keys:
- setattr(instance, key, None)
- self.descriptor = property(fget, fset, fdel)
- @util.preload_module("sqlalchemy.orm.properties")
- def declarative_scan(
- self,
- decl_scan: _ClassScanMapperConfig,
- registry: _RegistryType,
- cls: Type[Any],
- originating_module: Optional[str],
- key: str,
- mapped_container: Optional[Type[Mapped[Any]]],
- annotation: Optional[_AnnotationScanType],
- extracted_mapped_annotation: Optional[_AnnotationScanType],
- is_dataclass_field: bool,
- ) -> None:
- MappedColumn = util.preloaded.orm_properties.MappedColumn
- if (
- self.composite_class is None
- and extracted_mapped_annotation is None
- ):
- self._raise_for_required(key, cls)
- argument = extracted_mapped_annotation
- if is_pep593(argument):
- argument = get_args(argument)[0]
- if argument and self.composite_class is None:
- if isinstance(argument, str) or is_fwd_ref(
- argument, check_generic=True
- ):
- if originating_module is None:
- str_arg = (
- argument.__forward_arg__
- if hasattr(argument, "__forward_arg__")
- else str(argument)
- )
- raise sa_exc.ArgumentError(
- f"Can't use forward ref {argument} for composite "
- f"class argument; set up the type as Mapped[{str_arg}]"
- )
- argument = de_stringify_annotation(
- cls, argument, originating_module, include_generic=True
- )
- self.composite_class = argument
- if is_dataclass(self.composite_class):
- self._setup_for_dataclass(
- decl_scan, registry, cls, originating_module, key
- )
- else:
- for attr in self.attrs:
- if (
- isinstance(attr, (MappedColumn, schema.Column))
- and attr.name is None
- ):
- raise sa_exc.ArgumentError(
- "Composite class column arguments must be named "
- "unless a dataclass is used"
- )
- self._init_accessor()
- def _init_accessor(self) -> None:
- if is_dataclass(self.composite_class) and not hasattr(
- self.composite_class, "__composite_values__"
- ):
- insp = inspect.signature(self.composite_class)
- getter = operator.attrgetter(
- *[p.name for p in insp.parameters.values()]
- )
- if len(insp.parameters) == 1:
- self._generated_composite_accessor = lambda obj: (getter(obj),)
- else:
- self._generated_composite_accessor = getter
- if (
- self.composite_class is not None
- and isinstance(self.composite_class, type)
- and self.composite_class not in _composite_getters
- ):
- if self._generated_composite_accessor is not None:
- _composite_getters[self.composite_class] = (
- self._generated_composite_accessor
- )
- elif hasattr(self.composite_class, "__composite_values__"):
- _composite_getters[self.composite_class] = (
- lambda obj: obj.__composite_values__()
- )
- @util.preload_module("sqlalchemy.orm.properties")
- @util.preload_module("sqlalchemy.orm.decl_base")
- def _setup_for_dataclass(
- self,
- decl_scan: _ClassScanMapperConfig,
- registry: _RegistryType,
- cls: Type[Any],
- originating_module: Optional[str],
- key: str,
- ) -> None:
- MappedColumn = util.preloaded.orm_properties.MappedColumn
- decl_base = util.preloaded.orm_decl_base
- insp = inspect.signature(self.composite_class)
- for param, attr in itertools.zip_longest(
- insp.parameters.values(), self.attrs
- ):
- if param is None:
- raise sa_exc.ArgumentError(
- f"number of composite attributes "
- f"{len(self.attrs)} exceeds "
- f"that of the number of attributes in class "
- f"{self.composite_class.__name__} {len(insp.parameters)}"
- )
- if attr is None:
- # fill in missing attr spots with empty MappedColumn
- attr = MappedColumn()
- self.attrs += (attr,)
- if isinstance(attr, MappedColumn):
- attr.declarative_scan_for_composite(
- decl_scan,
- registry,
- cls,
- originating_module,
- key,
- param.name,
- param.annotation,
- )
- elif isinstance(attr, schema.Column):
- decl_base._undefer_column_name(param.name, attr)
- @util.memoized_property
- def _comparable_elements(self) -> Sequence[QueryableAttribute[Any]]:
- return [getattr(self.parent.class_, prop.key) for prop in self.props]
- @util.memoized_property
- @util.preload_module("orm.properties")
- def props(self) -> Sequence[MapperProperty[Any]]:
- props = []
- MappedColumn = util.preloaded.orm_properties.MappedColumn
- for attr in self.attrs:
- if isinstance(attr, str):
- prop = self.parent.get_property(attr, _configure_mappers=False)
- elif isinstance(attr, schema.Column):
- prop = self.parent._columntoproperty[attr]
- elif isinstance(attr, MappedColumn):
- prop = self.parent._columntoproperty[attr.column]
- elif isinstance(attr, attributes.InstrumentedAttribute):
- prop = attr.property
- else:
- prop = None
- if not isinstance(prop, MapperProperty):
- raise sa_exc.ArgumentError(
- "Composite expects Column objects or mapped "
- f"attributes/attribute names as arguments, got: {attr!r}"
- )
- props.append(prop)
- return props
- def _column_strategy_attrs(self) -> Sequence[QueryableAttribute[Any]]:
- return self._comparable_elements
- @util.non_memoized_property
- @util.preload_module("orm.properties")
- def columns(self) -> Sequence[Column[Any]]:
- MappedColumn = util.preloaded.orm_properties.MappedColumn
- return [
- a.column if isinstance(a, MappedColumn) else a
- for a in self.attrs
- if isinstance(a, (schema.Column, MappedColumn))
- ]
- @property
- def mapper_property_to_assign(self) -> Optional[MapperProperty[_CC]]:
- return self
- @property
- def columns_to_assign(self) -> List[Tuple[schema.Column[Any], int]]:
- return [(c, 0) for c in self.columns if c.table is None]
- @util.preload_module("orm.properties")
- def _setup_arguments_on_columns(self) -> None:
- """Propagate configuration arguments made on this composite
- to the target columns, for those that apply.
- """
- ColumnProperty = util.preloaded.orm_properties.ColumnProperty
- for prop in self.props:
- if not isinstance(prop, ColumnProperty):
- continue
- else:
- cprop = prop
- cprop.active_history = self.active_history
- if self.deferred:
- cprop.deferred = self.deferred
- cprop.strategy_key = (("deferred", True), ("instrument", True))
- cprop.group = self.group
- def _setup_event_handlers(self) -> None:
- """Establish events that populate/expire the composite attribute."""
- def load_handler(
- state: InstanceState[Any], context: ORMCompileState
- ) -> None:
- _load_refresh_handler(state, context, None, is_refresh=False)
- def refresh_handler(
- state: InstanceState[Any],
- context: ORMCompileState,
- to_load: Optional[Sequence[str]],
- ) -> None:
- # note this corresponds to sqlalchemy.ext.mutable load_attrs()
- if not to_load or (
- {self.key}.union(self._attribute_keys)
- ).intersection(to_load):
- _load_refresh_handler(state, context, to_load, is_refresh=True)
- def _load_refresh_handler(
- state: InstanceState[Any],
- context: ORMCompileState,
- to_load: Optional[Sequence[str]],
- is_refresh: bool,
- ) -> None:
- dict_ = state.dict
- # if context indicates we are coming from the
- # fget() handler, this already set the value; skip the
- # handler here. (other handlers like mutablecomposite will still
- # want to catch it)
- # there's an insufficiency here in that the fget() handler
- # really should not be using the refresh event and there should
- # be some other event that mutablecomposite can subscribe
- # towards for this.
- if (
- not is_refresh or context is self._COMPOSITE_FGET
- ) and self.key in dict_:
- return
- # if column elements aren't loaded, skip.
- # __get__() will initiate a load for those
- # columns
- for k in self._attribute_keys:
- if k not in dict_:
- return
- dict_[self.key] = self.composite_class(
- *[state.dict[key] for key in self._attribute_keys]
- )
- def expire_handler(
- state: InstanceState[Any], keys: Optional[Sequence[str]]
- ) -> None:
- if keys is None or set(self._attribute_keys).intersection(keys):
- state.dict.pop(self.key, None)
- def insert_update_handler(
- mapper: Mapper[Any],
- connection: Connection,
- state: InstanceState[Any],
- ) -> None:
- """After an insert or update, some columns may be expired due
- to server side defaults, or re-populated due to client side
- defaults. Pop out the composite value here so that it
- recreates.
- """
- state.dict.pop(self.key, None)
- event.listen(
- self.parent, "after_insert", insert_update_handler, raw=True
- )
- event.listen(
- self.parent, "after_update", insert_update_handler, raw=True
- )
- event.listen(
- self.parent, "load", load_handler, raw=True, propagate=True
- )
- event.listen(
- self.parent, "refresh", refresh_handler, raw=True, propagate=True
- )
- event.listen(
- self.parent, "expire", expire_handler, raw=True, propagate=True
- )
- proxy_attr = self.parent.class_manager[self.key]
- proxy_attr.impl.dispatch = proxy_attr.dispatch # type: ignore
- proxy_attr.impl.dispatch._active_history = self.active_history
- # TODO: need a deserialize hook here
- @util.memoized_property
- def _attribute_keys(self) -> Sequence[str]:
- return [prop.key for prop in self.props]
- def _populate_composite_bulk_save_mappings_fn(
- self,
- ) -> Callable[[Dict[str, Any]], None]:
- if self._generated_composite_accessor:
- get_values = self._generated_composite_accessor
- else:
- def get_values(val: Any) -> Tuple[Any]:
- return val.__composite_values__() # type: ignore
- attrs = [prop.key for prop in self.props]
- def populate(dest_dict: Dict[str, Any]) -> None:
- dest_dict.update(
- {
- key: val
- for key, val in zip(
- attrs, get_values(dest_dict.pop(self.key))
- )
- }
- )
- return populate
- def get_history(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
- ) -> History:
- """Provided for userland code that uses attributes.get_history()."""
- added: List[Any] = []
- deleted: List[Any] = []
- has_history = False
- for prop in self.props:
- key = prop.key
- hist = state.manager[key].impl.get_history(state, dict_)
- if hist.has_changes():
- has_history = True
- non_deleted = hist.non_deleted()
- if non_deleted:
- added.extend(non_deleted)
- else:
- added.append(None)
- if hist.deleted:
- deleted.extend(hist.deleted)
- else:
- deleted.append(None)
- if has_history:
- return attributes.History(
- [self.composite_class(*added)],
- (),
- [self.composite_class(*deleted)],
- )
- else:
- return attributes.History((), [self.composite_class(*added)], ())
- def _comparator_factory(
- self, mapper: Mapper[Any]
- ) -> Composite.Comparator[_CC]:
- return self.comparator_factory(self, mapper)
- class CompositeBundle(orm_util.Bundle[_T]):
- def __init__(
- self,
- property_: Composite[_T],
- expr: ClauseList,
- ):
- self.property = property_
- super().__init__(property_.key, *expr)
- def create_row_processor(
- self,
- query: Select[Any],
- procs: Sequence[Callable[[Row[Any]], Any]],
- labels: Sequence[str],
- ) -> Callable[[Row[Any]], Any]:
- def proc(row: Row[Any]) -> Any:
- return self.property.composite_class(
- *[proc(row) for proc in procs]
- )
- return proc
- class Comparator(PropComparator[_PT]):
- """Produce boolean, comparison, and other operators for
- :class:`.Composite` attributes.
- See the example in :ref:`composite_operations` for an overview
- of usage , as well as the documentation for :class:`.PropComparator`.
- .. seealso::
- :class:`.PropComparator`
- :class:`.ColumnOperators`
- :ref:`types_operators`
- :attr:`.TypeEngine.comparator_factory`
- """
- # https://github.com/python/mypy/issues/4266
- __hash__ = None # type: ignore
- prop: RODescriptorReference[Composite[_PT]]
- @util.memoized_property
- def clauses(self) -> ClauseList:
- return expression.ClauseList(
- group=False, *self._comparable_elements
- )
- def __clause_element__(self) -> CompositeProperty.CompositeBundle[_PT]:
- return self.expression
- @util.memoized_property
- def expression(self) -> CompositeProperty.CompositeBundle[_PT]:
- clauses = self.clauses._annotate(
- {
- "parententity": self._parententity,
- "parentmapper": self._parententity,
- "proxy_key": self.prop.key,
- }
- )
- return CompositeProperty.CompositeBundle(self.prop, clauses)
- def _bulk_update_tuples(
- self, value: Any
- ) -> Sequence[Tuple[_DMLColumnArgument, Any]]:
- if isinstance(value, BindParameter):
- value = value.value
- values: Sequence[Any]
- if value is None:
- values = [None for key in self.prop._attribute_keys]
- elif isinstance(self.prop.composite_class, type) and isinstance(
- value, self.prop.composite_class
- ):
- values = self.prop._composite_values_from_instance(
- value # type: ignore[arg-type]
- )
- else:
- raise sa_exc.ArgumentError(
- "Can't UPDATE composite attribute %s to %r"
- % (self.prop, value)
- )
- return list(zip(self._comparable_elements, values))
- @util.memoized_property
- def _comparable_elements(self) -> Sequence[QueryableAttribute[Any]]:
- if self._adapt_to_entity:
- return [
- getattr(self._adapt_to_entity.entity, prop.key)
- for prop in self.prop._comparable_elements
- ]
- else:
- return self.prop._comparable_elements
- def __eq__(self, other: Any) -> ColumnElement[bool]: # type: ignore[override] # noqa: E501
- return self._compare(operators.eq, other)
- def __ne__(self, other: Any) -> ColumnElement[bool]: # type: ignore[override] # noqa: E501
- return self._compare(operators.ne, other)
- def __lt__(self, other: Any) -> ColumnElement[bool]:
- return self._compare(operators.lt, other)
- def __gt__(self, other: Any) -> ColumnElement[bool]:
- return self._compare(operators.gt, other)
- def __le__(self, other: Any) -> ColumnElement[bool]:
- return self._compare(operators.le, other)
- def __ge__(self, other: Any) -> ColumnElement[bool]:
- return self._compare(operators.ge, other)
- # what might be interesting would be if we create
- # an instance of the composite class itself with
- # the columns as data members, then use "hybrid style" comparison
- # to create these comparisons. then your Point.__eq__() method could
- # be where comparison behavior is defined for SQL also. Likely
- # not a good choice for default behavior though, not clear how it would
- # work w/ dataclasses, etc. also no demand for any of this anyway.
- def _compare(
- self, operator: OperatorType, other: Any
- ) -> ColumnElement[bool]:
- values: Sequence[Any]
- if other is None:
- values = [None] * len(self.prop._comparable_elements)
- else:
- values = self.prop._composite_values_from_instance(other)
- comparisons = [
- operator(a, b)
- for a, b in zip(self.prop._comparable_elements, values)
- ]
- if self._adapt_to_entity:
- assert self.adapter is not None
- comparisons = [self.adapter(x) for x in comparisons]
- return sql.and_(*comparisons)
- def __str__(self) -> str:
- return str(self.parent.class_.__name__) + "." + self.key
- class Composite(CompositeProperty[_T], _DeclarativeMapped[_T]):
- """Declarative-compatible front-end for the :class:`.CompositeProperty`
- class.
- Public constructor is the :func:`_orm.composite` function.
- .. versionchanged:: 2.0 Added :class:`_orm.Composite` as a Declarative
- compatible subclass of :class:`_orm.CompositeProperty`.
- .. seealso::
- :ref:`mapper_composite`
- """
- inherit_cache = True
- """:meta private:"""
- class ConcreteInheritedProperty(DescriptorProperty[_T]):
- """A 'do nothing' :class:`.MapperProperty` that disables
- an attribute on a concrete subclass that is only present
- on the inherited mapper, not the concrete classes' mapper.
- Cases where this occurs include:
- * When the superclass mapper is mapped against a
- "polymorphic union", which includes all attributes from
- all subclasses.
- * When a relationship() is configured on an inherited mapper,
- but not on the subclass mapper. Concrete mappers require
- that relationship() is configured explicitly on each
- subclass.
- """
- def _comparator_factory(
- self, mapper: Mapper[Any]
- ) -> Type[PropComparator[_T]]:
- comparator_callable = None
- for m in self.parent.iterate_to_root():
- p = m._props[self.key]
- if getattr(p, "comparator_factory", None) is not None:
- comparator_callable = p.comparator_factory
- break
- assert comparator_callable is not None
- return comparator_callable(p, mapper) # type: ignore
- def __init__(self) -> None:
- super().__init__()
- def warn() -> NoReturn:
- raise AttributeError(
- "Concrete %s does not implement "
- "attribute %r at the instance level. Add "
- "this property explicitly to %s."
- % (self.parent, self.key, self.parent)
- )
- class NoninheritedConcreteProp:
- def __set__(s: Any, obj: Any, value: Any) -> NoReturn:
- warn()
- def __delete__(s: Any, obj: Any) -> NoReturn:
- warn()
- def __get__(s: Any, obj: Any, owner: Any) -> Any:
- if obj is None:
- return self.descriptor
- warn()
- self.descriptor = NoninheritedConcreteProp()
- class SynonymProperty(DescriptorProperty[_T]):
- """Denote an attribute name as a synonym to a mapped property,
- in that the attribute will mirror the value and expression behavior
- of another attribute.
- :class:`.Synonym` is constructed using the :func:`_orm.synonym`
- function.
- .. seealso::
- :ref:`synonyms` - Overview of synonyms
- """
- comparator_factory: Optional[Type[PropComparator[_T]]]
- def __init__(
- self,
- name: str,
- map_column: Optional[bool] = None,
- descriptor: Optional[Any] = None,
- comparator_factory: Optional[Type[PropComparator[_T]]] = None,
- attribute_options: Optional[_AttributeOptions] = None,
- info: Optional[_InfoType] = None,
- doc: Optional[str] = None,
- ):
- super().__init__(attribute_options=attribute_options)
- self.name = name
- self.map_column = map_column
- self.descriptor = descriptor
- self.comparator_factory = comparator_factory
- if doc:
- self.doc = doc
- elif descriptor and descriptor.__doc__:
- self.doc = descriptor.__doc__
- else:
- self.doc = None
- if info:
- self.info.update(info)
- util.set_creation_order(self)
- if not TYPE_CHECKING:
- @property
- def uses_objects(self) -> bool:
- return getattr(self.parent.class_, self.name).impl.uses_objects
- # TODO: when initialized, check _proxied_object,
- # emit a warning if its not a column-based property
- @util.memoized_property
- def _proxied_object(
- self,
- ) -> Union[MapperProperty[_T], SQLORMOperations[_T]]:
- attr = getattr(self.parent.class_, self.name)
- if not hasattr(attr, "property") or not isinstance(
- attr.property, MapperProperty
- ):
- # attribute is a non-MapperProprerty proxy such as
- # hybrid or association proxy
- if isinstance(attr, attributes.QueryableAttribute):
- return attr.comparator
- elif isinstance(attr, SQLORMOperations):
- # assocaition proxy comes here
- return attr
- raise sa_exc.InvalidRequestError(
- """synonym() attribute "%s.%s" only supports """
- """ORM mapped attributes, got %r"""
- % (self.parent.class_.__name__, self.name, attr)
- )
- return attr.property
- def _column_strategy_attrs(self) -> Sequence[QueryableAttribute[Any]]:
- return (getattr(self.parent.class_, self.name),)
- def _comparator_factory(self, mapper: Mapper[Any]) -> SQLORMOperations[_T]:
- prop = self._proxied_object
- if isinstance(prop, MapperProperty):
- if self.comparator_factory:
- comp = self.comparator_factory(prop, mapper)
- else:
- comp = prop.comparator_factory(prop, mapper)
- return comp
- else:
- return prop
- def get_history(
- self,
- state: InstanceState[Any],
- dict_: _InstanceDict,
- passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
- ) -> History:
- attr: QueryableAttribute[Any] = getattr(self.parent.class_, self.name)
- return attr.impl.get_history(state, dict_, passive=passive)
- @util.preload_module("sqlalchemy.orm.properties")
- def set_parent(self, parent: Mapper[Any], init: bool) -> None:
- properties = util.preloaded.orm_properties
- if self.map_column:
- # implement the 'map_column' option.
- if self.key not in parent.persist_selectable.c:
- raise sa_exc.ArgumentError(
- "Can't compile synonym '%s': no column on table "
- "'%s' named '%s'"
- % (
- self.name,
- parent.persist_selectable.description,
- self.key,
- )
- )
- elif (
- parent.persist_selectable.c[self.key]
- in parent._columntoproperty
- and parent._columntoproperty[
- parent.persist_selectable.c[self.key]
- ].key
- == self.name
- ):
- raise sa_exc.ArgumentError(
- "Can't call map_column=True for synonym %r=%r, "
- "a ColumnProperty already exists keyed to the name "
- "%r for column %r"
- % (self.key, self.name, self.name, self.key)
- )
- p: ColumnProperty[Any] = properties.ColumnProperty(
- parent.persist_selectable.c[self.key]
- )
- parent._configure_property(self.name, p, init=init, setparent=True)
- p._mapped_by_synonym = self.key
- self.parent = parent
- class Synonym(SynonymProperty[_T], _DeclarativeMapped[_T]):
- """Declarative front-end for the :class:`.SynonymProperty` class.
- Public constructor is the :func:`_orm.synonym` function.
- .. versionchanged:: 2.0 Added :class:`_orm.Synonym` as a Declarative
- compatible subclass for :class:`_orm.SynonymProperty`
- .. seealso::
- :ref:`synonyms` - Overview of synonyms
- """
- inherit_cache = True
- """:meta private:"""
|