SQLAlchemy 2.0.47__cp313-cp313t-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (274) hide show
  1. sqlalchemy/__init__.py +283 -0
  2. sqlalchemy/connectors/__init__.py +18 -0
  3. sqlalchemy/connectors/aioodbc.py +184 -0
  4. sqlalchemy/connectors/asyncio.py +429 -0
  5. sqlalchemy/connectors/pyodbc.py +250 -0
  6. sqlalchemy/cyextension/__init__.py +6 -0
  7. sqlalchemy/cyextension/collections.cp313t-win_amd64.pyd +0 -0
  8. sqlalchemy/cyextension/collections.pyx +409 -0
  9. sqlalchemy/cyextension/immutabledict.cp313t-win_amd64.pyd +0 -0
  10. sqlalchemy/cyextension/immutabledict.pxd +8 -0
  11. sqlalchemy/cyextension/immutabledict.pyx +133 -0
  12. sqlalchemy/cyextension/processors.cp313t-win_amd64.pyd +0 -0
  13. sqlalchemy/cyextension/processors.pyx +68 -0
  14. sqlalchemy/cyextension/resultproxy.cp313t-win_amd64.pyd +0 -0
  15. sqlalchemy/cyextension/resultproxy.pyx +102 -0
  16. sqlalchemy/cyextension/util.cp313t-win_amd64.pyd +0 -0
  17. sqlalchemy/cyextension/util.pyx +90 -0
  18. sqlalchemy/dialects/__init__.py +62 -0
  19. sqlalchemy/dialects/_typing.py +30 -0
  20. sqlalchemy/dialects/mssql/__init__.py +88 -0
  21. sqlalchemy/dialects/mssql/aioodbc.py +63 -0
  22. sqlalchemy/dialects/mssql/base.py +4093 -0
  23. sqlalchemy/dialects/mssql/information_schema.py +285 -0
  24. sqlalchemy/dialects/mssql/json.py +129 -0
  25. sqlalchemy/dialects/mssql/provision.py +185 -0
  26. sqlalchemy/dialects/mssql/pymssql.py +126 -0
  27. sqlalchemy/dialects/mssql/pyodbc.py +760 -0
  28. sqlalchemy/dialects/mysql/__init__.py +104 -0
  29. sqlalchemy/dialects/mysql/aiomysql.py +250 -0
  30. sqlalchemy/dialects/mysql/asyncmy.py +231 -0
  31. sqlalchemy/dialects/mysql/base.py +3949 -0
  32. sqlalchemy/dialects/mysql/cymysql.py +106 -0
  33. sqlalchemy/dialects/mysql/dml.py +225 -0
  34. sqlalchemy/dialects/mysql/enumerated.py +282 -0
  35. sqlalchemy/dialects/mysql/expression.py +146 -0
  36. sqlalchemy/dialects/mysql/json.py +91 -0
  37. sqlalchemy/dialects/mysql/mariadb.py +72 -0
  38. sqlalchemy/dialects/mysql/mariadbconnector.py +322 -0
  39. sqlalchemy/dialects/mysql/mysqlconnector.py +302 -0
  40. sqlalchemy/dialects/mysql/mysqldb.py +314 -0
  41. sqlalchemy/dialects/mysql/provision.py +153 -0
  42. sqlalchemy/dialects/mysql/pymysql.py +158 -0
  43. sqlalchemy/dialects/mysql/pyodbc.py +157 -0
  44. sqlalchemy/dialects/mysql/reflection.py +727 -0
  45. sqlalchemy/dialects/mysql/reserved_words.py +570 -0
  46. sqlalchemy/dialects/mysql/types.py +835 -0
  47. sqlalchemy/dialects/oracle/__init__.py +81 -0
  48. sqlalchemy/dialects/oracle/base.py +3802 -0
  49. sqlalchemy/dialects/oracle/cx_oracle.py +1555 -0
  50. sqlalchemy/dialects/oracle/dictionary.py +507 -0
  51. sqlalchemy/dialects/oracle/oracledb.py +941 -0
  52. sqlalchemy/dialects/oracle/provision.py +297 -0
  53. sqlalchemy/dialects/oracle/types.py +316 -0
  54. sqlalchemy/dialects/oracle/vector.py +365 -0
  55. sqlalchemy/dialects/postgresql/__init__.py +167 -0
  56. sqlalchemy/dialects/postgresql/_psycopg_common.py +189 -0
  57. sqlalchemy/dialects/postgresql/array.py +519 -0
  58. sqlalchemy/dialects/postgresql/asyncpg.py +1284 -0
  59. sqlalchemy/dialects/postgresql/base.py +5378 -0
  60. sqlalchemy/dialects/postgresql/dml.py +339 -0
  61. sqlalchemy/dialects/postgresql/ext.py +540 -0
  62. sqlalchemy/dialects/postgresql/hstore.py +406 -0
  63. sqlalchemy/dialects/postgresql/json.py +404 -0
  64. sqlalchemy/dialects/postgresql/named_types.py +524 -0
  65. sqlalchemy/dialects/postgresql/operators.py +129 -0
  66. sqlalchemy/dialects/postgresql/pg8000.py +669 -0
  67. sqlalchemy/dialects/postgresql/pg_catalog.py +326 -0
  68. sqlalchemy/dialects/postgresql/provision.py +183 -0
  69. sqlalchemy/dialects/postgresql/psycopg.py +862 -0
  70. sqlalchemy/dialects/postgresql/psycopg2.py +892 -0
  71. sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
  72. sqlalchemy/dialects/postgresql/ranges.py +1031 -0
  73. sqlalchemy/dialects/postgresql/types.py +313 -0
  74. sqlalchemy/dialects/sqlite/__init__.py +57 -0
  75. sqlalchemy/dialects/sqlite/aiosqlite.py +482 -0
  76. sqlalchemy/dialects/sqlite/base.py +3056 -0
  77. sqlalchemy/dialects/sqlite/dml.py +263 -0
  78. sqlalchemy/dialects/sqlite/json.py +92 -0
  79. sqlalchemy/dialects/sqlite/provision.py +229 -0
  80. sqlalchemy/dialects/sqlite/pysqlcipher.py +157 -0
  81. sqlalchemy/dialects/sqlite/pysqlite.py +756 -0
  82. sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
  83. sqlalchemy/engine/__init__.py +62 -0
  84. sqlalchemy/engine/_py_processors.py +136 -0
  85. sqlalchemy/engine/_py_row.py +128 -0
  86. sqlalchemy/engine/_py_util.py +74 -0
  87. sqlalchemy/engine/base.py +3390 -0
  88. sqlalchemy/engine/characteristics.py +155 -0
  89. sqlalchemy/engine/create.py +893 -0
  90. sqlalchemy/engine/cursor.py +2298 -0
  91. sqlalchemy/engine/default.py +2394 -0
  92. sqlalchemy/engine/events.py +965 -0
  93. sqlalchemy/engine/interfaces.py +3471 -0
  94. sqlalchemy/engine/mock.py +134 -0
  95. sqlalchemy/engine/processors.py +61 -0
  96. sqlalchemy/engine/reflection.py +2102 -0
  97. sqlalchemy/engine/result.py +2399 -0
  98. sqlalchemy/engine/row.py +400 -0
  99. sqlalchemy/engine/strategies.py +16 -0
  100. sqlalchemy/engine/url.py +924 -0
  101. sqlalchemy/engine/util.py +167 -0
  102. sqlalchemy/event/__init__.py +26 -0
  103. sqlalchemy/event/api.py +220 -0
  104. sqlalchemy/event/attr.py +676 -0
  105. sqlalchemy/event/base.py +472 -0
  106. sqlalchemy/event/legacy.py +258 -0
  107. sqlalchemy/event/registry.py +390 -0
  108. sqlalchemy/events.py +17 -0
  109. sqlalchemy/exc.py +832 -0
  110. sqlalchemy/ext/__init__.py +11 -0
  111. sqlalchemy/ext/associationproxy.py +2027 -0
  112. sqlalchemy/ext/asyncio/__init__.py +25 -0
  113. sqlalchemy/ext/asyncio/base.py +281 -0
  114. sqlalchemy/ext/asyncio/engine.py +1471 -0
  115. sqlalchemy/ext/asyncio/exc.py +21 -0
  116. sqlalchemy/ext/asyncio/result.py +965 -0
  117. sqlalchemy/ext/asyncio/scoping.py +1599 -0
  118. sqlalchemy/ext/asyncio/session.py +1947 -0
  119. sqlalchemy/ext/automap.py +1701 -0
  120. sqlalchemy/ext/baked.py +570 -0
  121. sqlalchemy/ext/compiler.py +600 -0
  122. sqlalchemy/ext/declarative/__init__.py +65 -0
  123. sqlalchemy/ext/declarative/extensions.py +564 -0
  124. sqlalchemy/ext/horizontal_shard.py +478 -0
  125. sqlalchemy/ext/hybrid.py +1535 -0
  126. sqlalchemy/ext/indexable.py +364 -0
  127. sqlalchemy/ext/instrumentation.py +450 -0
  128. sqlalchemy/ext/mutable.py +1085 -0
  129. sqlalchemy/ext/mypy/__init__.py +6 -0
  130. sqlalchemy/ext/mypy/apply.py +324 -0
  131. sqlalchemy/ext/mypy/decl_class.py +515 -0
  132. sqlalchemy/ext/mypy/infer.py +590 -0
  133. sqlalchemy/ext/mypy/names.py +335 -0
  134. sqlalchemy/ext/mypy/plugin.py +303 -0
  135. sqlalchemy/ext/mypy/util.py +357 -0
  136. sqlalchemy/ext/orderinglist.py +439 -0
  137. sqlalchemy/ext/serializer.py +185 -0
  138. sqlalchemy/future/__init__.py +16 -0
  139. sqlalchemy/future/engine.py +15 -0
  140. sqlalchemy/inspection.py +174 -0
  141. sqlalchemy/log.py +288 -0
  142. sqlalchemy/orm/__init__.py +171 -0
  143. sqlalchemy/orm/_orm_constructors.py +2661 -0
  144. sqlalchemy/orm/_typing.py +179 -0
  145. sqlalchemy/orm/attributes.py +2845 -0
  146. sqlalchemy/orm/base.py +971 -0
  147. sqlalchemy/orm/bulk_persistence.py +2135 -0
  148. sqlalchemy/orm/clsregistry.py +571 -0
  149. sqlalchemy/orm/collections.py +1627 -0
  150. sqlalchemy/orm/context.py +3334 -0
  151. sqlalchemy/orm/decl_api.py +2004 -0
  152. sqlalchemy/orm/decl_base.py +2192 -0
  153. sqlalchemy/orm/dependency.py +1302 -0
  154. sqlalchemy/orm/descriptor_props.py +1092 -0
  155. sqlalchemy/orm/dynamic.py +300 -0
  156. sqlalchemy/orm/evaluator.py +379 -0
  157. sqlalchemy/orm/events.py +3252 -0
  158. sqlalchemy/orm/exc.py +237 -0
  159. sqlalchemy/orm/identity.py +302 -0
  160. sqlalchemy/orm/instrumentation.py +754 -0
  161. sqlalchemy/orm/interfaces.py +1496 -0
  162. sqlalchemy/orm/loading.py +1686 -0
  163. sqlalchemy/orm/mapped_collection.py +557 -0
  164. sqlalchemy/orm/mapper.py +4444 -0
  165. sqlalchemy/orm/path_registry.py +809 -0
  166. sqlalchemy/orm/persistence.py +1788 -0
  167. sqlalchemy/orm/properties.py +935 -0
  168. sqlalchemy/orm/query.py +3459 -0
  169. sqlalchemy/orm/relationships.py +3508 -0
  170. sqlalchemy/orm/scoping.py +2148 -0
  171. sqlalchemy/orm/session.py +5280 -0
  172. sqlalchemy/orm/state.py +1168 -0
  173. sqlalchemy/orm/state_changes.py +196 -0
  174. sqlalchemy/orm/strategies.py +3470 -0
  175. sqlalchemy/orm/strategy_options.py +2568 -0
  176. sqlalchemy/orm/sync.py +164 -0
  177. sqlalchemy/orm/unitofwork.py +796 -0
  178. sqlalchemy/orm/util.py +2403 -0
  179. sqlalchemy/orm/writeonly.py +674 -0
  180. sqlalchemy/pool/__init__.py +44 -0
  181. sqlalchemy/pool/base.py +1524 -0
  182. sqlalchemy/pool/events.py +375 -0
  183. sqlalchemy/pool/impl.py +588 -0
  184. sqlalchemy/py.typed +0 -0
  185. sqlalchemy/schema.py +69 -0
  186. sqlalchemy/sql/__init__.py +145 -0
  187. sqlalchemy/sql/_dml_constructors.py +132 -0
  188. sqlalchemy/sql/_elements_constructors.py +1872 -0
  189. sqlalchemy/sql/_orm_types.py +20 -0
  190. sqlalchemy/sql/_py_util.py +75 -0
  191. sqlalchemy/sql/_selectable_constructors.py +763 -0
  192. sqlalchemy/sql/_typing.py +482 -0
  193. sqlalchemy/sql/annotation.py +587 -0
  194. sqlalchemy/sql/base.py +2293 -0
  195. sqlalchemy/sql/cache_key.py +1057 -0
  196. sqlalchemy/sql/coercions.py +1404 -0
  197. sqlalchemy/sql/compiler.py +8081 -0
  198. sqlalchemy/sql/crud.py +1752 -0
  199. sqlalchemy/sql/ddl.py +1444 -0
  200. sqlalchemy/sql/default_comparator.py +551 -0
  201. sqlalchemy/sql/dml.py +1850 -0
  202. sqlalchemy/sql/elements.py +5589 -0
  203. sqlalchemy/sql/events.py +458 -0
  204. sqlalchemy/sql/expression.py +159 -0
  205. sqlalchemy/sql/functions.py +2158 -0
  206. sqlalchemy/sql/lambdas.py +1442 -0
  207. sqlalchemy/sql/naming.py +209 -0
  208. sqlalchemy/sql/operators.py +2623 -0
  209. sqlalchemy/sql/roles.py +323 -0
  210. sqlalchemy/sql/schema.py +6222 -0
  211. sqlalchemy/sql/selectable.py +7265 -0
  212. sqlalchemy/sql/sqltypes.py +3930 -0
  213. sqlalchemy/sql/traversals.py +1024 -0
  214. sqlalchemy/sql/type_api.py +2368 -0
  215. sqlalchemy/sql/util.py +1485 -0
  216. sqlalchemy/sql/visitors.py +1164 -0
  217. sqlalchemy/testing/__init__.py +96 -0
  218. sqlalchemy/testing/assertions.py +994 -0
  219. sqlalchemy/testing/assertsql.py +520 -0
  220. sqlalchemy/testing/asyncio.py +135 -0
  221. sqlalchemy/testing/config.py +434 -0
  222. sqlalchemy/testing/engines.py +483 -0
  223. sqlalchemy/testing/entities.py +117 -0
  224. sqlalchemy/testing/exclusions.py +476 -0
  225. sqlalchemy/testing/fixtures/__init__.py +28 -0
  226. sqlalchemy/testing/fixtures/base.py +384 -0
  227. sqlalchemy/testing/fixtures/mypy.py +332 -0
  228. sqlalchemy/testing/fixtures/orm.py +227 -0
  229. sqlalchemy/testing/fixtures/sql.py +482 -0
  230. sqlalchemy/testing/pickleable.py +155 -0
  231. sqlalchemy/testing/plugin/__init__.py +6 -0
  232. sqlalchemy/testing/plugin/bootstrap.py +51 -0
  233. sqlalchemy/testing/plugin/plugin_base.py +828 -0
  234. sqlalchemy/testing/plugin/pytestplugin.py +892 -0
  235. sqlalchemy/testing/profiling.py +329 -0
  236. sqlalchemy/testing/provision.py +603 -0
  237. sqlalchemy/testing/requirements.py +1945 -0
  238. sqlalchemy/testing/schema.py +198 -0
  239. sqlalchemy/testing/suite/__init__.py +19 -0
  240. sqlalchemy/testing/suite/test_cte.py +237 -0
  241. sqlalchemy/testing/suite/test_ddl.py +389 -0
  242. sqlalchemy/testing/suite/test_deprecations.py +153 -0
  243. sqlalchemy/testing/suite/test_dialect.py +776 -0
  244. sqlalchemy/testing/suite/test_insert.py +630 -0
  245. sqlalchemy/testing/suite/test_reflection.py +3557 -0
  246. sqlalchemy/testing/suite/test_results.py +504 -0
  247. sqlalchemy/testing/suite/test_rowcount.py +258 -0
  248. sqlalchemy/testing/suite/test_select.py +2010 -0
  249. sqlalchemy/testing/suite/test_sequence.py +317 -0
  250. sqlalchemy/testing/suite/test_types.py +2147 -0
  251. sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
  252. sqlalchemy/testing/suite/test_update_delete.py +139 -0
  253. sqlalchemy/testing/util.py +535 -0
  254. sqlalchemy/testing/warnings.py +52 -0
  255. sqlalchemy/types.py +74 -0
  256. sqlalchemy/util/__init__.py +162 -0
  257. sqlalchemy/util/_collections.py +712 -0
  258. sqlalchemy/util/_concurrency_py3k.py +288 -0
  259. sqlalchemy/util/_has_cy.py +40 -0
  260. sqlalchemy/util/_py_collections.py +541 -0
  261. sqlalchemy/util/compat.py +421 -0
  262. sqlalchemy/util/concurrency.py +110 -0
  263. sqlalchemy/util/deprecations.py +401 -0
  264. sqlalchemy/util/langhelpers.py +2203 -0
  265. sqlalchemy/util/preloaded.py +150 -0
  266. sqlalchemy/util/queue.py +322 -0
  267. sqlalchemy/util/tool_support.py +201 -0
  268. sqlalchemy/util/topological.py +120 -0
  269. sqlalchemy/util/typing.py +734 -0
  270. sqlalchemy-2.0.47.dist-info/METADATA +243 -0
  271. sqlalchemy-2.0.47.dist-info/RECORD +274 -0
  272. sqlalchemy-2.0.47.dist-info/WHEEL +5 -0
  273. sqlalchemy-2.0.47.dist-info/licenses/LICENSE +19 -0
  274. sqlalchemy-2.0.47.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1092 @@
1
+ # orm/descriptor_props.py
2
+ # Copyright (C) 2005-2026 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
+ """Descriptor properties are more "auxiliary" properties
9
+ that exist as configurational elements, but don't participate
10
+ as actively in the load/persist ORM loop.
11
+
12
+ """
13
+ from __future__ import annotations
14
+
15
+ from dataclasses import is_dataclass
16
+ import inspect
17
+ import itertools
18
+ import operator
19
+ import typing
20
+ from typing import Any
21
+ from typing import Callable
22
+ from typing import Dict
23
+ from typing import List
24
+ from typing import NoReturn
25
+ from typing import Optional
26
+ from typing import Sequence
27
+ from typing import Tuple
28
+ from typing import Type
29
+ from typing import TYPE_CHECKING
30
+ from typing import TypeVar
31
+ from typing import Union
32
+ import weakref
33
+
34
+ from . import attributes
35
+ from . import util as orm_util
36
+ from .base import _DeclarativeMapped
37
+ from .base import LoaderCallableStatus
38
+ from .base import Mapped
39
+ from .base import PassiveFlag
40
+ from .base import SQLORMOperations
41
+ from .interfaces import _AttributeOptions
42
+ from .interfaces import _IntrospectsAnnotations
43
+ from .interfaces import _MapsColumns
44
+ from .interfaces import MapperProperty
45
+ from .interfaces import PropComparator
46
+ from .util import _none_set
47
+ from .util import de_stringify_annotation
48
+ from .. import event
49
+ from .. import exc as sa_exc
50
+ from .. import schema
51
+ from .. import sql
52
+ from .. import util
53
+ from ..sql import expression
54
+ from ..sql import operators
55
+ from ..sql.elements import BindParameter
56
+ from ..util.typing import get_args
57
+ from ..util.typing import is_fwd_ref
58
+ from ..util.typing import is_pep593
59
+
60
+
61
+ if typing.TYPE_CHECKING:
62
+ from ._typing import _InstanceDict
63
+ from ._typing import _RegistryType
64
+ from .attributes import History
65
+ from .attributes import InstrumentedAttribute
66
+ from .attributes import QueryableAttribute
67
+ from .context import ORMCompileState
68
+ from .decl_base import _ClassScanMapperConfig
69
+ from .mapper import Mapper
70
+ from .properties import ColumnProperty
71
+ from .properties import MappedColumn
72
+ from .state import InstanceState
73
+ from ..engine.base import Connection
74
+ from ..engine.row import Row
75
+ from ..sql._typing import _DMLColumnArgument
76
+ from ..sql._typing import _InfoType
77
+ from ..sql.elements import ClauseList
78
+ from ..sql.elements import ColumnElement
79
+ from ..sql.operators import OperatorType
80
+ from ..sql.schema import Column
81
+ from ..sql.selectable import Select
82
+ from ..util.typing import _AnnotationScanType
83
+ from ..util.typing import CallableReference
84
+ from ..util.typing import DescriptorReference
85
+ from ..util.typing import RODescriptorReference
86
+
87
+ _T = TypeVar("_T", bound=Any)
88
+ _PT = TypeVar("_PT", bound=Any)
89
+
90
+
91
+ class DescriptorProperty(MapperProperty[_T]):
92
+ """:class:`.MapperProperty` which proxies access to a
93
+ user-defined descriptor."""
94
+
95
+ doc: Optional[str] = None
96
+
97
+ uses_objects = False
98
+ _links_to_entity = False
99
+
100
+ descriptor: DescriptorReference[Any]
101
+
102
+ def _column_strategy_attrs(self) -> Sequence[QueryableAttribute[Any]]:
103
+ raise NotImplementedError(
104
+ "This MapperProperty does not implement column loader strategies"
105
+ )
106
+
107
+ def get_history(
108
+ self,
109
+ state: InstanceState[Any],
110
+ dict_: _InstanceDict,
111
+ passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
112
+ ) -> History:
113
+ raise NotImplementedError()
114
+
115
+ def instrument_class(self, mapper: Mapper[Any]) -> None:
116
+ prop = self
117
+
118
+ class _ProxyImpl(attributes.AttributeImpl):
119
+ accepts_scalar_loader = False
120
+ load_on_unexpire = True
121
+ collection = False
122
+
123
+ @property
124
+ def uses_objects(self) -> bool: # type: ignore
125
+ return prop.uses_objects
126
+
127
+ def __init__(self, key: str):
128
+ self.key = key
129
+
130
+ def get_history(
131
+ self,
132
+ state: InstanceState[Any],
133
+ dict_: _InstanceDict,
134
+ passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
135
+ ) -> History:
136
+ return prop.get_history(state, dict_, passive)
137
+
138
+ if self.descriptor is None:
139
+ desc = getattr(mapper.class_, self.key, None)
140
+ if mapper._is_userland_descriptor(self.key, desc):
141
+ self.descriptor = desc
142
+
143
+ if self.descriptor is None:
144
+
145
+ def fset(obj: Any, value: Any) -> None:
146
+ setattr(obj, self.name, value)
147
+
148
+ def fdel(obj: Any) -> None:
149
+ delattr(obj, self.name)
150
+
151
+ def fget(obj: Any) -> Any:
152
+ return getattr(obj, self.name)
153
+
154
+ self.descriptor = property(fget=fget, fset=fset, fdel=fdel)
155
+
156
+ proxy_attr = attributes.create_proxied_attribute(self.descriptor)(
157
+ self.parent.class_,
158
+ self.key,
159
+ self.descriptor,
160
+ lambda: self._comparator_factory(mapper),
161
+ doc=self.doc,
162
+ original_property=self,
163
+ )
164
+ proxy_attr.impl = _ProxyImpl(self.key)
165
+ mapper.class_manager.instrument_attribute(self.key, proxy_attr)
166
+
167
+
168
+ _CompositeAttrType = Union[
169
+ str,
170
+ "Column[_T]",
171
+ "MappedColumn[_T]",
172
+ "InstrumentedAttribute[_T]",
173
+ "Mapped[_T]",
174
+ ]
175
+
176
+
177
+ _CC = TypeVar("_CC", bound=Any)
178
+
179
+
180
+ _composite_getters: weakref.WeakKeyDictionary[
181
+ Type[Any], Callable[[Any], Tuple[Any, ...]]
182
+ ] = weakref.WeakKeyDictionary()
183
+
184
+
185
+ class CompositeProperty(
186
+ _MapsColumns[_CC], _IntrospectsAnnotations, DescriptorProperty[_CC]
187
+ ):
188
+ """Defines a "composite" mapped attribute, representing a collection
189
+ of columns as one attribute.
190
+
191
+ :class:`.CompositeProperty` is constructed using the :func:`.composite`
192
+ function.
193
+
194
+ .. seealso::
195
+
196
+ :ref:`mapper_composite`
197
+
198
+ """
199
+
200
+ composite_class: Union[Type[_CC], Callable[..., _CC]]
201
+ attrs: Tuple[_CompositeAttrType[Any], ...]
202
+
203
+ _generated_composite_accessor: CallableReference[
204
+ Optional[Callable[[_CC], Tuple[Any, ...]]]
205
+ ]
206
+
207
+ comparator_factory: Type[Comparator[_CC]]
208
+
209
+ def __init__(
210
+ self,
211
+ _class_or_attr: Union[
212
+ None, Type[_CC], Callable[..., _CC], _CompositeAttrType[Any]
213
+ ] = None,
214
+ *attrs: _CompositeAttrType[Any],
215
+ attribute_options: Optional[_AttributeOptions] = None,
216
+ active_history: bool = False,
217
+ deferred: bool = False,
218
+ group: Optional[str] = None,
219
+ comparator_factory: Optional[Type[Comparator[_CC]]] = None,
220
+ info: Optional[_InfoType] = None,
221
+ **kwargs: Any,
222
+ ):
223
+ super().__init__(attribute_options=attribute_options)
224
+
225
+ if isinstance(_class_or_attr, (Mapped, str, sql.ColumnElement)):
226
+ self.attrs = (_class_or_attr,) + attrs
227
+ # will initialize within declarative_scan
228
+ self.composite_class = None # type: ignore
229
+ else:
230
+ self.composite_class = _class_or_attr # type: ignore
231
+ self.attrs = attrs
232
+
233
+ self.active_history = active_history
234
+ self.deferred = deferred
235
+ self.group = group
236
+ self.comparator_factory = (
237
+ comparator_factory
238
+ if comparator_factory is not None
239
+ else self.__class__.Comparator
240
+ )
241
+ self._generated_composite_accessor = None
242
+ if info is not None:
243
+ self.info.update(info)
244
+
245
+ util.set_creation_order(self)
246
+ self._create_descriptor()
247
+ self._init_accessor()
248
+
249
+ def instrument_class(self, mapper: Mapper[Any]) -> None:
250
+ super().instrument_class(mapper)
251
+ self._setup_event_handlers()
252
+
253
+ def _composite_values_from_instance(self, value: _CC) -> Tuple[Any, ...]:
254
+ if self._generated_composite_accessor:
255
+ return self._generated_composite_accessor(value)
256
+ else:
257
+ try:
258
+ accessor = value.__composite_values__
259
+ except AttributeError as ae:
260
+ raise sa_exc.InvalidRequestError(
261
+ f"Composite class {self.composite_class.__name__} is not "
262
+ f"a dataclass and does not define a __composite_values__()"
263
+ " method; can't get state"
264
+ ) from ae
265
+ else:
266
+ return accessor() # type: ignore
267
+
268
+ def do_init(self) -> None:
269
+ """Initialization which occurs after the :class:`.Composite`
270
+ has been associated with its parent mapper.
271
+
272
+ """
273
+ self._setup_arguments_on_columns()
274
+
275
+ _COMPOSITE_FGET = object()
276
+
277
+ def _create_descriptor(self) -> None:
278
+ """Create the Python descriptor that will serve as
279
+ the access point on instances of the mapped class.
280
+
281
+ """
282
+
283
+ def fget(instance: Any) -> Any:
284
+ dict_ = attributes.instance_dict(instance)
285
+ state = attributes.instance_state(instance)
286
+
287
+ if self.key not in dict_:
288
+ # key not present. Iterate through related
289
+ # attributes, retrieve their values. This
290
+ # ensures they all load.
291
+ values = [
292
+ getattr(instance, key) for key in self._attribute_keys
293
+ ]
294
+
295
+ # current expected behavior here is that the composite is
296
+ # created on access if the object is persistent or if
297
+ # col attributes have non-None. This would be better
298
+ # if the composite were created unconditionally,
299
+ # but that would be a behavioral change.
300
+ if self.key not in dict_ and (
301
+ state.key is not None or not _none_set.issuperset(values)
302
+ ):
303
+ dict_[self.key] = self.composite_class(*values)
304
+ state.manager.dispatch.refresh(
305
+ state, self._COMPOSITE_FGET, [self.key]
306
+ )
307
+
308
+ return dict_.get(self.key, None)
309
+
310
+ def fset(instance: Any, value: Any) -> None:
311
+ dict_ = attributes.instance_dict(instance)
312
+ state = attributes.instance_state(instance)
313
+ attr = state.manager[self.key]
314
+
315
+ if attr.dispatch._active_history:
316
+ previous = fget(instance)
317
+ else:
318
+ previous = dict_.get(self.key, LoaderCallableStatus.NO_VALUE)
319
+
320
+ for fn in attr.dispatch.set:
321
+ value = fn(state, value, previous, attr.impl)
322
+ dict_[self.key] = value
323
+ if value is None:
324
+ for key in self._attribute_keys:
325
+ setattr(instance, key, None)
326
+ else:
327
+ for key, value in zip(
328
+ self._attribute_keys,
329
+ self._composite_values_from_instance(value),
330
+ ):
331
+ setattr(instance, key, value)
332
+
333
+ def fdel(instance: Any) -> None:
334
+ state = attributes.instance_state(instance)
335
+ dict_ = attributes.instance_dict(instance)
336
+ attr = state.manager[self.key]
337
+
338
+ if attr.dispatch._active_history:
339
+ previous = fget(instance)
340
+ dict_.pop(self.key, None)
341
+ else:
342
+ previous = dict_.pop(self.key, LoaderCallableStatus.NO_VALUE)
343
+
344
+ attr = state.manager[self.key]
345
+ attr.dispatch.remove(state, previous, attr.impl)
346
+ for key in self._attribute_keys:
347
+ setattr(instance, key, None)
348
+
349
+ self.descriptor = property(fget, fset, fdel)
350
+
351
+ @util.preload_module("sqlalchemy.orm.properties")
352
+ def declarative_scan(
353
+ self,
354
+ decl_scan: _ClassScanMapperConfig,
355
+ registry: _RegistryType,
356
+ cls: Type[Any],
357
+ originating_module: Optional[str],
358
+ key: str,
359
+ mapped_container: Optional[Type[Mapped[Any]]],
360
+ annotation: Optional[_AnnotationScanType],
361
+ extracted_mapped_annotation: Optional[_AnnotationScanType],
362
+ is_dataclass_field: bool,
363
+ ) -> None:
364
+ MappedColumn = util.preloaded.orm_properties.MappedColumn
365
+ if (
366
+ self.composite_class is None
367
+ and extracted_mapped_annotation is None
368
+ ):
369
+ self._raise_for_required(key, cls)
370
+ argument = extracted_mapped_annotation
371
+
372
+ if is_pep593(argument):
373
+ argument = get_args(argument)[0]
374
+
375
+ if argument and self.composite_class is None:
376
+ if isinstance(argument, str) or is_fwd_ref(
377
+ argument, check_generic=True
378
+ ):
379
+ if originating_module is None:
380
+ str_arg = (
381
+ argument.__forward_arg__
382
+ if hasattr(argument, "__forward_arg__")
383
+ else str(argument)
384
+ )
385
+ raise sa_exc.ArgumentError(
386
+ f"Can't use forward ref {argument} for composite "
387
+ f"class argument; set up the type as Mapped[{str_arg}]"
388
+ )
389
+ argument = de_stringify_annotation(
390
+ cls, argument, originating_module, include_generic=True
391
+ )
392
+
393
+ self.composite_class = argument
394
+
395
+ if is_dataclass(self.composite_class):
396
+ self._setup_for_dataclass(
397
+ decl_scan, registry, cls, originating_module, key
398
+ )
399
+ else:
400
+ for attr in self.attrs:
401
+ if (
402
+ isinstance(attr, (MappedColumn, schema.Column))
403
+ and attr.name is None
404
+ ):
405
+ raise sa_exc.ArgumentError(
406
+ "Composite class column arguments must be named "
407
+ "unless a dataclass is used"
408
+ )
409
+ self._init_accessor()
410
+
411
+ def _init_accessor(self) -> None:
412
+ if is_dataclass(self.composite_class) and not hasattr(
413
+ self.composite_class, "__composite_values__"
414
+ ):
415
+ insp = inspect.signature(self.composite_class)
416
+ getter = operator.attrgetter(
417
+ *[p.name for p in insp.parameters.values()]
418
+ )
419
+ if len(insp.parameters) == 1:
420
+ self._generated_composite_accessor = lambda obj: (getter(obj),)
421
+ else:
422
+ self._generated_composite_accessor = getter
423
+
424
+ if (
425
+ self.composite_class is not None
426
+ and isinstance(self.composite_class, type)
427
+ and self.composite_class not in _composite_getters
428
+ ):
429
+ if self._generated_composite_accessor is not None:
430
+ _composite_getters[self.composite_class] = (
431
+ self._generated_composite_accessor
432
+ )
433
+ elif hasattr(self.composite_class, "__composite_values__"):
434
+ _composite_getters[self.composite_class] = (
435
+ lambda obj: obj.__composite_values__()
436
+ )
437
+
438
+ @util.preload_module("sqlalchemy.orm.properties")
439
+ @util.preload_module("sqlalchemy.orm.decl_base")
440
+ def _setup_for_dataclass(
441
+ self,
442
+ decl_scan: _ClassScanMapperConfig,
443
+ registry: _RegistryType,
444
+ cls: Type[Any],
445
+ originating_module: Optional[str],
446
+ key: str,
447
+ ) -> None:
448
+ MappedColumn = util.preloaded.orm_properties.MappedColumn
449
+
450
+ decl_base = util.preloaded.orm_decl_base
451
+
452
+ insp = inspect.signature(self.composite_class)
453
+ for param, attr in itertools.zip_longest(
454
+ insp.parameters.values(), self.attrs
455
+ ):
456
+ if param is None:
457
+ raise sa_exc.ArgumentError(
458
+ f"number of composite attributes "
459
+ f"{len(self.attrs)} exceeds "
460
+ f"that of the number of attributes in class "
461
+ f"{self.composite_class.__name__} {len(insp.parameters)}"
462
+ )
463
+ if attr is None:
464
+ # fill in missing attr spots with empty MappedColumn
465
+ attr = MappedColumn()
466
+ self.attrs += (attr,)
467
+
468
+ if isinstance(attr, MappedColumn):
469
+ attr.declarative_scan_for_composite(
470
+ decl_scan,
471
+ registry,
472
+ cls,
473
+ originating_module,
474
+ key,
475
+ param.name,
476
+ param.annotation,
477
+ )
478
+ elif isinstance(attr, schema.Column):
479
+ decl_base._undefer_column_name(param.name, attr)
480
+
481
+ @util.memoized_property
482
+ def _comparable_elements(self) -> Sequence[QueryableAttribute[Any]]:
483
+ return [getattr(self.parent.class_, prop.key) for prop in self.props]
484
+
485
+ @util.memoized_property
486
+ @util.preload_module("orm.properties")
487
+ def props(self) -> Sequence[MapperProperty[Any]]:
488
+ props = []
489
+ MappedColumn = util.preloaded.orm_properties.MappedColumn
490
+
491
+ for attr in self.attrs:
492
+ if isinstance(attr, str):
493
+ prop = self.parent.get_property(attr, _configure_mappers=False)
494
+ elif isinstance(attr, schema.Column):
495
+ prop = self.parent._columntoproperty[attr]
496
+ elif isinstance(attr, MappedColumn):
497
+ prop = self.parent._columntoproperty[attr.column]
498
+ elif isinstance(attr, attributes.InstrumentedAttribute):
499
+ prop = attr.property
500
+ else:
501
+ prop = None
502
+
503
+ if not isinstance(prop, MapperProperty):
504
+ raise sa_exc.ArgumentError(
505
+ "Composite expects Column objects or mapped "
506
+ f"attributes/attribute names as arguments, got: {attr!r}"
507
+ )
508
+
509
+ props.append(prop)
510
+ return props
511
+
512
+ def _column_strategy_attrs(self) -> Sequence[QueryableAttribute[Any]]:
513
+ return self._comparable_elements
514
+
515
+ @util.non_memoized_property
516
+ @util.preload_module("orm.properties")
517
+ def columns(self) -> Sequence[Column[Any]]:
518
+ MappedColumn = util.preloaded.orm_properties.MappedColumn
519
+ return [
520
+ a.column if isinstance(a, MappedColumn) else a
521
+ for a in self.attrs
522
+ if isinstance(a, (schema.Column, MappedColumn))
523
+ ]
524
+
525
+ @property
526
+ def mapper_property_to_assign(self) -> Optional[MapperProperty[_CC]]:
527
+ return self
528
+
529
+ @property
530
+ def columns_to_assign(self) -> List[Tuple[schema.Column[Any], int]]:
531
+ return [(c, 0) for c in self.columns if c.table is None]
532
+
533
+ @util.preload_module("orm.properties")
534
+ def _setup_arguments_on_columns(self) -> None:
535
+ """Propagate configuration arguments made on this composite
536
+ to the target columns, for those that apply.
537
+
538
+ """
539
+ ColumnProperty = util.preloaded.orm_properties.ColumnProperty
540
+
541
+ for prop in self.props:
542
+ if not isinstance(prop, ColumnProperty):
543
+ continue
544
+ else:
545
+ cprop = prop
546
+
547
+ cprop.active_history = self.active_history
548
+ if self.deferred:
549
+ cprop.deferred = self.deferred
550
+ cprop.strategy_key = (("deferred", True), ("instrument", True))
551
+ cprop.group = self.group
552
+
553
+ def _setup_event_handlers(self) -> None:
554
+ """Establish events that populate/expire the composite attribute."""
555
+
556
+ def load_handler(
557
+ state: InstanceState[Any], context: ORMCompileState
558
+ ) -> None:
559
+ _load_refresh_handler(state, context, None, is_refresh=False)
560
+
561
+ def refresh_handler(
562
+ state: InstanceState[Any],
563
+ context: ORMCompileState,
564
+ to_load: Optional[Sequence[str]],
565
+ ) -> None:
566
+ # note this corresponds to sqlalchemy.ext.mutable load_attrs()
567
+
568
+ if not to_load or (
569
+ {self.key}.union(self._attribute_keys)
570
+ ).intersection(to_load):
571
+ _load_refresh_handler(state, context, to_load, is_refresh=True)
572
+
573
+ def _load_refresh_handler(
574
+ state: InstanceState[Any],
575
+ context: ORMCompileState,
576
+ to_load: Optional[Sequence[str]],
577
+ is_refresh: bool,
578
+ ) -> None:
579
+ dict_ = state.dict
580
+
581
+ # if context indicates we are coming from the
582
+ # fget() handler, this already set the value; skip the
583
+ # handler here. (other handlers like mutablecomposite will still
584
+ # want to catch it)
585
+ # there's an insufficiency here in that the fget() handler
586
+ # really should not be using the refresh event and there should
587
+ # be some other event that mutablecomposite can subscribe
588
+ # towards for this.
589
+
590
+ if (
591
+ not is_refresh or context is self._COMPOSITE_FGET
592
+ ) and self.key in dict_:
593
+ return
594
+
595
+ # if column elements aren't loaded, skip.
596
+ # __get__() will initiate a load for those
597
+ # columns
598
+ for k in self._attribute_keys:
599
+ if k not in dict_:
600
+ return
601
+
602
+ dict_[self.key] = self.composite_class(
603
+ *[state.dict[key] for key in self._attribute_keys]
604
+ )
605
+
606
+ def expire_handler(
607
+ state: InstanceState[Any], keys: Optional[Sequence[str]]
608
+ ) -> None:
609
+ if keys is None or set(self._attribute_keys).intersection(keys):
610
+ state.dict.pop(self.key, None)
611
+
612
+ def insert_update_handler(
613
+ mapper: Mapper[Any],
614
+ connection: Connection,
615
+ state: InstanceState[Any],
616
+ ) -> None:
617
+ """After an insert or update, some columns may be expired due
618
+ to server side defaults, or re-populated due to client side
619
+ defaults. Pop out the composite value here so that it
620
+ recreates.
621
+
622
+ """
623
+
624
+ state.dict.pop(self.key, None)
625
+
626
+ event.listen(
627
+ self.parent, "after_insert", insert_update_handler, raw=True
628
+ )
629
+ event.listen(
630
+ self.parent, "after_update", insert_update_handler, raw=True
631
+ )
632
+ event.listen(
633
+ self.parent, "load", load_handler, raw=True, propagate=True
634
+ )
635
+ event.listen(
636
+ self.parent, "refresh", refresh_handler, raw=True, propagate=True
637
+ )
638
+ event.listen(
639
+ self.parent, "expire", expire_handler, raw=True, propagate=True
640
+ )
641
+
642
+ proxy_attr = self.parent.class_manager[self.key]
643
+ proxy_attr.impl.dispatch = proxy_attr.dispatch # type: ignore
644
+ proxy_attr.impl.dispatch._active_history = self.active_history
645
+
646
+ # TODO: need a deserialize hook here
647
+
648
+ @util.memoized_property
649
+ def _attribute_keys(self) -> Sequence[str]:
650
+ return [prop.key for prop in self.props]
651
+
652
+ def _populate_composite_bulk_save_mappings_fn(
653
+ self,
654
+ ) -> Callable[[Dict[str, Any]], None]:
655
+ if self._generated_composite_accessor:
656
+ get_values = self._generated_composite_accessor
657
+ else:
658
+
659
+ def get_values(val: Any) -> Tuple[Any]:
660
+ return val.__composite_values__() # type: ignore
661
+
662
+ attrs = [prop.key for prop in self.props]
663
+
664
+ def populate(dest_dict: Dict[str, Any]) -> None:
665
+ dest_dict.update(
666
+ {
667
+ key: val
668
+ for key, val in zip(
669
+ attrs, get_values(dest_dict.pop(self.key))
670
+ )
671
+ }
672
+ )
673
+
674
+ return populate
675
+
676
+ def get_history(
677
+ self,
678
+ state: InstanceState[Any],
679
+ dict_: _InstanceDict,
680
+ passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
681
+ ) -> History:
682
+ """Provided for userland code that uses attributes.get_history()."""
683
+
684
+ added: List[Any] = []
685
+ deleted: List[Any] = []
686
+
687
+ has_history = False
688
+ for prop in self.props:
689
+ key = prop.key
690
+ hist = state.manager[key].impl.get_history(state, dict_)
691
+ if hist.has_changes():
692
+ has_history = True
693
+
694
+ non_deleted = hist.non_deleted()
695
+ if non_deleted:
696
+ added.extend(non_deleted)
697
+ else:
698
+ added.append(None)
699
+ if hist.deleted:
700
+ deleted.extend(hist.deleted)
701
+ else:
702
+ deleted.append(None)
703
+
704
+ if has_history:
705
+ return attributes.History(
706
+ [self.composite_class(*added)],
707
+ (),
708
+ [self.composite_class(*deleted)],
709
+ )
710
+ else:
711
+ return attributes.History((), [self.composite_class(*added)], ())
712
+
713
+ def _comparator_factory(
714
+ self, mapper: Mapper[Any]
715
+ ) -> Composite.Comparator[_CC]:
716
+ return self.comparator_factory(self, mapper)
717
+
718
+ class CompositeBundle(orm_util.Bundle[_T]):
719
+ def __init__(
720
+ self,
721
+ property_: Composite[_T],
722
+ expr: ClauseList,
723
+ ):
724
+ self.property = property_
725
+ super().__init__(property_.key, *expr)
726
+
727
+ def create_row_processor(
728
+ self,
729
+ query: Select[Any],
730
+ procs: Sequence[Callable[[Row[Any]], Any]],
731
+ labels: Sequence[str],
732
+ ) -> Callable[[Row[Any]], Any]:
733
+ def proc(row: Row[Any]) -> Any:
734
+ return self.property.composite_class(
735
+ *[proc(row) for proc in procs]
736
+ )
737
+
738
+ return proc
739
+
740
+ class Comparator(PropComparator[_PT]):
741
+ """Produce boolean, comparison, and other operators for
742
+ :class:`.Composite` attributes.
743
+
744
+ See the example in :ref:`composite_operations` for an overview
745
+ of usage , as well as the documentation for :class:`.PropComparator`.
746
+
747
+ .. seealso::
748
+
749
+ :class:`.PropComparator`
750
+
751
+ :class:`.ColumnOperators`
752
+
753
+ :ref:`types_operators`
754
+
755
+ :attr:`.TypeEngine.comparator_factory`
756
+
757
+ """
758
+
759
+ # https://github.com/python/mypy/issues/4266
760
+ __hash__ = None # type: ignore
761
+
762
+ prop: RODescriptorReference[Composite[_PT]]
763
+
764
+ @util.memoized_property
765
+ def clauses(self) -> ClauseList:
766
+ return expression.ClauseList(
767
+ group=False, *self._comparable_elements
768
+ )
769
+
770
+ def __clause_element__(self) -> CompositeProperty.CompositeBundle[_PT]:
771
+ return self.expression
772
+
773
+ @util.memoized_property
774
+ def expression(self) -> CompositeProperty.CompositeBundle[_PT]:
775
+ clauses = self.clauses._annotate(
776
+ {
777
+ "parententity": self._parententity,
778
+ "parentmapper": self._parententity,
779
+ "proxy_key": self.prop.key,
780
+ }
781
+ )
782
+ return CompositeProperty.CompositeBundle(self.prop, clauses)
783
+
784
+ def _bulk_update_tuples(
785
+ self, value: Any
786
+ ) -> Sequence[Tuple[_DMLColumnArgument, Any]]:
787
+ if isinstance(value, BindParameter):
788
+ value = value.value
789
+
790
+ values: Sequence[Any]
791
+
792
+ if value is None:
793
+ values = [None for key in self.prop._attribute_keys]
794
+ elif isinstance(self.prop.composite_class, type) and isinstance(
795
+ value, self.prop.composite_class
796
+ ):
797
+ values = self.prop._composite_values_from_instance(
798
+ value # type: ignore[arg-type]
799
+ )
800
+ else:
801
+ raise sa_exc.ArgumentError(
802
+ "Can't UPDATE composite attribute %s to %r"
803
+ % (self.prop, value)
804
+ )
805
+
806
+ return list(zip(self._comparable_elements, values))
807
+
808
+ @util.memoized_property
809
+ def _comparable_elements(self) -> Sequence[QueryableAttribute[Any]]:
810
+ if self._adapt_to_entity:
811
+ return [
812
+ getattr(self._adapt_to_entity.entity, prop.key)
813
+ for prop in self.prop._comparable_elements
814
+ ]
815
+ else:
816
+ return self.prop._comparable_elements
817
+
818
+ def __eq__(self, other: Any) -> ColumnElement[bool]: # type: ignore[override] # noqa: E501
819
+ return self._compare(operators.eq, other)
820
+
821
+ def __ne__(self, other: Any) -> ColumnElement[bool]: # type: ignore[override] # noqa: E501
822
+ return self._compare(operators.ne, other)
823
+
824
+ def __lt__(self, other: Any) -> ColumnElement[bool]:
825
+ return self._compare(operators.lt, other)
826
+
827
+ def __gt__(self, other: Any) -> ColumnElement[bool]:
828
+ return self._compare(operators.gt, other)
829
+
830
+ def __le__(self, other: Any) -> ColumnElement[bool]:
831
+ return self._compare(operators.le, other)
832
+
833
+ def __ge__(self, other: Any) -> ColumnElement[bool]:
834
+ return self._compare(operators.ge, other)
835
+
836
+ # what might be interesting would be if we create
837
+ # an instance of the composite class itself with
838
+ # the columns as data members, then use "hybrid style" comparison
839
+ # to create these comparisons. then your Point.__eq__() method could
840
+ # be where comparison behavior is defined for SQL also. Likely
841
+ # not a good choice for default behavior though, not clear how it would
842
+ # work w/ dataclasses, etc. also no demand for any of this anyway.
843
+ def _compare(
844
+ self, operator: OperatorType, other: Any
845
+ ) -> ColumnElement[bool]:
846
+ values: Sequence[Any]
847
+ if other is None:
848
+ values = [None] * len(self.prop._comparable_elements)
849
+ else:
850
+ values = self.prop._composite_values_from_instance(other)
851
+ comparisons = [
852
+ operator(a, b)
853
+ for a, b in zip(self.prop._comparable_elements, values)
854
+ ]
855
+ if self._adapt_to_entity:
856
+ assert self.adapter is not None
857
+ comparisons = [self.adapter(x) for x in comparisons]
858
+ return sql.and_(*comparisons)
859
+
860
+ def __str__(self) -> str:
861
+ return str(self.parent.class_.__name__) + "." + self.key
862
+
863
+
864
+ class Composite(CompositeProperty[_T], _DeclarativeMapped[_T]):
865
+ """Declarative-compatible front-end for the :class:`.CompositeProperty`
866
+ class.
867
+
868
+ Public constructor is the :func:`_orm.composite` function.
869
+
870
+ .. versionchanged:: 2.0 Added :class:`_orm.Composite` as a Declarative
871
+ compatible subclass of :class:`_orm.CompositeProperty`.
872
+
873
+ .. seealso::
874
+
875
+ :ref:`mapper_composite`
876
+
877
+ """
878
+
879
+ inherit_cache = True
880
+ """:meta private:"""
881
+
882
+
883
+ class ConcreteInheritedProperty(DescriptorProperty[_T]):
884
+ """A 'do nothing' :class:`.MapperProperty` that disables
885
+ an attribute on a concrete subclass that is only present
886
+ on the inherited mapper, not the concrete classes' mapper.
887
+
888
+ Cases where this occurs include:
889
+
890
+ * When the superclass mapper is mapped against a
891
+ "polymorphic union", which includes all attributes from
892
+ all subclasses.
893
+ * When a relationship() is configured on an inherited mapper,
894
+ but not on the subclass mapper. Concrete mappers require
895
+ that relationship() is configured explicitly on each
896
+ subclass.
897
+
898
+ """
899
+
900
+ def _comparator_factory(
901
+ self, mapper: Mapper[Any]
902
+ ) -> Type[PropComparator[_T]]:
903
+ comparator_callable = None
904
+
905
+ for m in self.parent.iterate_to_root():
906
+ p = m._props[self.key]
907
+ if getattr(p, "comparator_factory", None) is not None:
908
+ comparator_callable = p.comparator_factory
909
+ break
910
+ assert comparator_callable is not None
911
+ return comparator_callable(p, mapper) # type: ignore
912
+
913
+ def __init__(self) -> None:
914
+ super().__init__()
915
+
916
+ def warn() -> NoReturn:
917
+ raise AttributeError(
918
+ "Concrete %s does not implement "
919
+ "attribute %r at the instance level. Add "
920
+ "this property explicitly to %s."
921
+ % (self.parent, self.key, self.parent)
922
+ )
923
+
924
+ class NoninheritedConcreteProp:
925
+ def __set__(s: Any, obj: Any, value: Any) -> NoReturn:
926
+ warn()
927
+
928
+ def __delete__(s: Any, obj: Any) -> NoReturn:
929
+ warn()
930
+
931
+ def __get__(s: Any, obj: Any, owner: Any) -> Any:
932
+ if obj is None:
933
+ return self.descriptor
934
+ warn()
935
+
936
+ self.descriptor = NoninheritedConcreteProp()
937
+
938
+
939
+ class SynonymProperty(DescriptorProperty[_T]):
940
+ """Denote an attribute name as a synonym to a mapped property,
941
+ in that the attribute will mirror the value and expression behavior
942
+ of another attribute.
943
+
944
+ :class:`.Synonym` is constructed using the :func:`_orm.synonym`
945
+ function.
946
+
947
+ .. seealso::
948
+
949
+ :ref:`synonyms` - Overview of synonyms
950
+
951
+ """
952
+
953
+ comparator_factory: Optional[Type[PropComparator[_T]]]
954
+
955
+ def __init__(
956
+ self,
957
+ name: str,
958
+ map_column: Optional[bool] = None,
959
+ descriptor: Optional[Any] = None,
960
+ comparator_factory: Optional[Type[PropComparator[_T]]] = None,
961
+ attribute_options: Optional[_AttributeOptions] = None,
962
+ info: Optional[_InfoType] = None,
963
+ doc: Optional[str] = None,
964
+ ):
965
+ super().__init__(attribute_options=attribute_options)
966
+
967
+ self.name = name
968
+ self.map_column = map_column
969
+ self.descriptor = descriptor
970
+ self.comparator_factory = comparator_factory
971
+ if doc:
972
+ self.doc = doc
973
+ elif descriptor and descriptor.__doc__:
974
+ self.doc = descriptor.__doc__
975
+ else:
976
+ self.doc = None
977
+ if info:
978
+ self.info.update(info)
979
+
980
+ util.set_creation_order(self)
981
+
982
+ if not TYPE_CHECKING:
983
+
984
+ @property
985
+ def uses_objects(self) -> bool:
986
+ return getattr(self.parent.class_, self.name).impl.uses_objects
987
+
988
+ # TODO: when initialized, check _proxied_object,
989
+ # emit a warning if its not a column-based property
990
+
991
+ @util.memoized_property
992
+ def _proxied_object(
993
+ self,
994
+ ) -> Union[MapperProperty[_T], SQLORMOperations[_T]]:
995
+ attr = getattr(self.parent.class_, self.name)
996
+ if not hasattr(attr, "property") or not isinstance(
997
+ attr.property, MapperProperty
998
+ ):
999
+ # attribute is a non-MapperProprerty proxy such as
1000
+ # hybrid or association proxy
1001
+ if isinstance(attr, attributes.QueryableAttribute):
1002
+ return attr.comparator
1003
+ elif isinstance(attr, SQLORMOperations):
1004
+ # association proxy comes here
1005
+ return attr
1006
+
1007
+ raise sa_exc.InvalidRequestError(
1008
+ """synonym() attribute "%s.%s" only supports """
1009
+ """ORM mapped attributes, got %r"""
1010
+ % (self.parent.class_.__name__, self.name, attr)
1011
+ )
1012
+ return attr.property
1013
+
1014
+ def _column_strategy_attrs(self) -> Sequence[QueryableAttribute[Any]]:
1015
+ return (getattr(self.parent.class_, self.name),)
1016
+
1017
+ def _comparator_factory(self, mapper: Mapper[Any]) -> SQLORMOperations[_T]:
1018
+ prop = self._proxied_object
1019
+
1020
+ if isinstance(prop, MapperProperty):
1021
+ if self.comparator_factory:
1022
+ comp = self.comparator_factory(prop, mapper)
1023
+ else:
1024
+ comp = prop.comparator_factory(prop, mapper)
1025
+ return comp
1026
+ else:
1027
+ return prop
1028
+
1029
+ def get_history(
1030
+ self,
1031
+ state: InstanceState[Any],
1032
+ dict_: _InstanceDict,
1033
+ passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
1034
+ ) -> History:
1035
+ attr: QueryableAttribute[Any] = getattr(self.parent.class_, self.name)
1036
+ return attr.impl.get_history(state, dict_, passive=passive)
1037
+
1038
+ @util.preload_module("sqlalchemy.orm.properties")
1039
+ def set_parent(self, parent: Mapper[Any], init: bool) -> None:
1040
+ properties = util.preloaded.orm_properties
1041
+
1042
+ if self.map_column:
1043
+ # implement the 'map_column' option.
1044
+ if self.key not in parent.persist_selectable.c:
1045
+ raise sa_exc.ArgumentError(
1046
+ "Can't compile synonym '%s': no column on table "
1047
+ "'%s' named '%s'"
1048
+ % (
1049
+ self.name,
1050
+ parent.persist_selectable.description,
1051
+ self.key,
1052
+ )
1053
+ )
1054
+ elif (
1055
+ parent.persist_selectable.c[self.key]
1056
+ in parent._columntoproperty
1057
+ and parent._columntoproperty[
1058
+ parent.persist_selectable.c[self.key]
1059
+ ].key
1060
+ == self.name
1061
+ ):
1062
+ raise sa_exc.ArgumentError(
1063
+ "Can't call map_column=True for synonym %r=%r, "
1064
+ "a ColumnProperty already exists keyed to the name "
1065
+ "%r for column %r"
1066
+ % (self.key, self.name, self.name, self.key)
1067
+ )
1068
+ p: ColumnProperty[Any] = properties.ColumnProperty(
1069
+ parent.persist_selectable.c[self.key]
1070
+ )
1071
+ parent._configure_property(self.name, p, init=init, setparent=True)
1072
+ p._mapped_by_synonym = self.key
1073
+
1074
+ self.parent = parent
1075
+
1076
+
1077
+ class Synonym(SynonymProperty[_T], _DeclarativeMapped[_T]):
1078
+ """Declarative front-end for the :class:`.SynonymProperty` class.
1079
+
1080
+ Public constructor is the :func:`_orm.synonym` function.
1081
+
1082
+ .. versionchanged:: 2.0 Added :class:`_orm.Synonym` as a Declarative
1083
+ compatible subclass for :class:`_orm.SynonymProperty`
1084
+
1085
+ .. seealso::
1086
+
1087
+ :ref:`synonyms` - Overview of synonyms
1088
+
1089
+ """
1090
+
1091
+ inherit_cache = True
1092
+ """:meta private:"""