SQLAlchemy 2.1.0b2__cp313-cp313t-win_arm64.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 (270) hide show
  1. sqlalchemy/__init__.py +298 -0
  2. sqlalchemy/connectors/__init__.py +18 -0
  3. sqlalchemy/connectors/aioodbc.py +171 -0
  4. sqlalchemy/connectors/asyncio.py +476 -0
  5. sqlalchemy/connectors/pyodbc.py +250 -0
  6. sqlalchemy/dialects/__init__.py +62 -0
  7. sqlalchemy/dialects/_typing.py +30 -0
  8. sqlalchemy/dialects/mssql/__init__.py +89 -0
  9. sqlalchemy/dialects/mssql/aioodbc.py +63 -0
  10. sqlalchemy/dialects/mssql/base.py +4166 -0
  11. sqlalchemy/dialects/mssql/information_schema.py +285 -0
  12. sqlalchemy/dialects/mssql/json.py +140 -0
  13. sqlalchemy/dialects/mssql/mssqlpython.py +220 -0
  14. sqlalchemy/dialects/mssql/provision.py +196 -0
  15. sqlalchemy/dialects/mssql/pymssql.py +126 -0
  16. sqlalchemy/dialects/mssql/pyodbc.py +698 -0
  17. sqlalchemy/dialects/mysql/__init__.py +106 -0
  18. sqlalchemy/dialects/mysql/_mariadb_shim.py +312 -0
  19. sqlalchemy/dialects/mysql/aiomysql.py +226 -0
  20. sqlalchemy/dialects/mysql/asyncmy.py +214 -0
  21. sqlalchemy/dialects/mysql/base.py +3877 -0
  22. sqlalchemy/dialects/mysql/cymysql.py +106 -0
  23. sqlalchemy/dialects/mysql/dml.py +279 -0
  24. sqlalchemy/dialects/mysql/enumerated.py +277 -0
  25. sqlalchemy/dialects/mysql/expression.py +146 -0
  26. sqlalchemy/dialects/mysql/json.py +92 -0
  27. sqlalchemy/dialects/mysql/mariadb.py +67 -0
  28. sqlalchemy/dialects/mysql/mariadbconnector.py +330 -0
  29. sqlalchemy/dialects/mysql/mysqlconnector.py +296 -0
  30. sqlalchemy/dialects/mysql/mysqldb.py +312 -0
  31. sqlalchemy/dialects/mysql/provision.py +153 -0
  32. sqlalchemy/dialects/mysql/pymysql.py +157 -0
  33. sqlalchemy/dialects/mysql/pyodbc.py +156 -0
  34. sqlalchemy/dialects/mysql/reflection.py +724 -0
  35. sqlalchemy/dialects/mysql/reserved_words.py +570 -0
  36. sqlalchemy/dialects/mysql/types.py +845 -0
  37. sqlalchemy/dialects/oracle/__init__.py +85 -0
  38. sqlalchemy/dialects/oracle/base.py +3977 -0
  39. sqlalchemy/dialects/oracle/cx_oracle.py +1601 -0
  40. sqlalchemy/dialects/oracle/dictionary.py +507 -0
  41. sqlalchemy/dialects/oracle/json.py +158 -0
  42. sqlalchemy/dialects/oracle/oracledb.py +909 -0
  43. sqlalchemy/dialects/oracle/provision.py +288 -0
  44. sqlalchemy/dialects/oracle/types.py +367 -0
  45. sqlalchemy/dialects/oracle/vector.py +368 -0
  46. sqlalchemy/dialects/postgresql/__init__.py +171 -0
  47. sqlalchemy/dialects/postgresql/_psycopg_common.py +229 -0
  48. sqlalchemy/dialects/postgresql/array.py +534 -0
  49. sqlalchemy/dialects/postgresql/asyncpg.py +1323 -0
  50. sqlalchemy/dialects/postgresql/base.py +5789 -0
  51. sqlalchemy/dialects/postgresql/bitstring.py +327 -0
  52. sqlalchemy/dialects/postgresql/dml.py +360 -0
  53. sqlalchemy/dialects/postgresql/ext.py +593 -0
  54. sqlalchemy/dialects/postgresql/hstore.py +423 -0
  55. sqlalchemy/dialects/postgresql/json.py +408 -0
  56. sqlalchemy/dialects/postgresql/named_types.py +521 -0
  57. sqlalchemy/dialects/postgresql/operators.py +130 -0
  58. sqlalchemy/dialects/postgresql/pg8000.py +670 -0
  59. sqlalchemy/dialects/postgresql/pg_catalog.py +344 -0
  60. sqlalchemy/dialects/postgresql/provision.py +184 -0
  61. sqlalchemy/dialects/postgresql/psycopg.py +799 -0
  62. sqlalchemy/dialects/postgresql/psycopg2.py +860 -0
  63. sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
  64. sqlalchemy/dialects/postgresql/ranges.py +1002 -0
  65. sqlalchemy/dialects/postgresql/types.py +388 -0
  66. sqlalchemy/dialects/sqlite/__init__.py +57 -0
  67. sqlalchemy/dialects/sqlite/aiosqlite.py +321 -0
  68. sqlalchemy/dialects/sqlite/base.py +3063 -0
  69. sqlalchemy/dialects/sqlite/dml.py +279 -0
  70. sqlalchemy/dialects/sqlite/json.py +100 -0
  71. sqlalchemy/dialects/sqlite/provision.py +229 -0
  72. sqlalchemy/dialects/sqlite/pysqlcipher.py +161 -0
  73. sqlalchemy/dialects/sqlite/pysqlite.py +754 -0
  74. sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
  75. sqlalchemy/engine/__init__.py +62 -0
  76. sqlalchemy/engine/_processors_cy.cp313t-win_arm64.pyd +0 -0
  77. sqlalchemy/engine/_processors_cy.py +92 -0
  78. sqlalchemy/engine/_result_cy.cp313t-win_arm64.pyd +0 -0
  79. sqlalchemy/engine/_result_cy.py +633 -0
  80. sqlalchemy/engine/_row_cy.cp313t-win_arm64.pyd +0 -0
  81. sqlalchemy/engine/_row_cy.py +232 -0
  82. sqlalchemy/engine/_util_cy.cp313t-win_arm64.pyd +0 -0
  83. sqlalchemy/engine/_util_cy.py +136 -0
  84. sqlalchemy/engine/base.py +3354 -0
  85. sqlalchemy/engine/characteristics.py +155 -0
  86. sqlalchemy/engine/create.py +877 -0
  87. sqlalchemy/engine/cursor.py +2421 -0
  88. sqlalchemy/engine/default.py +2402 -0
  89. sqlalchemy/engine/events.py +965 -0
  90. sqlalchemy/engine/interfaces.py +3495 -0
  91. sqlalchemy/engine/mock.py +134 -0
  92. sqlalchemy/engine/processors.py +82 -0
  93. sqlalchemy/engine/reflection.py +2100 -0
  94. sqlalchemy/engine/result.py +1966 -0
  95. sqlalchemy/engine/row.py +397 -0
  96. sqlalchemy/engine/strategies.py +16 -0
  97. sqlalchemy/engine/url.py +922 -0
  98. sqlalchemy/engine/util.py +156 -0
  99. sqlalchemy/event/__init__.py +26 -0
  100. sqlalchemy/event/api.py +220 -0
  101. sqlalchemy/event/attr.py +674 -0
  102. sqlalchemy/event/base.py +472 -0
  103. sqlalchemy/event/legacy.py +258 -0
  104. sqlalchemy/event/registry.py +390 -0
  105. sqlalchemy/events.py +17 -0
  106. sqlalchemy/exc.py +922 -0
  107. sqlalchemy/ext/__init__.py +11 -0
  108. sqlalchemy/ext/associationproxy.py +2072 -0
  109. sqlalchemy/ext/asyncio/__init__.py +29 -0
  110. sqlalchemy/ext/asyncio/base.py +281 -0
  111. sqlalchemy/ext/asyncio/engine.py +1487 -0
  112. sqlalchemy/ext/asyncio/exc.py +21 -0
  113. sqlalchemy/ext/asyncio/result.py +994 -0
  114. sqlalchemy/ext/asyncio/scoping.py +1679 -0
  115. sqlalchemy/ext/asyncio/session.py +2007 -0
  116. sqlalchemy/ext/automap.py +1701 -0
  117. sqlalchemy/ext/baked.py +559 -0
  118. sqlalchemy/ext/compiler.py +600 -0
  119. sqlalchemy/ext/declarative/__init__.py +65 -0
  120. sqlalchemy/ext/declarative/extensions.py +560 -0
  121. sqlalchemy/ext/horizontal_shard.py +481 -0
  122. sqlalchemy/ext/hybrid.py +1877 -0
  123. sqlalchemy/ext/indexable.py +364 -0
  124. sqlalchemy/ext/instrumentation.py +450 -0
  125. sqlalchemy/ext/mutable.py +1081 -0
  126. sqlalchemy/ext/orderinglist.py +439 -0
  127. sqlalchemy/ext/serializer.py +185 -0
  128. sqlalchemy/future/__init__.py +16 -0
  129. sqlalchemy/future/engine.py +15 -0
  130. sqlalchemy/inspection.py +174 -0
  131. sqlalchemy/log.py +283 -0
  132. sqlalchemy/orm/__init__.py +176 -0
  133. sqlalchemy/orm/_orm_constructors.py +2694 -0
  134. sqlalchemy/orm/_typing.py +179 -0
  135. sqlalchemy/orm/attributes.py +2868 -0
  136. sqlalchemy/orm/base.py +976 -0
  137. sqlalchemy/orm/bulk_persistence.py +2152 -0
  138. sqlalchemy/orm/clsregistry.py +582 -0
  139. sqlalchemy/orm/collections.py +1568 -0
  140. sqlalchemy/orm/context.py +3471 -0
  141. sqlalchemy/orm/decl_api.py +2280 -0
  142. sqlalchemy/orm/decl_base.py +2309 -0
  143. sqlalchemy/orm/dependency.py +1306 -0
  144. sqlalchemy/orm/descriptor_props.py +1183 -0
  145. sqlalchemy/orm/dynamic.py +307 -0
  146. sqlalchemy/orm/evaluator.py +379 -0
  147. sqlalchemy/orm/events.py +3386 -0
  148. sqlalchemy/orm/exc.py +237 -0
  149. sqlalchemy/orm/identity.py +302 -0
  150. sqlalchemy/orm/instrumentation.py +746 -0
  151. sqlalchemy/orm/interfaces.py +1589 -0
  152. sqlalchemy/orm/loading.py +1684 -0
  153. sqlalchemy/orm/mapped_collection.py +557 -0
  154. sqlalchemy/orm/mapper.py +4411 -0
  155. sqlalchemy/orm/path_registry.py +829 -0
  156. sqlalchemy/orm/persistence.py +1789 -0
  157. sqlalchemy/orm/properties.py +973 -0
  158. sqlalchemy/orm/query.py +3528 -0
  159. sqlalchemy/orm/relationships.py +3570 -0
  160. sqlalchemy/orm/scoping.py +2232 -0
  161. sqlalchemy/orm/session.py +5403 -0
  162. sqlalchemy/orm/state.py +1175 -0
  163. sqlalchemy/orm/state_changes.py +196 -0
  164. sqlalchemy/orm/strategies.py +3492 -0
  165. sqlalchemy/orm/strategy_options.py +2562 -0
  166. sqlalchemy/orm/sync.py +164 -0
  167. sqlalchemy/orm/unitofwork.py +798 -0
  168. sqlalchemy/orm/util.py +2438 -0
  169. sqlalchemy/orm/writeonly.py +694 -0
  170. sqlalchemy/pool/__init__.py +41 -0
  171. sqlalchemy/pool/base.py +1522 -0
  172. sqlalchemy/pool/events.py +375 -0
  173. sqlalchemy/pool/impl.py +582 -0
  174. sqlalchemy/py.typed +0 -0
  175. sqlalchemy/schema.py +74 -0
  176. sqlalchemy/sql/__init__.py +156 -0
  177. sqlalchemy/sql/_annotated_cols.py +397 -0
  178. sqlalchemy/sql/_dml_constructors.py +132 -0
  179. sqlalchemy/sql/_elements_constructors.py +2164 -0
  180. sqlalchemy/sql/_orm_types.py +20 -0
  181. sqlalchemy/sql/_selectable_constructors.py +840 -0
  182. sqlalchemy/sql/_typing.py +487 -0
  183. sqlalchemy/sql/_util_cy.cp313t-win_arm64.pyd +0 -0
  184. sqlalchemy/sql/_util_cy.py +127 -0
  185. sqlalchemy/sql/annotation.py +590 -0
  186. sqlalchemy/sql/base.py +2699 -0
  187. sqlalchemy/sql/cache_key.py +1066 -0
  188. sqlalchemy/sql/coercions.py +1373 -0
  189. sqlalchemy/sql/compiler.py +8327 -0
  190. sqlalchemy/sql/crud.py +1815 -0
  191. sqlalchemy/sql/ddl.py +1928 -0
  192. sqlalchemy/sql/default_comparator.py +654 -0
  193. sqlalchemy/sql/dml.py +1977 -0
  194. sqlalchemy/sql/elements.py +6033 -0
  195. sqlalchemy/sql/events.py +458 -0
  196. sqlalchemy/sql/expression.py +172 -0
  197. sqlalchemy/sql/functions.py +2305 -0
  198. sqlalchemy/sql/lambdas.py +1443 -0
  199. sqlalchemy/sql/naming.py +209 -0
  200. sqlalchemy/sql/operators.py +2897 -0
  201. sqlalchemy/sql/roles.py +332 -0
  202. sqlalchemy/sql/schema.py +6703 -0
  203. sqlalchemy/sql/selectable.py +7553 -0
  204. sqlalchemy/sql/sqltypes.py +4093 -0
  205. sqlalchemy/sql/traversals.py +1042 -0
  206. sqlalchemy/sql/type_api.py +2446 -0
  207. sqlalchemy/sql/util.py +1495 -0
  208. sqlalchemy/sql/visitors.py +1157 -0
  209. sqlalchemy/testing/__init__.py +96 -0
  210. sqlalchemy/testing/assertions.py +1007 -0
  211. sqlalchemy/testing/assertsql.py +519 -0
  212. sqlalchemy/testing/asyncio.py +128 -0
  213. sqlalchemy/testing/config.py +440 -0
  214. sqlalchemy/testing/engines.py +483 -0
  215. sqlalchemy/testing/entities.py +117 -0
  216. sqlalchemy/testing/exclusions.py +476 -0
  217. sqlalchemy/testing/fixtures/__init__.py +30 -0
  218. sqlalchemy/testing/fixtures/base.py +384 -0
  219. sqlalchemy/testing/fixtures/mypy.py +247 -0
  220. sqlalchemy/testing/fixtures/orm.py +227 -0
  221. sqlalchemy/testing/fixtures/sql.py +538 -0
  222. sqlalchemy/testing/pickleable.py +155 -0
  223. sqlalchemy/testing/plugin/__init__.py +6 -0
  224. sqlalchemy/testing/plugin/bootstrap.py +51 -0
  225. sqlalchemy/testing/plugin/plugin_base.py +828 -0
  226. sqlalchemy/testing/plugin/pytestplugin.py +892 -0
  227. sqlalchemy/testing/profiling.py +329 -0
  228. sqlalchemy/testing/provision.py +613 -0
  229. sqlalchemy/testing/requirements.py +1978 -0
  230. sqlalchemy/testing/schema.py +198 -0
  231. sqlalchemy/testing/suite/__init__.py +19 -0
  232. sqlalchemy/testing/suite/test_cte.py +237 -0
  233. sqlalchemy/testing/suite/test_ddl.py +420 -0
  234. sqlalchemy/testing/suite/test_dialect.py +776 -0
  235. sqlalchemy/testing/suite/test_insert.py +630 -0
  236. sqlalchemy/testing/suite/test_reflection.py +3557 -0
  237. sqlalchemy/testing/suite/test_results.py +660 -0
  238. sqlalchemy/testing/suite/test_rowcount.py +258 -0
  239. sqlalchemy/testing/suite/test_select.py +2112 -0
  240. sqlalchemy/testing/suite/test_sequence.py +317 -0
  241. sqlalchemy/testing/suite/test_table_via_select.py +686 -0
  242. sqlalchemy/testing/suite/test_types.py +2271 -0
  243. sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
  244. sqlalchemy/testing/suite/test_update_delete.py +139 -0
  245. sqlalchemy/testing/util.py +535 -0
  246. sqlalchemy/testing/warnings.py +52 -0
  247. sqlalchemy/types.py +76 -0
  248. sqlalchemy/util/__init__.py +158 -0
  249. sqlalchemy/util/_collections.py +688 -0
  250. sqlalchemy/util/_collections_cy.cp313t-win_arm64.pyd +0 -0
  251. sqlalchemy/util/_collections_cy.pxd +8 -0
  252. sqlalchemy/util/_collections_cy.py +516 -0
  253. sqlalchemy/util/_has_cython.py +46 -0
  254. sqlalchemy/util/_immutabledict_cy.cp313t-win_arm64.pyd +0 -0
  255. sqlalchemy/util/_immutabledict_cy.py +240 -0
  256. sqlalchemy/util/compat.py +299 -0
  257. sqlalchemy/util/concurrency.py +322 -0
  258. sqlalchemy/util/cython.py +79 -0
  259. sqlalchemy/util/deprecations.py +401 -0
  260. sqlalchemy/util/langhelpers.py +2320 -0
  261. sqlalchemy/util/preloaded.py +152 -0
  262. sqlalchemy/util/queue.py +304 -0
  263. sqlalchemy/util/tool_support.py +201 -0
  264. sqlalchemy/util/topological.py +120 -0
  265. sqlalchemy/util/typing.py +711 -0
  266. sqlalchemy-2.1.0b2.dist-info/METADATA +269 -0
  267. sqlalchemy-2.1.0b2.dist-info/RECORD +270 -0
  268. sqlalchemy-2.1.0b2.dist-info/WHEEL +5 -0
  269. sqlalchemy-2.1.0b2.dist-info/licenses/LICENSE +19 -0
  270. sqlalchemy-2.1.0b2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,2309 @@
1
+ # orm/decl_base.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
+ """Internal implementation for declarative."""
9
+
10
+ from __future__ import annotations
11
+
12
+ import collections
13
+ import dataclasses
14
+ import itertools
15
+ import re
16
+ from typing import Any
17
+ from typing import Callable
18
+ from typing import cast
19
+ from typing import Dict
20
+ from typing import get_args
21
+ from typing import Iterable
22
+ from typing import List
23
+ from typing import Mapping
24
+ from typing import NamedTuple
25
+ from typing import NoReturn
26
+ from typing import Optional
27
+ from typing import Protocol
28
+ from typing import Sequence
29
+ from typing import Tuple
30
+ from typing import Type
31
+ from typing import TYPE_CHECKING
32
+ from typing import TypeVar
33
+ from typing import Union
34
+ import weakref
35
+
36
+ from . import attributes
37
+ from . import clsregistry
38
+ from . import exc as orm_exc
39
+ from . import instrumentation
40
+ from . import mapperlib
41
+ from ._typing import _O
42
+ from ._typing import attr_is_internal_proxy
43
+ from .attributes import InstrumentedAttribute
44
+ from .attributes import QueryableAttribute
45
+ from .base import _is_mapped_class
46
+ from .base import InspectionAttr
47
+ from .descriptor_props import CompositeProperty
48
+ from .descriptor_props import SynonymProperty
49
+ from .interfaces import _AttributeOptions
50
+ from .interfaces import _DataclassArguments
51
+ from .interfaces import _DCAttributeOptions
52
+ from .interfaces import _IntrospectsAnnotations
53
+ from .interfaces import _MappedAttribute
54
+ from .interfaces import _MapsColumns
55
+ from .interfaces import MapperProperty
56
+ from .mapper import Mapper
57
+ from .properties import ColumnProperty
58
+ from .properties import MappedColumn
59
+ from .util import _extract_mapped_subtype
60
+ from .util import _is_mapped_annotation
61
+ from .util import class_mapper
62
+ from .util import de_stringify_annotation
63
+ from .. import event
64
+ from .. import exc
65
+ from .. import util
66
+ from ..sql import expression
67
+ from ..sql._annotated_cols import TypedColumns
68
+ from ..sql.base import _NoArg
69
+ from ..sql.schema import Column
70
+ from ..sql.schema import Table
71
+ from ..util import topological
72
+ from ..util.typing import _AnnotationScanType
73
+ from ..util.typing import is_fwd_ref
74
+ from ..util.typing import is_literal
75
+
76
+ if TYPE_CHECKING:
77
+ from ._typing import _ClassDict
78
+ from ._typing import _RegistryType
79
+ from .base import Mapped
80
+ from .decl_api import declared_attr
81
+ from .instrumentation import ClassManager
82
+ from ..sql.elements import NamedColumn
83
+ from ..sql.schema import MetaData
84
+ from ..sql.selectable import FromClause
85
+
86
+ _T = TypeVar("_T", bound=Any)
87
+
88
+ _MapperKwArgs = Mapping[str, Any]
89
+ _TableArgsType = Union[Tuple[Any, ...], Dict[str, Any]]
90
+
91
+
92
+ class MappedClassProtocol(Protocol[_O]):
93
+ """A protocol representing a SQLAlchemy mapped class.
94
+
95
+ The protocol is generic on the type of class, use
96
+ ``MappedClassProtocol[Any]`` to allow any mapped class.
97
+ """
98
+
99
+ __name__: str
100
+ __mapper__: Mapper[_O]
101
+ __table__: FromClause
102
+
103
+ def __call__(self, **kw: Any) -> _O: ...
104
+
105
+
106
+ class _DeclMappedClassProtocol(MappedClassProtocol[_O], Protocol):
107
+ "Internal more detailed version of ``MappedClassProtocol``."
108
+
109
+ metadata: MetaData
110
+ __tablename__: str
111
+ __mapper_args__: _MapperKwArgs
112
+ __table_args__: Optional[_TableArgsType]
113
+
114
+ _sa_apply_dc_transforms: Optional[_DataclassArguments]
115
+
116
+ def __declare_first__(self) -> None: ...
117
+
118
+ def __declare_last__(self) -> None: ...
119
+
120
+
121
+ def _declared_mapping_info(
122
+ cls: Type[Any],
123
+ ) -> Optional[Union[_DeferredDeclarativeConfig, Mapper[Any]]]:
124
+ # deferred mapping
125
+ if _DeferredDeclarativeConfig.has_cls(cls):
126
+ return _DeferredDeclarativeConfig.config_for_cls(cls)
127
+ # regular mapping
128
+ elif _is_mapped_class(cls):
129
+ return class_mapper(cls, configure=False)
130
+ else:
131
+ return None
132
+
133
+
134
+ def _is_supercls_for_inherits(cls: Type[Any]) -> bool:
135
+ """return True if this class will be used as a superclass to set in
136
+ 'inherits'.
137
+
138
+ This includes deferred mapper configs that aren't mapped yet, however does
139
+ not include classes with _sa_decl_prepare_nocascade (e.g.
140
+ ``AbstractConcreteBase``); these concrete-only classes are not set up as
141
+ "inherits" until after mappers are configured using
142
+ mapper._set_concrete_base()
143
+
144
+ """
145
+ if _DeferredDeclarativeConfig.has_cls(cls):
146
+ return not _get_immediate_cls_attr(
147
+ cls, "_sa_decl_prepare_nocascade", strict=True
148
+ )
149
+ # regular mapping
150
+ elif _is_mapped_class(cls):
151
+ return True
152
+ else:
153
+ return False
154
+
155
+
156
+ def _resolve_for_abstract_or_classical(cls: Type[Any]) -> Optional[Type[Any]]:
157
+ if cls is object:
158
+ return None
159
+
160
+ sup: Optional[Type[Any]]
161
+
162
+ if cls.__dict__.get("__abstract__", False):
163
+ for base_ in cls.__bases__:
164
+ sup = _resolve_for_abstract_or_classical(base_)
165
+ if sup is not None:
166
+ return sup
167
+ else:
168
+ return None
169
+ else:
170
+ clsmanager = _dive_for_cls_manager(cls)
171
+
172
+ if clsmanager:
173
+ return clsmanager.class_
174
+ else:
175
+ return cls
176
+
177
+
178
+ def _get_immediate_cls_attr(
179
+ cls: Type[Any], attrname: str, strict: bool = False
180
+ ) -> Optional[Any]:
181
+ """return an attribute of the class that is either present directly
182
+ on the class, e.g. not on a superclass, or is from a superclass but
183
+ this superclass is a non-mapped mixin, that is, not a descendant of
184
+ the declarative base and is also not classically mapped.
185
+
186
+ This is used to detect attributes that indicate something about
187
+ a mapped class independently from any mapped classes that it may
188
+ inherit from.
189
+
190
+ """
191
+
192
+ # the rules are different for this name than others,
193
+ # make sure we've moved it out. transitional
194
+ assert attrname != "__abstract__"
195
+
196
+ if not issubclass(cls, object):
197
+ return None
198
+
199
+ if attrname in cls.__dict__:
200
+ return getattr(cls, attrname)
201
+
202
+ for base in cls.__mro__[1:]:
203
+ _is_classical_inherits = _dive_for_cls_manager(base) is not None
204
+
205
+ if attrname in base.__dict__ and (
206
+ base is cls
207
+ or (
208
+ (base in cls.__bases__ if strict else True)
209
+ and not _is_classical_inherits
210
+ )
211
+ ):
212
+ return getattr(base, attrname)
213
+ else:
214
+ return None
215
+
216
+
217
+ def _dive_for_cls_manager(cls: Type[_O]) -> Optional[ClassManager[_O]]:
218
+ # because the class manager registration is pluggable,
219
+ # we need to do the search for every class in the hierarchy,
220
+ # rather than just a simple "cls._sa_class_manager"
221
+
222
+ for base in cls.__mro__:
223
+ manager: Optional[ClassManager[_O]] = attributes.opt_manager_of_class(
224
+ base
225
+ )
226
+ if manager:
227
+ return manager
228
+ return None
229
+
230
+
231
+ @util.preload_module("sqlalchemy.orm.decl_api")
232
+ def _is_declarative_props(obj: Any) -> bool:
233
+ _declared_attr_common = util.preloaded.orm_decl_api._declared_attr_common
234
+
235
+ return isinstance(obj, (_declared_attr_common, util.classproperty))
236
+
237
+
238
+ def _check_declared_props_nocascade(
239
+ obj: Any, name: str, cls: Type[_O]
240
+ ) -> bool:
241
+ if _is_declarative_props(obj):
242
+ if getattr(obj, "_cascading", False):
243
+ util.warn(
244
+ "@declared_attr.cascading is not supported on the %s "
245
+ "attribute on class %s. This attribute invokes for "
246
+ "subclasses in any case." % (name, cls)
247
+ )
248
+ return True
249
+ else:
250
+ return False
251
+
252
+
253
+ class _ORMClassConfigurator:
254
+ """Object that configures a class that's potentially going to be
255
+ mapped, and/or turned into an ORM dataclass.
256
+
257
+ This is the base class for all the configurator objects.
258
+
259
+ """
260
+
261
+ __slots__ = ("cls", "classname", "__weakref__")
262
+
263
+ cls: Type[Any]
264
+ classname: str
265
+
266
+ def __init__(self, cls_: Type[Any]):
267
+ self.cls = util.assert_arg_type(cls_, type, "cls_")
268
+ self.classname = cls_.__name__
269
+
270
+ @classmethod
271
+ def _as_declarative(
272
+ cls, registry: _RegistryType, cls_: Type[Any], dict_: _ClassDict
273
+ ) -> Optional[_MapperConfig]:
274
+ manager = attributes.opt_manager_of_class(cls_)
275
+ if manager and manager.class_ is cls_:
276
+ raise exc.InvalidRequestError(
277
+ f"Class {cls_!r} already has been instrumented declaratively"
278
+ )
279
+
280
+ # allow subclassing an orm class with typed columns without
281
+ # generating an orm class
282
+ if cls_.__dict__.get("__abstract__", False) or issubclass(
283
+ cls_, TypedColumns
284
+ ):
285
+ return None
286
+
287
+ defer_map = _get_immediate_cls_attr(
288
+ cls_, "_sa_decl_prepare_nocascade", strict=True
289
+ ) or hasattr(cls_, "_sa_decl_prepare")
290
+
291
+ if defer_map:
292
+ return _DeferredDeclarativeConfig(registry, cls_, dict_)
293
+ else:
294
+ return _DeclarativeMapperConfig(registry, cls_, dict_)
295
+
296
+ @classmethod
297
+ def _as_unmapped_dataclass(
298
+ cls, cls_: Type[Any], dict_: _ClassDict
299
+ ) -> _UnmappedDataclassConfig:
300
+ return _UnmappedDataclassConfig(cls_, dict_)
301
+
302
+ @classmethod
303
+ def _mapper(
304
+ cls,
305
+ registry: _RegistryType,
306
+ cls_: Type[_O],
307
+ table: Optional[FromClause],
308
+ mapper_kw: _MapperKwArgs,
309
+ ) -> Mapper[_O]:
310
+ _ImperativeMapperConfig(registry, cls_, table, mapper_kw)
311
+ return cast("MappedClassProtocol[_O]", cls_).__mapper__
312
+
313
+
314
+ class _MapperConfig(_ORMClassConfigurator):
315
+ """Configurator that configures a class that's potentially going to be
316
+ mapped, and optionally turned into a dataclass as well."""
317
+
318
+ __slots__ = (
319
+ "properties",
320
+ "declared_attr_reg",
321
+ )
322
+
323
+ properties: util.OrderedDict[
324
+ str,
325
+ Union[
326
+ Sequence[NamedColumn[Any]], NamedColumn[Any], MapperProperty[Any]
327
+ ],
328
+ ]
329
+ declared_attr_reg: Dict[declared_attr[Any], Any]
330
+
331
+ def __init__(
332
+ self,
333
+ registry: _RegistryType,
334
+ cls_: Type[Any],
335
+ ):
336
+ super().__init__(cls_)
337
+ self.properties = util.OrderedDict()
338
+ self.declared_attr_reg = {}
339
+
340
+ instrumentation.register_class(
341
+ self.cls,
342
+ finalize=False,
343
+ registry=registry,
344
+ declarative_scan=self,
345
+ init_method=registry.constructor,
346
+ )
347
+
348
+ def set_cls_attribute(self, attrname: str, value: _T) -> _T:
349
+ manager = instrumentation.manager_of_class(self.cls)
350
+ manager.install_member(attrname, value)
351
+ return value
352
+
353
+ def map(self, mapper_kw: _MapperKwArgs) -> Mapper[Any]:
354
+ raise NotImplementedError()
355
+
356
+ def _early_mapping(self, mapper_kw: _MapperKwArgs) -> None:
357
+ self.map(mapper_kw)
358
+
359
+
360
+ class _ImperativeMapperConfig(_MapperConfig):
361
+ """Configurator that configures a class for an imperative mapping."""
362
+
363
+ __slots__ = ("local_table", "inherits")
364
+
365
+ def __init__(
366
+ self,
367
+ registry: _RegistryType,
368
+ cls_: Type[_O],
369
+ table: Optional[FromClause],
370
+ mapper_kw: _MapperKwArgs,
371
+ ):
372
+ super().__init__(registry, cls_)
373
+
374
+ self.local_table = self.set_cls_attribute("__table__", table)
375
+
376
+ with mapperlib._CONFIGURE_MUTEX:
377
+ clsregistry._add_class(
378
+ self.classname, self.cls, registry._class_registry
379
+ )
380
+
381
+ self._setup_inheritance(mapper_kw)
382
+
383
+ self._early_mapping(mapper_kw)
384
+
385
+ def map(self, mapper_kw: _MapperKwArgs = util.EMPTY_DICT) -> Mapper[Any]:
386
+ mapper_cls = Mapper
387
+
388
+ return self.set_cls_attribute(
389
+ "__mapper__",
390
+ mapper_cls(self.cls, self.local_table, **mapper_kw),
391
+ )
392
+
393
+ def _setup_inheritance(self, mapper_kw: _MapperKwArgs) -> None:
394
+ cls = self.cls
395
+
396
+ inherits = None
397
+ inherits_search = []
398
+
399
+ # since we search for classical mappings now, search for
400
+ # multiple mapped bases as well and raise an error.
401
+ for base_ in cls.__bases__:
402
+ c = _resolve_for_abstract_or_classical(base_)
403
+ if c is None:
404
+ continue
405
+
406
+ if _is_supercls_for_inherits(c) and c not in inherits_search:
407
+ inherits_search.append(c)
408
+
409
+ if inherits_search:
410
+ if len(inherits_search) > 1:
411
+ raise exc.InvalidRequestError(
412
+ "Class %s has multiple mapped bases: %r"
413
+ % (cls, inherits_search)
414
+ )
415
+ inherits = inherits_search[0]
416
+
417
+ self.inherits = inherits
418
+
419
+
420
+ class _CollectedAnnotation(NamedTuple):
421
+ raw_annotation: _AnnotationScanType
422
+ mapped_container: Optional[Type[Mapped[Any]]]
423
+ extracted_mapped_annotation: Union[_AnnotationScanType, str]
424
+ is_dataclass: bool
425
+ attr_value: Any
426
+ originating_module: str
427
+ originating_class: Type[Any]
428
+
429
+
430
+ class _ClassScanAbstractConfig(_ORMClassConfigurator):
431
+ """Abstract base for a configurator that configures a class for a
432
+ declarative mapping, or an unmapped ORM dataclass.
433
+
434
+ Defines scanning of pep-484 annotations as well as ORM dataclass
435
+ applicators
436
+
437
+ """
438
+
439
+ __slots__ = ()
440
+
441
+ clsdict_view: _ClassDict
442
+ collected_annotations: Dict[str, _CollectedAnnotation]
443
+ collected_attributes: Dict[str, Any]
444
+
445
+ is_dataclass_prior_to_mapping: bool
446
+ allow_unmapped_annotations: bool
447
+
448
+ dataclass_setup_arguments: Optional[_DataclassArguments]
449
+ """if the class has SQLAlchemy native dataclass parameters, where
450
+ we will turn the class into a dataclass within the declarative mapping
451
+ process.
452
+
453
+ """
454
+
455
+ allow_dataclass_fields: bool
456
+ """if true, look for dataclass-processed Field objects on the target
457
+ class as well as superclasses and extract ORM mapping directives from
458
+ the "metadata" attribute of each Field.
459
+
460
+ if False, dataclass fields can still be used, however they won't be
461
+ mapped.
462
+
463
+ """
464
+
465
+ _include_dunders = {
466
+ "__table__",
467
+ "__mapper_args__",
468
+ "__tablename__",
469
+ "__table_args__",
470
+ }
471
+
472
+ _match_exclude_dunders = re.compile(r"^(?:_sa_|__)")
473
+
474
+ def _scan_attributes(self) -> None:
475
+ raise NotImplementedError()
476
+
477
+ def _setup_dataclasses_transforms(
478
+ self, *, enable_descriptor_defaults: bool, revert: bool = False
479
+ ) -> None:
480
+ dataclass_setup_arguments = self.dataclass_setup_arguments
481
+ if not dataclass_setup_arguments:
482
+ return
483
+
484
+ # can't use is_dataclass since it uses hasattr
485
+ if "__dataclass_fields__" in self.cls.__dict__:
486
+ raise exc.InvalidRequestError(
487
+ f"Class {self.cls} is already a dataclass; ensure that "
488
+ "base classes / decorator styles of establishing dataclasses "
489
+ "are not being mixed. "
490
+ "This can happen if a class that inherits from "
491
+ "'MappedAsDataclass', even indirectly, is been mapped with "
492
+ "'@registry.mapped_as_dataclass'"
493
+ )
494
+
495
+ # can't create a dataclass if __table__ is already there. This would
496
+ # fail an assertion when calling _get_arguments_for_make_dataclass:
497
+ # assert False, "Mapped[] received without a mapping declaration"
498
+ if "__table__" in self.cls.__dict__:
499
+ raise exc.InvalidRequestError(
500
+ f"Class {self.cls} already defines a '__table__'. "
501
+ "ORM Annotated Dataclasses do not support a pre-existing "
502
+ "'__table__' element"
503
+ )
504
+
505
+ raise_for_non_dc_attrs = collections.defaultdict(list)
506
+
507
+ def _allow_dataclass_field(
508
+ key: str, originating_class: Type[Any]
509
+ ) -> bool:
510
+ if (
511
+ originating_class is not self.cls
512
+ and "__dataclass_fields__" not in originating_class.__dict__
513
+ ):
514
+ raise_for_non_dc_attrs[originating_class].append(key)
515
+
516
+ return True
517
+
518
+ field_list = [
519
+ _AttributeOptions._get_arguments_for_make_dataclass(
520
+ self,
521
+ key,
522
+ anno,
523
+ mapped_container,
524
+ self.collected_attributes.get(key, _NoArg.NO_ARG),
525
+ dataclass_setup_arguments,
526
+ enable_descriptor_defaults,
527
+ )
528
+ for key, anno, mapped_container in (
529
+ (
530
+ key,
531
+ raw_anno,
532
+ mapped_container,
533
+ )
534
+ for key, (
535
+ raw_anno,
536
+ mapped_container,
537
+ mapped_anno,
538
+ is_dc,
539
+ attr_value,
540
+ originating_module,
541
+ originating_class,
542
+ ) in self.collected_annotations.items()
543
+ if _allow_dataclass_field(key, originating_class)
544
+ and (
545
+ key not in self.collected_attributes
546
+ # issue #9226; check for attributes that we've collected
547
+ # which are already instrumented, which we would assume
548
+ # mean we are in an ORM inheritance mapping and this
549
+ # attribute is already mapped on the superclass. Under
550
+ # no circumstance should any QueryableAttribute be sent to
551
+ # the dataclass() function; anything that's mapped should
552
+ # be Field and that's it
553
+ or not isinstance(
554
+ self.collected_attributes[key], QueryableAttribute
555
+ )
556
+ )
557
+ )
558
+ ]
559
+ if raise_for_non_dc_attrs:
560
+ for (
561
+ originating_class,
562
+ non_dc_attrs,
563
+ ) in raise_for_non_dc_attrs.items():
564
+ raise exc.InvalidRequestError(
565
+ f"When transforming {self.cls} to a dataclass, "
566
+ f"attribute(s) "
567
+ f"{', '.join(repr(key) for key in non_dc_attrs)} "
568
+ f"originates from superclass "
569
+ f"{originating_class}, which is not a dataclass. When "
570
+ f"declaring SQLAlchemy Declarative "
571
+ f"Dataclasses, ensure that all mixin classes and other "
572
+ f"superclasses which include attributes are also a "
573
+ f"subclass of MappedAsDataclass or make use of the "
574
+ f"@unmapped_dataclass decorator.",
575
+ code="dcmx",
576
+ )
577
+
578
+ if revert:
579
+ # the "revert" case is used only by an unmapped mixin class
580
+ # that is nonetheless using Mapped construct and needs to
581
+ # itself be a dataclass
582
+ revert_dict = {
583
+ name: self.cls.__dict__[name]
584
+ for name in (item[0] for item in field_list)
585
+ if name in self.cls.__dict__
586
+ }
587
+ else:
588
+ revert_dict = None
589
+
590
+ # get original annotations using ForwardRef for symbols that
591
+ # are unresolvable
592
+ orig_annotations = util.get_annotations(self.cls)
593
+
594
+ # build a new __annotations__ dict from the fields we have.
595
+ # this has to be done carefully since we have to maintain
596
+ # the correct order! wow
597
+ swap_annotations = {}
598
+ defaults = {}
599
+
600
+ for item in field_list:
601
+ if len(item) == 2:
602
+ name, tp = item
603
+ elif len(item) == 3:
604
+ name, tp, spec = item
605
+ defaults[name] = spec
606
+ else:
607
+ assert False
608
+
609
+ # add the annotation to the new dict we are creating.
610
+ # note that if name is in orig_annotations, we expect
611
+ # tp and orig_annotations[name] to be identical.
612
+ swap_annotations[name] = orig_annotations.get(name, tp)
613
+
614
+ for k, v in defaults.items():
615
+ setattr(self.cls, k, v)
616
+
617
+ self._assert_dc_arguments(dataclass_setup_arguments)
618
+
619
+ dataclass_callable = dataclass_setup_arguments["dataclass_callable"]
620
+ if dataclass_callable is _NoArg.NO_ARG:
621
+ dataclass_callable = dataclasses.dataclass
622
+
623
+ # create a merged __annotations__ dictionary, maintaining order
624
+ # as best we can:
625
+
626
+ # 1. merge all keys in orig_annotations that occur before
627
+ # we see any of our mapped fields (this can be attributes like
628
+ # __table_args__ etc.)
629
+ new_annotations = {
630
+ k: orig_annotations[k]
631
+ for k in itertools.takewhile(
632
+ lambda k: k not in swap_annotations, orig_annotations
633
+ )
634
+ }
635
+
636
+ # 2. then put in all the dataclass annotations we have
637
+ new_annotations |= swap_annotations
638
+
639
+ # 3. them merge all of orig_annotations which will add remaining
640
+ # keys
641
+ new_annotations |= orig_annotations
642
+
643
+ # 4. this becomes the new class annotations.
644
+ restore_anno = util.restore_annotations(self.cls, new_annotations)
645
+
646
+ try:
647
+ dataclass_callable( # type: ignore[call-overload]
648
+ self.cls,
649
+ **{ # type: ignore[call-overload,unused-ignore]
650
+ k: v
651
+ for k, v in dataclass_setup_arguments.items()
652
+ if v is not _NoArg.NO_ARG
653
+ and k not in ("dataclass_callable",)
654
+ },
655
+ )
656
+ except (TypeError, ValueError) as ex:
657
+ raise exc.InvalidRequestError(
658
+ f"Python dataclasses error encountered when creating "
659
+ f"dataclass for {self.cls.__name__!r}: "
660
+ f"{ex!r}. Please refer to Python dataclasses "
661
+ "documentation for additional information.",
662
+ code="dcte",
663
+ ) from ex
664
+ finally:
665
+ if revert and revert_dict:
666
+ # used for mixin dataclasses; we have to restore the
667
+ # mapped_column(), relationship() etc. to the class so these
668
+ # take place for a mapped class scan
669
+ for k, v in revert_dict.items():
670
+ setattr(self.cls, k, v)
671
+
672
+ restore_anno()
673
+
674
+ def _collect_annotation(
675
+ self,
676
+ name: str,
677
+ raw_annotation: _AnnotationScanType,
678
+ originating_class: Type[Any],
679
+ expect_mapped: Optional[bool],
680
+ attr_value: Any,
681
+ ) -> Optional[_CollectedAnnotation]:
682
+ if name in self.collected_annotations:
683
+ return self.collected_annotations[name]
684
+
685
+ if raw_annotation is None:
686
+ return None
687
+
688
+ is_dataclass = self.is_dataclass_prior_to_mapping
689
+ allow_unmapped = self.allow_unmapped_annotations
690
+
691
+ if expect_mapped is None:
692
+ is_dataclass_field = isinstance(attr_value, dataclasses.Field)
693
+ expect_mapped = (
694
+ not is_dataclass_field
695
+ and not allow_unmapped
696
+ and (
697
+ attr_value is None
698
+ or isinstance(attr_value, _MappedAttribute)
699
+ )
700
+ )
701
+
702
+ is_dataclass_field = False
703
+ extracted = _extract_mapped_subtype(
704
+ raw_annotation,
705
+ self.cls,
706
+ originating_class.__module__,
707
+ name,
708
+ type(attr_value),
709
+ required=False,
710
+ is_dataclass_field=is_dataclass_field,
711
+ expect_mapped=expect_mapped and not is_dataclass,
712
+ )
713
+ if extracted is None:
714
+ # ClassVar can come out here
715
+ return None
716
+
717
+ extracted_mapped_annotation, mapped_container = extracted
718
+
719
+ if attr_value is None and not is_literal(extracted_mapped_annotation):
720
+ for elem in get_args(extracted_mapped_annotation):
721
+ if is_fwd_ref(
722
+ elem, check_generic=True, check_for_plain_string=True
723
+ ):
724
+ elem = de_stringify_annotation(
725
+ self.cls,
726
+ elem,
727
+ originating_class.__module__,
728
+ include_generic=True,
729
+ )
730
+ # look in Annotated[...] for an ORM construct,
731
+ # such as Annotated[int, mapped_column(primary_key=True)]
732
+ if isinstance(elem, _IntrospectsAnnotations):
733
+ attr_value = elem.found_in_pep593_annotated()
734
+
735
+ self.collected_annotations[name] = ca = _CollectedAnnotation(
736
+ raw_annotation,
737
+ mapped_container,
738
+ extracted_mapped_annotation,
739
+ is_dataclass,
740
+ attr_value,
741
+ originating_class.__module__,
742
+ originating_class,
743
+ )
744
+ return ca
745
+
746
+ @classmethod
747
+ def _assert_dc_arguments(cls, arguments: _DataclassArguments) -> None:
748
+ allowed = {
749
+ "init",
750
+ "repr",
751
+ "order",
752
+ "eq",
753
+ "unsafe_hash",
754
+ "kw_only",
755
+ "match_args",
756
+ "dataclass_callable",
757
+ }
758
+ disallowed_args = set(arguments).difference(allowed)
759
+ if disallowed_args:
760
+ msg = ", ".join(f"{arg!r}" for arg in sorted(disallowed_args))
761
+ raise exc.ArgumentError(
762
+ f"Dataclass argument(s) {msg} are not accepted"
763
+ )
764
+
765
+ def _cls_attr_override_checker(
766
+ self, cls: Type[_O]
767
+ ) -> Callable[[str, Any], bool]:
768
+ """Produce a function that checks if a class has overridden an
769
+ attribute, taking SQLAlchemy-enabled dataclass fields into account.
770
+
771
+ """
772
+
773
+ if self.allow_dataclass_fields:
774
+ sa_dataclass_metadata_key = _get_immediate_cls_attr(
775
+ cls, "__sa_dataclass_metadata_key__"
776
+ )
777
+ else:
778
+ sa_dataclass_metadata_key = None
779
+
780
+ if not sa_dataclass_metadata_key:
781
+
782
+ def attribute_is_overridden(key: str, obj: Any) -> bool:
783
+ return getattr(cls, key, obj) is not obj
784
+
785
+ else:
786
+ all_datacls_fields = {
787
+ f.name: f.metadata[sa_dataclass_metadata_key]
788
+ for f in util.dataclass_fields(cls)
789
+ if sa_dataclass_metadata_key in f.metadata
790
+ }
791
+ local_datacls_fields = {
792
+ f.name: f.metadata[sa_dataclass_metadata_key]
793
+ for f in util.local_dataclass_fields(cls)
794
+ if sa_dataclass_metadata_key in f.metadata
795
+ }
796
+
797
+ absent = object()
798
+
799
+ def attribute_is_overridden(key: str, obj: Any) -> bool:
800
+ if _is_declarative_props(obj):
801
+ obj = obj.fget
802
+
803
+ # this function likely has some failure modes still if
804
+ # someone is doing a deep mixing of the same attribute
805
+ # name as plain Python attribute vs. dataclass field.
806
+
807
+ ret = local_datacls_fields.get(key, absent)
808
+ if _is_declarative_props(ret):
809
+ ret = ret.fget
810
+
811
+ if ret is obj:
812
+ return False
813
+ elif ret is not absent:
814
+ return True
815
+
816
+ all_field = all_datacls_fields.get(key, absent)
817
+
818
+ ret = getattr(cls, key, obj)
819
+
820
+ if ret is obj:
821
+ return False
822
+
823
+ # for dataclasses, this could be the
824
+ # 'default' of the field. so filter more specifically
825
+ # for an already-mapped InstrumentedAttribute
826
+ if ret is not absent and isinstance(
827
+ ret, InstrumentedAttribute
828
+ ):
829
+ return True
830
+
831
+ if all_field is obj:
832
+ return False
833
+ elif all_field is not absent:
834
+ return True
835
+
836
+ # can't find another attribute
837
+ return False
838
+
839
+ return attribute_is_overridden
840
+
841
+ def _cls_attr_resolver(
842
+ self, cls: Type[Any]
843
+ ) -> Callable[[], Iterable[Tuple[str, Any, Any, bool]]]:
844
+ """produce a function to iterate the "attributes" of a class
845
+ which we want to consider for mapping, adjusting for SQLAlchemy fields
846
+ embedded in dataclass fields.
847
+
848
+ """
849
+ cls_annotations = util.get_annotations(cls)
850
+
851
+ cls_vars = vars(cls)
852
+
853
+ _include_dunders = self._include_dunders
854
+ _match_exclude_dunders = self._match_exclude_dunders
855
+
856
+ names = [
857
+ n
858
+ for n in util.merge_lists_w_ordering(
859
+ list(cls_vars), list(cls_annotations)
860
+ )
861
+ if not _match_exclude_dunders.match(n) or n in _include_dunders
862
+ ]
863
+
864
+ if self.allow_dataclass_fields:
865
+ sa_dataclass_metadata_key: Optional[str] = _get_immediate_cls_attr(
866
+ cls, "__sa_dataclass_metadata_key__"
867
+ )
868
+ else:
869
+ sa_dataclass_metadata_key = None
870
+
871
+ if not sa_dataclass_metadata_key:
872
+
873
+ def local_attributes_for_class() -> (
874
+ Iterable[Tuple[str, Any, Any, bool]]
875
+ ):
876
+ return (
877
+ (
878
+ name,
879
+ cls_vars.get(name),
880
+ cls_annotations.get(name),
881
+ False,
882
+ )
883
+ for name in names
884
+ )
885
+
886
+ else:
887
+ dataclass_fields = {
888
+ field.name: field for field in util.local_dataclass_fields(cls)
889
+ }
890
+
891
+ fixed_sa_dataclass_metadata_key = sa_dataclass_metadata_key
892
+
893
+ def local_attributes_for_class() -> (
894
+ Iterable[Tuple[str, Any, Any, bool]]
895
+ ):
896
+ for name in names:
897
+ field = dataclass_fields.get(name, None)
898
+ if field and sa_dataclass_metadata_key in field.metadata:
899
+ yield field.name, _as_dc_declaredattr(
900
+ field.metadata, fixed_sa_dataclass_metadata_key
901
+ ), cls_annotations.get(field.name), True
902
+ else:
903
+ yield name, cls_vars.get(name), cls_annotations.get(
904
+ name
905
+ ), False
906
+
907
+ return local_attributes_for_class
908
+
909
+
910
+ class _DeclarativeMapperConfig(_MapperConfig, _ClassScanAbstractConfig):
911
+ """Configurator that will produce a declarative mapped class"""
912
+
913
+ __slots__ = (
914
+ "registry",
915
+ "local_table",
916
+ "persist_selectable",
917
+ "declared_columns",
918
+ "column_ordering",
919
+ "column_copies",
920
+ "table_args",
921
+ "tablename",
922
+ "mapper_args",
923
+ "mapper_args_fn",
924
+ "table_fn",
925
+ "inherits",
926
+ "single",
927
+ "clsdict_view",
928
+ "collected_attributes",
929
+ "collected_annotations",
930
+ "allow_dataclass_fields",
931
+ "dataclass_setup_arguments",
932
+ "is_dataclass_prior_to_mapping",
933
+ "allow_unmapped_annotations",
934
+ )
935
+
936
+ is_deferred = False
937
+ registry: _RegistryType
938
+ local_table: Optional[FromClause]
939
+ persist_selectable: Optional[FromClause]
940
+ declared_columns: util.OrderedSet[Column[Any]]
941
+ column_ordering: Dict[Column[Any], int]
942
+ column_copies: Dict[
943
+ Union[MappedColumn[Any], Column[Any]],
944
+ Union[MappedColumn[Any], Column[Any]],
945
+ ]
946
+ tablename: Optional[str]
947
+ mapper_args: Mapping[str, Any]
948
+ table_args: Optional[_TableArgsType]
949
+ mapper_args_fn: Optional[Callable[[], Dict[str, Any]]]
950
+ inherits: Optional[Type[Any]]
951
+ single: bool
952
+
953
+ def __init__(
954
+ self,
955
+ registry: _RegistryType,
956
+ cls_: Type[_O],
957
+ dict_: _ClassDict,
958
+ ):
959
+ # grab class dict before the instrumentation manager has been added.
960
+ # reduces cycles
961
+ self.clsdict_view = (
962
+ util.immutabledict(dict_) if dict_ else util.EMPTY_DICT
963
+ )
964
+ super().__init__(registry, cls_)
965
+ self.registry = registry
966
+ self.persist_selectable = None
967
+
968
+ self.collected_attributes = {}
969
+ self.collected_annotations = {}
970
+ self.declared_columns = util.OrderedSet()
971
+ self.column_ordering = {}
972
+ self.column_copies = {}
973
+ self.single = False
974
+ self.dataclass_setup_arguments = dca = getattr(
975
+ self.cls, "_sa_apply_dc_transforms", None
976
+ )
977
+
978
+ self.allow_unmapped_annotations = getattr(
979
+ self.cls, "__allow_unmapped__", False
980
+ ) or bool(self.dataclass_setup_arguments)
981
+
982
+ self.is_dataclass_prior_to_mapping = cld = dataclasses.is_dataclass(
983
+ cls_
984
+ )
985
+
986
+ sdk = _get_immediate_cls_attr(cls_, "__sa_dataclass_metadata_key__")
987
+
988
+ # we don't want to consume Field objects from a not-already-dataclass.
989
+ # the Field objects won't have their "name" or "type" populated,
990
+ # and while it seems like we could just set these on Field as we
991
+ # read them, Field is documented as "user read only" and we need to
992
+ # stay far away from any off-label use of dataclasses APIs.
993
+ if (not cld or dca) and sdk:
994
+ raise exc.InvalidRequestError(
995
+ "SQLAlchemy mapped dataclasses can't consume mapping "
996
+ "information from dataclass.Field() objects if the immediate "
997
+ "class is not already a dataclass."
998
+ )
999
+
1000
+ # if already a dataclass, and __sa_dataclass_metadata_key__ present,
1001
+ # then also look inside of dataclass.Field() objects yielded by
1002
+ # dataclasses.get_fields(cls) when scanning for attributes
1003
+ self.allow_dataclass_fields = bool(sdk and cld)
1004
+
1005
+ self._setup_declared_events()
1006
+
1007
+ self._scan_attributes()
1008
+
1009
+ self._setup_dataclasses_transforms(enable_descriptor_defaults=True)
1010
+
1011
+ with mapperlib._CONFIGURE_MUTEX:
1012
+ clsregistry._add_class(
1013
+ self.classname, self.cls, registry._class_registry
1014
+ )
1015
+
1016
+ self._setup_inheriting_mapper()
1017
+
1018
+ self._extract_mappable_attributes()
1019
+
1020
+ self._extract_declared_columns()
1021
+
1022
+ self._setup_table()
1023
+
1024
+ self._setup_inheriting_columns()
1025
+
1026
+ self._early_mapping(util.EMPTY_DICT)
1027
+
1028
+ def _setup_declared_events(self) -> None:
1029
+ if _get_immediate_cls_attr(self.cls, "__declare_last__"):
1030
+
1031
+ @event.listens_for(Mapper, "after_configured")
1032
+ def after_configured() -> None:
1033
+ cast(
1034
+ "_DeclMappedClassProtocol[Any]", self.cls
1035
+ ).__declare_last__()
1036
+
1037
+ if _get_immediate_cls_attr(self.cls, "__declare_first__"):
1038
+
1039
+ @event.listens_for(Mapper, "before_configured")
1040
+ def before_configured() -> None:
1041
+ cast(
1042
+ "_DeclMappedClassProtocol[Any]", self.cls
1043
+ ).__declare_first__()
1044
+
1045
+ def _scan_attributes(self) -> None:
1046
+ cls = self.cls
1047
+
1048
+ cls_as_Decl = cast("_DeclMappedClassProtocol[Any]", cls)
1049
+
1050
+ clsdict_view = self.clsdict_view
1051
+ collected_attributes = self.collected_attributes
1052
+ column_copies = self.column_copies
1053
+ _include_dunders = self._include_dunders
1054
+ mapper_args_fn = None
1055
+ table_args = inherited_table_args = None
1056
+ table_fn = None
1057
+ tablename = None
1058
+ fixed_table = "__table__" in clsdict_view
1059
+
1060
+ attribute_is_overridden = self._cls_attr_override_checker(self.cls)
1061
+
1062
+ bases = []
1063
+
1064
+ for base in cls.__mro__:
1065
+ # collect bases and make sure standalone columns are copied
1066
+ # to be the column they will ultimately be on the class,
1067
+ # so that declared_attr functions use the right columns.
1068
+ # need to do this all the way up the hierarchy first
1069
+ # (see #8190)
1070
+
1071
+ class_mapped = base is not cls and _is_supercls_for_inherits(base)
1072
+
1073
+ local_attributes_for_class = self._cls_attr_resolver(base)
1074
+
1075
+ if not class_mapped and base is not cls:
1076
+ locally_collected_columns = self._produce_column_copies(
1077
+ local_attributes_for_class,
1078
+ attribute_is_overridden,
1079
+ fixed_table,
1080
+ base,
1081
+ )
1082
+ else:
1083
+ locally_collected_columns = {}
1084
+
1085
+ bases.append(
1086
+ (
1087
+ base,
1088
+ class_mapped,
1089
+ local_attributes_for_class,
1090
+ locally_collected_columns,
1091
+ )
1092
+ )
1093
+
1094
+ for (
1095
+ base,
1096
+ class_mapped,
1097
+ local_attributes_for_class,
1098
+ locally_collected_columns,
1099
+ ) in bases:
1100
+ # this transfer can also take place as we scan each name
1101
+ # for finer-grained control of how collected_attributes is
1102
+ # populated, as this is what impacts column ordering.
1103
+ # however it's simpler to get it out of the way here.
1104
+ collected_attributes.update(locally_collected_columns)
1105
+
1106
+ for (
1107
+ name,
1108
+ obj,
1109
+ annotation,
1110
+ is_dataclass_field,
1111
+ ) in local_attributes_for_class():
1112
+ if name in _include_dunders:
1113
+ if name == "__mapper_args__":
1114
+ check_decl = _check_declared_props_nocascade(
1115
+ obj, name, cls
1116
+ )
1117
+ if not mapper_args_fn and (
1118
+ not class_mapped or check_decl
1119
+ ):
1120
+ # don't even invoke __mapper_args__ until
1121
+ # after we've determined everything about the
1122
+ # mapped table.
1123
+ # make a copy of it so a class-level dictionary
1124
+ # is not overwritten when we update column-based
1125
+ # arguments.
1126
+ def _mapper_args_fn() -> Dict[str, Any]:
1127
+ return dict(cls_as_Decl.__mapper_args__)
1128
+
1129
+ mapper_args_fn = _mapper_args_fn
1130
+
1131
+ elif name == "__tablename__":
1132
+ check_decl = _check_declared_props_nocascade(
1133
+ obj, name, cls
1134
+ )
1135
+ if not tablename and (not class_mapped or check_decl):
1136
+ tablename = cls_as_Decl.__tablename__
1137
+ elif name == "__table__":
1138
+ check_decl = _check_declared_props_nocascade(
1139
+ obj, name, cls
1140
+ )
1141
+ # if a @declared_attr using "__table__" is detected,
1142
+ # wrap up a callable to look for "__table__" from
1143
+ # the final concrete class when we set up a table.
1144
+ # this was fixed by
1145
+ # #11509, regression in 2.0 from version 1.4.
1146
+ if check_decl and not table_fn:
1147
+ # don't even invoke __table__ until we're ready
1148
+ def _table_fn() -> FromClause:
1149
+ return cls_as_Decl.__table__
1150
+
1151
+ table_fn = _table_fn
1152
+
1153
+ elif name == "__table_args__":
1154
+ check_decl = _check_declared_props_nocascade(
1155
+ obj, name, cls
1156
+ )
1157
+ if not table_args and (not class_mapped or check_decl):
1158
+ table_args = cls_as_Decl.__table_args__
1159
+ if not isinstance(
1160
+ table_args, (tuple, dict, type(None))
1161
+ ):
1162
+ raise exc.ArgumentError(
1163
+ "__table_args__ value must be a tuple, "
1164
+ "dict, or None"
1165
+ )
1166
+ if base is not cls:
1167
+ inherited_table_args = True
1168
+ else:
1169
+ # any other dunder names; should not be here
1170
+ # as we have tested for all four names in
1171
+ # _include_dunders
1172
+ assert False
1173
+ elif class_mapped:
1174
+ if _is_declarative_props(obj) and not obj._quiet:
1175
+ util.warn(
1176
+ "Regular (i.e. not __special__) "
1177
+ "attribute '%s.%s' uses @declared_attr, "
1178
+ "but owning class %s is mapped - "
1179
+ "not applying to subclass %s."
1180
+ % (base.__name__, name, base, cls)
1181
+ )
1182
+
1183
+ continue
1184
+ elif base is not cls:
1185
+ # we're a mixin, abstract base, or something that is
1186
+ # acting like that for now.
1187
+
1188
+ if isinstance(obj, (Column, MappedColumn)):
1189
+ # already copied columns to the mapped class.
1190
+ continue
1191
+ elif isinstance(obj, MapperProperty):
1192
+ raise exc.InvalidRequestError(
1193
+ "Mapper properties (i.e. deferred,"
1194
+ "column_property(), relationship(), etc.) must "
1195
+ "be declared as @declared_attr callables "
1196
+ "on declarative mixin classes. For dataclass "
1197
+ "field() objects, use a lambda:"
1198
+ )
1199
+ elif _is_declarative_props(obj):
1200
+ # tried to get overloads to tell this to
1201
+ # pylance, no luck
1202
+ assert obj is not None
1203
+
1204
+ if obj._cascading:
1205
+ if name in clsdict_view:
1206
+ # unfortunately, while we can use the user-
1207
+ # defined attribute here to allow a clean
1208
+ # override, if there's another
1209
+ # subclass below then it still tries to use
1210
+ # this. not sure if there is enough
1211
+ # information here to add this as a feature
1212
+ # later on.
1213
+ util.warn(
1214
+ "Attribute '%s' on class %s cannot be "
1215
+ "processed due to "
1216
+ "@declared_attr.cascading; "
1217
+ "skipping" % (name, cls)
1218
+ )
1219
+ collected_attributes[name] = column_copies[obj] = (
1220
+ ret
1221
+ ) = obj.__get__(obj, cls)
1222
+ setattr(cls, name, ret)
1223
+ else:
1224
+ if is_dataclass_field:
1225
+ # access attribute using normal class access
1226
+ # first, to see if it's been mapped on a
1227
+ # superclass. note if the dataclasses.field()
1228
+ # has "default", this value can be anything.
1229
+ ret = getattr(cls, name, None)
1230
+
1231
+ # so, if it's anything that's not ORM
1232
+ # mapped, assume we should invoke the
1233
+ # declared_attr
1234
+ if not isinstance(ret, InspectionAttr):
1235
+ ret = obj.fget()
1236
+ else:
1237
+ # access attribute using normal class access.
1238
+ # if the declared attr already took place
1239
+ # on a superclass that is mapped, then
1240
+ # this is no longer a declared_attr, it will
1241
+ # be the InstrumentedAttribute
1242
+ ret = getattr(cls, name)
1243
+
1244
+ # correct for proxies created from hybrid_property
1245
+ # or similar. note there is no known case that
1246
+ # produces nested proxies, so we are only
1247
+ # looking one level deep right now.
1248
+
1249
+ if (
1250
+ isinstance(ret, InspectionAttr)
1251
+ and attr_is_internal_proxy(ret)
1252
+ and not isinstance(
1253
+ ret.original_property, MapperProperty
1254
+ )
1255
+ ):
1256
+ ret = ret.descriptor
1257
+
1258
+ collected_attributes[name] = column_copies[obj] = (
1259
+ ret
1260
+ )
1261
+
1262
+ if (
1263
+ isinstance(ret, (Column, MapperProperty))
1264
+ and ret.doc is None
1265
+ ):
1266
+ ret.doc = obj.__doc__
1267
+
1268
+ self._collect_annotation(
1269
+ name,
1270
+ obj._collect_return_annotation(),
1271
+ base,
1272
+ True,
1273
+ obj,
1274
+ )
1275
+ elif _is_mapped_annotation(annotation, cls, base):
1276
+ # Mapped annotation without any object.
1277
+ # product_column_copies should have handled this.
1278
+ # if future support for other MapperProperty,
1279
+ # then test if this name is already handled and
1280
+ # otherwise proceed to generate.
1281
+ if not fixed_table:
1282
+ assert (
1283
+ name in collected_attributes
1284
+ or attribute_is_overridden(name, None)
1285
+ )
1286
+ continue
1287
+ else:
1288
+ # here, the attribute is some other kind of
1289
+ # property that we assume is not part of the
1290
+ # declarative mapping. however, check for some
1291
+ # more common mistakes
1292
+ self._warn_for_decl_attributes(base, name, obj)
1293
+ elif is_dataclass_field and (
1294
+ name not in clsdict_view or clsdict_view[name] is not obj
1295
+ ):
1296
+ # here, we are definitely looking at the target class
1297
+ # and not a superclass. this is currently a
1298
+ # dataclass-only path. if the name is only
1299
+ # a dataclass field and isn't in local cls.__dict__,
1300
+ # put the object there.
1301
+ # assert that the dataclass-enabled resolver agrees
1302
+ # with what we are seeing
1303
+
1304
+ assert not attribute_is_overridden(name, obj)
1305
+
1306
+ if _is_declarative_props(obj):
1307
+ obj = obj.fget()
1308
+
1309
+ collected_attributes[name] = obj
1310
+ self._collect_annotation(
1311
+ name, annotation, base, False, obj
1312
+ )
1313
+ else:
1314
+ collected_annotation = self._collect_annotation(
1315
+ name, annotation, base, None, obj
1316
+ )
1317
+ is_mapped = (
1318
+ collected_annotation is not None
1319
+ and collected_annotation.mapped_container is not None
1320
+ )
1321
+ generated_obj = (
1322
+ collected_annotation.attr_value
1323
+ if collected_annotation is not None
1324
+ else obj
1325
+ )
1326
+ if obj is None and not fixed_table and is_mapped:
1327
+ collected_attributes[name] = (
1328
+ generated_obj
1329
+ if generated_obj is not None
1330
+ else MappedColumn()
1331
+ )
1332
+ elif name in clsdict_view:
1333
+ collected_attributes[name] = obj
1334
+ # else if the name is not in the cls.__dict__,
1335
+ # don't collect it as an attribute.
1336
+ # we will see the annotation only, which is meaningful
1337
+ # both for mapping and dataclasses setup
1338
+
1339
+ if inherited_table_args and not tablename:
1340
+ table_args = None
1341
+
1342
+ self.table_args = table_args
1343
+ self.tablename = tablename
1344
+ self.mapper_args_fn = mapper_args_fn
1345
+ self.table_fn = table_fn
1346
+
1347
+ @classmethod
1348
+ def _update_annotations_for_non_mapped_class(
1349
+ cls, klass: Type[_O]
1350
+ ) -> Mapping[str, _AnnotationScanType]:
1351
+ cls_annotations = util.get_annotations(klass)
1352
+
1353
+ new_anno = {}
1354
+ for name, annotation in cls_annotations.items():
1355
+ if _is_mapped_annotation(annotation, klass, klass):
1356
+ extracted = _extract_mapped_subtype(
1357
+ annotation,
1358
+ klass,
1359
+ klass.__module__,
1360
+ name,
1361
+ type(None),
1362
+ required=False,
1363
+ is_dataclass_field=False,
1364
+ expect_mapped=False,
1365
+ )
1366
+ if extracted:
1367
+ inner, _ = extracted
1368
+ new_anno[name] = inner
1369
+ else:
1370
+ new_anno[name] = annotation
1371
+ return new_anno
1372
+
1373
+ def _warn_for_decl_attributes(
1374
+ self, cls: Type[Any], key: str, c: Any
1375
+ ) -> None:
1376
+ if isinstance(c, expression.ColumnElement):
1377
+ util.warn(
1378
+ f"Attribute '{key}' on class {cls} appears to "
1379
+ "be a non-schema SQLAlchemy expression "
1380
+ "object; this won't be part of the declarative mapping. "
1381
+ "To map arbitrary expressions, use ``column_property()`` "
1382
+ "or a similar function such as ``deferred()``, "
1383
+ "``query_expression()`` etc. "
1384
+ )
1385
+
1386
+ def _produce_column_copies(
1387
+ self,
1388
+ attributes_for_class: Callable[
1389
+ [], Iterable[Tuple[str, Any, Any, bool]]
1390
+ ],
1391
+ attribute_is_overridden: Callable[[str, Any], bool],
1392
+ fixed_table: bool,
1393
+ originating_class: Type[Any],
1394
+ ) -> Dict[str, Union[Column[Any], MappedColumn[Any]]]:
1395
+ cls = self.cls
1396
+ dict_ = self.clsdict_view
1397
+ locally_collected_attributes = {}
1398
+ column_copies = self.column_copies
1399
+ # copy mixin columns to the mapped class
1400
+
1401
+ for name, obj, annotation, is_dataclass in attributes_for_class():
1402
+ if (
1403
+ not fixed_table
1404
+ and obj is None
1405
+ and _is_mapped_annotation(annotation, cls, originating_class)
1406
+ ):
1407
+ # obj is None means this is the annotation only path
1408
+
1409
+ if attribute_is_overridden(name, obj):
1410
+ # perform same "overridden" check as we do for
1411
+ # Column/MappedColumn, this is how a mixin col is not
1412
+ # applied to an inherited subclass that does not have
1413
+ # the mixin. the anno-only path added here for
1414
+ # #9564
1415
+ continue
1416
+
1417
+ collected_annotation = self._collect_annotation(
1418
+ name, annotation, originating_class, True, obj
1419
+ )
1420
+ obj = (
1421
+ collected_annotation.attr_value
1422
+ if collected_annotation is not None
1423
+ else obj
1424
+ )
1425
+ if obj is None:
1426
+ obj = MappedColumn()
1427
+
1428
+ locally_collected_attributes[name] = obj
1429
+ setattr(cls, name, obj)
1430
+
1431
+ elif isinstance(obj, (Column, MappedColumn)):
1432
+ if attribute_is_overridden(name, obj):
1433
+ # if column has been overridden
1434
+ # (like by the InstrumentedAttribute of the
1435
+ # superclass), skip. don't collect the annotation
1436
+ # either (issue #8718)
1437
+ continue
1438
+
1439
+ collected_annotation = self._collect_annotation(
1440
+ name, annotation, originating_class, True, obj
1441
+ )
1442
+ obj = (
1443
+ collected_annotation.attr_value
1444
+ if collected_annotation is not None
1445
+ else obj
1446
+ )
1447
+
1448
+ if name not in dict_ and not (
1449
+ "__table__" in dict_
1450
+ and (getattr(obj, "name", None) or name)
1451
+ in dict_["__table__"].c
1452
+ ):
1453
+ if obj.foreign_keys:
1454
+ for fk in obj.foreign_keys:
1455
+ if (
1456
+ fk._table_column is not None
1457
+ and fk._table_column.table is None
1458
+ ):
1459
+ raise exc.InvalidRequestError(
1460
+ "Columns with foreign keys to "
1461
+ "non-table-bound "
1462
+ "columns must be declared as "
1463
+ "@declared_attr callables "
1464
+ "on declarative mixin classes. "
1465
+ "For dataclass "
1466
+ "field() objects, use a lambda:."
1467
+ )
1468
+
1469
+ column_copies[obj] = copy_ = obj._copy()
1470
+
1471
+ locally_collected_attributes[name] = copy_
1472
+ setattr(cls, name, copy_)
1473
+
1474
+ return locally_collected_attributes
1475
+
1476
+ def _extract_mappable_attributes(self) -> None:
1477
+ cls = self.cls
1478
+ collected_attributes = self.collected_attributes
1479
+
1480
+ our_stuff = self.properties
1481
+
1482
+ _include_dunders = self._include_dunders
1483
+
1484
+ late_mapped = _get_immediate_cls_attr(
1485
+ cls, "_sa_decl_prepare_nocascade", strict=True
1486
+ )
1487
+
1488
+ allow_unmapped_annotations = self.allow_unmapped_annotations
1489
+ expect_annotations_wo_mapped = (
1490
+ allow_unmapped_annotations or self.is_dataclass_prior_to_mapping
1491
+ )
1492
+
1493
+ look_for_dataclass_things = bool(self.dataclass_setup_arguments)
1494
+
1495
+ for k in list(collected_attributes):
1496
+ if k in _include_dunders:
1497
+ continue
1498
+
1499
+ value = collected_attributes[k]
1500
+
1501
+ if _is_declarative_props(value):
1502
+ # @declared_attr in collected_attributes only occurs here for a
1503
+ # @declared_attr that's directly on the mapped class;
1504
+ # for a mixin, these have already been evaluated
1505
+ if value._cascading:
1506
+ util.warn(
1507
+ "Use of @declared_attr.cascading only applies to "
1508
+ "Declarative 'mixin' and 'abstract' classes. "
1509
+ "Currently, this flag is ignored on mapped class "
1510
+ "%s" % self.cls
1511
+ )
1512
+
1513
+ value = getattr(cls, k)
1514
+
1515
+ elif (
1516
+ isinstance(value, QueryableAttribute)
1517
+ and value.class_ is not cls
1518
+ and value.key != k
1519
+ ):
1520
+ # detect a QueryableAttribute that's already mapped being
1521
+ # assigned elsewhere in userland, turn into a synonym()
1522
+ value = SynonymProperty(value.key)
1523
+ setattr(cls, k, value)
1524
+
1525
+ if (
1526
+ isinstance(value, tuple)
1527
+ and len(value) == 1
1528
+ and isinstance(value[0], (Column, _MappedAttribute))
1529
+ ):
1530
+ util.warn(
1531
+ "Ignoring declarative-like tuple value of attribute "
1532
+ "'%s': possibly a copy-and-paste error with a comma "
1533
+ "accidentally placed at the end of the line?" % k
1534
+ )
1535
+ continue
1536
+ elif look_for_dataclass_things and isinstance(
1537
+ value, dataclasses.Field
1538
+ ):
1539
+ # we collected a dataclass Field; dataclasses would have
1540
+ # set up the correct state on the class
1541
+ continue
1542
+ elif not isinstance(value, (Column, _DCAttributeOptions)):
1543
+ # using @declared_attr for some object that
1544
+ # isn't Column/MapperProperty/_DCAttributeOptions; remove
1545
+ # from the clsdict_view
1546
+ # and place the evaluated value onto the class.
1547
+ collected_attributes.pop(k)
1548
+ self._warn_for_decl_attributes(cls, k, value)
1549
+ if not late_mapped:
1550
+ setattr(cls, k, value)
1551
+ continue
1552
+ # we expect to see the name 'metadata' in some valid cases;
1553
+ # however at this point we see it's assigned to something trying
1554
+ # to be mapped, so raise for that.
1555
+ # TODO: should "registry" here be also? might be too late
1556
+ # to change that now (2.0 betas)
1557
+ elif k in ("metadata",):
1558
+ raise exc.InvalidRequestError(
1559
+ f"Attribute name '{k}' is reserved when using the "
1560
+ "Declarative API."
1561
+ )
1562
+ elif isinstance(value, Column):
1563
+ _undefer_column_name(
1564
+ k, self.column_copies.get(value, value) # type: ignore
1565
+ )
1566
+ else:
1567
+ if isinstance(value, _IntrospectsAnnotations):
1568
+ (
1569
+ annotation,
1570
+ mapped_container,
1571
+ extracted_mapped_annotation,
1572
+ is_dataclass,
1573
+ attr_value,
1574
+ originating_module,
1575
+ originating_class,
1576
+ ) = self.collected_annotations.get(
1577
+ k, (None, None, None, False, None, None, None)
1578
+ )
1579
+
1580
+ # issue #8692 - don't do any annotation interpretation if
1581
+ # an annotation were present and a container such as
1582
+ # Mapped[] etc. were not used. If annotation is None,
1583
+ # do declarative_scan so that the property can raise
1584
+ # for required
1585
+ if (
1586
+ mapped_container is not None
1587
+ or annotation is None
1588
+ # issue #10516: need to do declarative_scan even with
1589
+ # a non-Mapped annotation if we are doing
1590
+ # __allow_unmapped__, for things like col.name
1591
+ # assignment
1592
+ or allow_unmapped_annotations
1593
+ ):
1594
+ try:
1595
+ value.declarative_scan(
1596
+ self,
1597
+ self.registry,
1598
+ cls,
1599
+ originating_module,
1600
+ k,
1601
+ mapped_container,
1602
+ annotation,
1603
+ extracted_mapped_annotation,
1604
+ is_dataclass,
1605
+ )
1606
+ except NameError as ne:
1607
+ raise orm_exc.MappedAnnotationError(
1608
+ f"Could not resolve all types within mapped "
1609
+ f'annotation: "{annotation}". Ensure all '
1610
+ f"types are written correctly and are "
1611
+ f"imported within the module in use."
1612
+ ) from ne
1613
+ else:
1614
+ # assert that we were expecting annotations
1615
+ # without Mapped[] were going to be passed.
1616
+ # otherwise an error should have been raised
1617
+ # by util._extract_mapped_subtype before we got here.
1618
+ assert expect_annotations_wo_mapped
1619
+
1620
+ if isinstance(value, _DCAttributeOptions):
1621
+ if (
1622
+ value._has_dataclass_arguments
1623
+ and not look_for_dataclass_things
1624
+ ):
1625
+ if isinstance(value, MapperProperty):
1626
+ argnames = [
1627
+ "init",
1628
+ "default_factory",
1629
+ "repr",
1630
+ "default",
1631
+ "dataclass_metadata",
1632
+ ]
1633
+ else:
1634
+ argnames = [
1635
+ "init",
1636
+ "default_factory",
1637
+ "repr",
1638
+ "dataclass_metadata",
1639
+ ]
1640
+
1641
+ args = {
1642
+ a
1643
+ for a in argnames
1644
+ if getattr(
1645
+ value._attribute_options, f"dataclasses_{a}"
1646
+ )
1647
+ is not _NoArg.NO_ARG
1648
+ }
1649
+
1650
+ raise exc.ArgumentError(
1651
+ f"Attribute '{k}' on class {cls} includes "
1652
+ f"dataclasses argument(s): "
1653
+ f"{', '.join(sorted(repr(a) for a in args))} but "
1654
+ f"class does not specify "
1655
+ "SQLAlchemy native dataclass configuration."
1656
+ )
1657
+
1658
+ if not isinstance(value, (MapperProperty, _MapsColumns)):
1659
+ # filter for _DCAttributeOptions objects that aren't
1660
+ # MapperProperty / mapped_column(). Currently this
1661
+ # includes AssociationProxy. pop it from the things
1662
+ # we're going to map and set it up as a descriptor
1663
+ # on the class.
1664
+ collected_attributes.pop(k)
1665
+
1666
+ # Assoc Prox (or other descriptor object that may
1667
+ # use _DCAttributeOptions) is usually here, except if
1668
+ # 1. we're a
1669
+ # dataclass, dataclasses would have removed the
1670
+ # attr here or 2. assoc proxy is coming from a
1671
+ # superclass, we want it to be direct here so it
1672
+ # tracks state or 3. assoc prox comes from
1673
+ # declared_attr, uncommon case
1674
+ setattr(cls, k, value)
1675
+ continue
1676
+
1677
+ our_stuff[k] = value
1678
+
1679
+ def _extract_declared_columns(self) -> None:
1680
+ our_stuff = self.properties
1681
+
1682
+ # extract columns from the class dict
1683
+ declared_columns = self.declared_columns
1684
+ column_ordering = self.column_ordering
1685
+ name_to_prop_key = collections.defaultdict(set)
1686
+
1687
+ for key, c in list(our_stuff.items()):
1688
+ if isinstance(c, _MapsColumns):
1689
+ mp_to_assign = c.mapper_property_to_assign
1690
+ if mp_to_assign:
1691
+ our_stuff[key] = mp_to_assign
1692
+ else:
1693
+ # if no mapper property to assign, this currently means
1694
+ # this is a MappedColumn that will produce a Column for us
1695
+ del our_stuff[key]
1696
+
1697
+ for col, sort_order in c.columns_to_assign:
1698
+ if not isinstance(c, CompositeProperty):
1699
+ name_to_prop_key[col.name].add(key)
1700
+ declared_columns.add(col)
1701
+
1702
+ # we would assert this, however we want the below
1703
+ # warning to take effect instead. See #9630
1704
+ # assert col not in column_ordering
1705
+
1706
+ column_ordering[col] = sort_order
1707
+
1708
+ # if this is a MappedColumn and the attribute key we
1709
+ # have is not what the column has for its key, map the
1710
+ # Column explicitly under the attribute key name.
1711
+ # otherwise, Mapper will map it under the column key.
1712
+ if mp_to_assign is None and key != col.key:
1713
+ our_stuff[key] = col
1714
+ elif isinstance(c, Column):
1715
+ # undefer previously occurred here, and now occurs earlier.
1716
+ # ensure every column we get here has been named
1717
+ assert c.name is not None
1718
+ name_to_prop_key[c.name].add(key)
1719
+ declared_columns.add(c)
1720
+ # if the column is the same name as the key,
1721
+ # remove it from the explicit properties dict.
1722
+ # the normal rules for assigning column-based properties
1723
+ # will take over, including precedence of columns
1724
+ # in multi-column ColumnProperties.
1725
+ if key == c.key:
1726
+ del our_stuff[key]
1727
+
1728
+ for name, keys in name_to_prop_key.items():
1729
+ if len(keys) > 1:
1730
+ util.warn(
1731
+ "On class %r, Column object %r named "
1732
+ "directly multiple times, "
1733
+ "only one will be used: %s. "
1734
+ "Consider using orm.synonym instead"
1735
+ % (self.classname, name, (", ".join(sorted(keys))))
1736
+ )
1737
+
1738
+ def _setup_table(self, table: Optional[FromClause] = None) -> None:
1739
+ cls = self.cls
1740
+ cls_as_Decl = cast("MappedClassProtocol[Any]", cls)
1741
+
1742
+ tablename = self.tablename
1743
+ table_args = self.table_args
1744
+ clsdict_view = self.clsdict_view
1745
+ declared_columns = self.declared_columns
1746
+ column_ordering = self.column_ordering
1747
+
1748
+ manager = attributes.manager_of_class(cls)
1749
+
1750
+ if (
1751
+ self.table_fn is None
1752
+ and "__table__" not in clsdict_view
1753
+ and table is None
1754
+ ):
1755
+ if hasattr(cls, "__table_cls__"):
1756
+ table_cls = cast(
1757
+ Type[Table],
1758
+ util.unbound_method_to_callable(cls.__table_cls__), # type: ignore # noqa: E501
1759
+ )
1760
+ else:
1761
+ table_cls = Table
1762
+
1763
+ if tablename is not None:
1764
+ args: Tuple[Any, ...] = ()
1765
+ table_kw: Dict[str, Any] = {}
1766
+
1767
+ if table_args:
1768
+ if isinstance(table_args, dict):
1769
+ table_kw = table_args
1770
+ elif isinstance(table_args, tuple):
1771
+ if isinstance(table_args[-1], dict):
1772
+ args, table_kw = table_args[0:-1], table_args[-1]
1773
+ else:
1774
+ args = table_args
1775
+
1776
+ autoload_with = clsdict_view.get("__autoload_with__")
1777
+ if autoload_with:
1778
+ table_kw["autoload_with"] = autoload_with
1779
+
1780
+ autoload = clsdict_view.get("__autoload__")
1781
+ if autoload:
1782
+ table_kw["autoload"] = True
1783
+
1784
+ sorted_columns = sorted(
1785
+ declared_columns,
1786
+ key=lambda c: column_ordering.get(c, 0),
1787
+ )
1788
+ table = self.set_cls_attribute(
1789
+ "__table__",
1790
+ table_cls(
1791
+ tablename,
1792
+ self._metadata_for_cls(manager),
1793
+ *sorted_columns,
1794
+ *args,
1795
+ **table_kw,
1796
+ ),
1797
+ )
1798
+ else:
1799
+ if table is None:
1800
+ if self.table_fn:
1801
+ table = self.set_cls_attribute(
1802
+ "__table__", self.table_fn()
1803
+ )
1804
+ else:
1805
+ table = cls_as_Decl.__table__
1806
+ if declared_columns:
1807
+ for c in declared_columns:
1808
+ if not table.c.contains_column(c):
1809
+ raise exc.ArgumentError(
1810
+ "Can't add additional column %r when "
1811
+ "specifying __table__" % c.key
1812
+ )
1813
+
1814
+ self.local_table = table
1815
+
1816
+ def _metadata_for_cls(self, manager: ClassManager[Any]) -> MetaData:
1817
+ meta: Optional[MetaData] = getattr(self.cls, "metadata", None)
1818
+ if meta is not None:
1819
+ return meta
1820
+ else:
1821
+ return manager.registry.metadata
1822
+
1823
+ def _setup_inheriting_mapper(self) -> None:
1824
+ cls = self.cls
1825
+
1826
+ inherits = None
1827
+
1828
+ if inherits is None:
1829
+ # since we search for classical mappings now, search for
1830
+ # multiple mapped bases as well and raise an error.
1831
+ inherits_search = []
1832
+ for base_ in cls.__bases__:
1833
+ c = _resolve_for_abstract_or_classical(base_)
1834
+ if c is None:
1835
+ continue
1836
+
1837
+ if _is_supercls_for_inherits(c) and c not in inherits_search:
1838
+ inherits_search.append(c)
1839
+
1840
+ if inherits_search:
1841
+ if len(inherits_search) > 1:
1842
+ raise exc.InvalidRequestError(
1843
+ "Class %s has multiple mapped bases: %r"
1844
+ % (cls, inherits_search)
1845
+ )
1846
+ inherits = inherits_search[0]
1847
+ elif isinstance(inherits, Mapper):
1848
+ inherits = inherits.class_
1849
+
1850
+ self.inherits = inherits
1851
+
1852
+ clsdict_view = self.clsdict_view
1853
+ if "__table__" not in clsdict_view and self.tablename is None:
1854
+ self.single = True
1855
+
1856
+ def _setup_inheriting_columns(self) -> None:
1857
+ table = self.local_table
1858
+ cls = self.cls
1859
+ table_args = self.table_args
1860
+ declared_columns = self.declared_columns
1861
+
1862
+ if (
1863
+ table is None
1864
+ and self.inherits is None
1865
+ and not _get_immediate_cls_attr(cls, "__no_table__")
1866
+ ):
1867
+ raise exc.InvalidRequestError(
1868
+ "Class %r does not have a __table__ or __tablename__ "
1869
+ "specified and does not inherit from an existing "
1870
+ "table-mapped class." % cls
1871
+ )
1872
+ elif self.inherits:
1873
+ inherited_mapper_or_config = _declared_mapping_info(self.inherits)
1874
+ assert inherited_mapper_or_config is not None
1875
+ inherited_table = inherited_mapper_or_config.local_table
1876
+ inherited_persist_selectable = (
1877
+ inherited_mapper_or_config.persist_selectable
1878
+ )
1879
+
1880
+ if table is None:
1881
+ # single table inheritance.
1882
+ # ensure no table args
1883
+ if table_args:
1884
+ raise exc.ArgumentError(
1885
+ "Can't place __table_args__ on an inherited class "
1886
+ "with no table."
1887
+ )
1888
+
1889
+ # add any columns declared here to the inherited table.
1890
+ if declared_columns and not isinstance(inherited_table, Table):
1891
+ raise exc.ArgumentError(
1892
+ f"Can't declare columns on single-table-inherited "
1893
+ f"subclass {self.cls}; superclass {self.inherits} "
1894
+ "is not mapped to a Table"
1895
+ )
1896
+
1897
+ for col in declared_columns:
1898
+ assert inherited_table is not None
1899
+ if col.name in inherited_table.c:
1900
+ if inherited_table.c[col.name] is col:
1901
+ continue
1902
+ raise exc.ArgumentError(
1903
+ f"Column '{col}' on class {cls.__name__} "
1904
+ f"conflicts with existing column "
1905
+ f"'{inherited_table.c[col.name]}'. If using "
1906
+ f"Declarative, consider using the "
1907
+ "use_existing_column parameter of mapped_column() "
1908
+ "to resolve conflicts."
1909
+ )
1910
+ if col.primary_key:
1911
+ raise exc.ArgumentError(
1912
+ "Can't place primary key columns on an inherited "
1913
+ "class with no table."
1914
+ )
1915
+
1916
+ if TYPE_CHECKING:
1917
+ assert isinstance(inherited_table, Table)
1918
+
1919
+ inherited_table.append_column(col)
1920
+ if (
1921
+ inherited_persist_selectable is not None
1922
+ and inherited_persist_selectable is not inherited_table
1923
+ ):
1924
+ inherited_persist_selectable._refresh_for_new_column(
1925
+ col
1926
+ )
1927
+
1928
+ def _prepare_mapper_arguments(self, mapper_kw: _MapperKwArgs) -> None:
1929
+ properties = self.properties
1930
+
1931
+ if self.mapper_args_fn:
1932
+ mapper_args = self.mapper_args_fn()
1933
+ else:
1934
+ mapper_args = {}
1935
+
1936
+ if mapper_kw:
1937
+ mapper_args.update(mapper_kw)
1938
+
1939
+ if "properties" in mapper_args:
1940
+ properties = dict(properties)
1941
+ properties.update(mapper_args["properties"])
1942
+
1943
+ # make sure that column copies are used rather
1944
+ # than the original columns from any mixins
1945
+ for k in ("version_id_col", "polymorphic_on"):
1946
+ if k in mapper_args:
1947
+ v = mapper_args[k]
1948
+ mapper_args[k] = self.column_copies.get(v, v)
1949
+
1950
+ if "primary_key" in mapper_args:
1951
+ mapper_args["primary_key"] = [
1952
+ self.column_copies.get(v, v)
1953
+ for v in util.to_list(mapper_args["primary_key"])
1954
+ ]
1955
+
1956
+ if "inherits" in mapper_args:
1957
+ inherits_arg = mapper_args["inherits"]
1958
+ if isinstance(inherits_arg, Mapper):
1959
+ inherits_arg = inherits_arg.class_
1960
+
1961
+ if inherits_arg is not self.inherits:
1962
+ raise exc.InvalidRequestError(
1963
+ "mapper inherits argument given for non-inheriting "
1964
+ "class %s" % (mapper_args["inherits"])
1965
+ )
1966
+
1967
+ if self.inherits:
1968
+ mapper_args["inherits"] = self.inherits
1969
+
1970
+ if self.inherits and not mapper_args.get("concrete", False):
1971
+ # note the superclass is expected to have a Mapper assigned and
1972
+ # not be a deferred config, as this is called within map()
1973
+ inherited_mapper = class_mapper(self.inherits, False)
1974
+ inherited_table = inherited_mapper.local_table
1975
+
1976
+ # single or joined inheritance
1977
+ # exclude any cols on the inherited table which are
1978
+ # not mapped on the parent class, to avoid
1979
+ # mapping columns specific to sibling/nephew classes
1980
+ if "exclude_properties" not in mapper_args:
1981
+ mapper_args["exclude_properties"] = exclude_properties = {
1982
+ c.key
1983
+ for c in inherited_table.c
1984
+ if c not in inherited_mapper._columntoproperty
1985
+ }.union(inherited_mapper.exclude_properties or ())
1986
+ exclude_properties.difference_update(
1987
+ [c.key for c in self.declared_columns]
1988
+ )
1989
+
1990
+ # look through columns in the current mapper that
1991
+ # are keyed to a propname different than the colname
1992
+ # (if names were the same, we'd have popped it out above,
1993
+ # in which case the mapper makes this combination).
1994
+ # See if the superclass has a similar column property.
1995
+ # If so, join them together.
1996
+ for k, col in list(properties.items()):
1997
+ if not isinstance(col, expression.ColumnElement):
1998
+ continue
1999
+ if k in inherited_mapper._props:
2000
+ p = inherited_mapper._props[k]
2001
+ if isinstance(p, ColumnProperty):
2002
+ # note here we place the subclass column
2003
+ # first. See [ticket:1892] for background.
2004
+ properties[k] = [col] + p.columns
2005
+ result_mapper_args = mapper_args.copy()
2006
+ result_mapper_args["properties"] = properties
2007
+ self.mapper_args = result_mapper_args
2008
+
2009
+ def map(self, mapper_kw: _MapperKwArgs = util.EMPTY_DICT) -> Mapper[Any]:
2010
+ self._prepare_mapper_arguments(mapper_kw)
2011
+ if hasattr(self.cls, "__mapper_cls__"):
2012
+ mapper_cls = cast(
2013
+ "Type[Mapper[Any]]",
2014
+ util.unbound_method_to_callable(
2015
+ self.cls.__mapper_cls__ # type: ignore
2016
+ ),
2017
+ )
2018
+ else:
2019
+ mapper_cls = Mapper
2020
+
2021
+ return self.set_cls_attribute(
2022
+ "__mapper__",
2023
+ mapper_cls(self.cls, self.local_table, **self.mapper_args),
2024
+ )
2025
+
2026
+
2027
+ class _UnmappedDataclassConfig(_ClassScanAbstractConfig):
2028
+ """Configurator that will produce an unmapped dataclass."""
2029
+
2030
+ __slots__ = (
2031
+ "clsdict_view",
2032
+ "collected_attributes",
2033
+ "collected_annotations",
2034
+ "allow_dataclass_fields",
2035
+ "dataclass_setup_arguments",
2036
+ "is_dataclass_prior_to_mapping",
2037
+ "allow_unmapped_annotations",
2038
+ )
2039
+
2040
+ def __init__(
2041
+ self,
2042
+ cls_: Type[_O],
2043
+ dict_: _ClassDict,
2044
+ ):
2045
+ super().__init__(cls_)
2046
+ self.clsdict_view = (
2047
+ util.immutabledict(dict_) if dict_ else util.EMPTY_DICT
2048
+ )
2049
+ self.dataclass_setup_arguments = getattr(
2050
+ self.cls, "_sa_apply_dc_transforms", None
2051
+ )
2052
+
2053
+ self.is_dataclass_prior_to_mapping = dataclasses.is_dataclass(cls_)
2054
+ self.allow_dataclass_fields = False
2055
+ self.allow_unmapped_annotations = True
2056
+ self.collected_attributes = {}
2057
+ self.collected_annotations = {}
2058
+
2059
+ self._scan_attributes()
2060
+
2061
+ self._setup_dataclasses_transforms(
2062
+ enable_descriptor_defaults=False, revert=True
2063
+ )
2064
+
2065
+ def _scan_attributes(self) -> None:
2066
+ cls = self.cls
2067
+
2068
+ clsdict_view = self.clsdict_view
2069
+ collected_attributes = self.collected_attributes
2070
+ _include_dunders = self._include_dunders
2071
+
2072
+ attribute_is_overridden = self._cls_attr_override_checker(self.cls)
2073
+
2074
+ local_attributes_for_class = self._cls_attr_resolver(cls)
2075
+ for (
2076
+ name,
2077
+ obj,
2078
+ annotation,
2079
+ is_dataclass_field,
2080
+ ) in local_attributes_for_class():
2081
+ if name in _include_dunders:
2082
+ continue
2083
+ elif is_dataclass_field and (
2084
+ name not in clsdict_view or clsdict_view[name] is not obj
2085
+ ):
2086
+ # here, we are definitely looking at the target class
2087
+ # and not a superclass. this is currently a
2088
+ # dataclass-only path. if the name is only
2089
+ # a dataclass field and isn't in local cls.__dict__,
2090
+ # put the object there.
2091
+ # assert that the dataclass-enabled resolver agrees
2092
+ # with what we are seeing
2093
+
2094
+ assert not attribute_is_overridden(name, obj)
2095
+
2096
+ if _is_declarative_props(obj):
2097
+ obj = obj.fget()
2098
+
2099
+ collected_attributes[name] = obj
2100
+ self._collect_annotation(name, annotation, cls, False, obj)
2101
+ else:
2102
+ self._collect_annotation(name, annotation, cls, None, obj)
2103
+ if name in clsdict_view:
2104
+ collected_attributes[name] = obj
2105
+
2106
+
2107
+ @util.preload_module("sqlalchemy.orm.decl_api")
2108
+ def _as_dc_declaredattr(
2109
+ field_metadata: Mapping[str, Any], sa_dataclass_metadata_key: str
2110
+ ) -> Any:
2111
+ # wrap lambdas inside dataclass fields inside an ad-hoc declared_attr.
2112
+ # we can't write it because field.metadata is immutable :( so we have
2113
+ # to go through extra trouble to compare these
2114
+ decl_api = util.preloaded.orm_decl_api
2115
+ obj = field_metadata[sa_dataclass_metadata_key]
2116
+ if callable(obj) and not isinstance(obj, decl_api.declared_attr):
2117
+ return decl_api.declared_attr(obj)
2118
+ else:
2119
+ return obj
2120
+
2121
+
2122
+ class _DeferredDeclarativeConfig(_DeclarativeMapperConfig):
2123
+ """Configurator that extends _DeclarativeMapperConfig to add a
2124
+ "deferred" step, to allow extensions like AbstractConcreteBase,
2125
+ DeferredMapping to partially set up a mapping that is "prepared"
2126
+ when table metadata is ready.
2127
+
2128
+ """
2129
+
2130
+ _cls: weakref.ref[Type[Any]]
2131
+
2132
+ is_deferred = True
2133
+
2134
+ _configs: util.OrderedDict[
2135
+ weakref.ref[Type[Any]], _DeferredDeclarativeConfig
2136
+ ] = util.OrderedDict()
2137
+
2138
+ def _early_mapping(self, mapper_kw: _MapperKwArgs) -> None:
2139
+ pass
2140
+
2141
+ @property
2142
+ def cls(self) -> Type[Any]:
2143
+ return self._cls() # type: ignore
2144
+
2145
+ @cls.setter
2146
+ def cls(self, class_: Type[Any]) -> None:
2147
+ self._cls = weakref.ref(class_, self._remove_config_cls)
2148
+ self._configs[self._cls] = self
2149
+
2150
+ @classmethod
2151
+ def _remove_config_cls(cls, ref: weakref.ref[Type[Any]]) -> None:
2152
+ cls._configs.pop(ref, None)
2153
+
2154
+ @classmethod
2155
+ def has_cls(cls, class_: Type[Any]) -> bool:
2156
+ # 2.6 fails on weakref if class_ is an old style class
2157
+ return isinstance(class_, type) and weakref.ref(class_) in cls._configs
2158
+
2159
+ @classmethod
2160
+ def raise_unmapped_for_cls(cls, class_: Type[Any]) -> NoReturn:
2161
+ if hasattr(class_, "_sa_raise_deferred_config"):
2162
+ class_._sa_raise_deferred_config()
2163
+
2164
+ raise orm_exc.UnmappedClassError(
2165
+ class_,
2166
+ msg=(
2167
+ f"Class {orm_exc._safe_cls_name(class_)} has a deferred "
2168
+ "mapping on it. It is not yet usable as a mapped class."
2169
+ ),
2170
+ )
2171
+
2172
+ @classmethod
2173
+ def config_for_cls(cls, class_: Type[Any]) -> _DeferredDeclarativeConfig:
2174
+ return cls._configs[weakref.ref(class_)]
2175
+
2176
+ @classmethod
2177
+ def classes_for_base(
2178
+ cls, base_cls: Type[Any], sort: bool = True
2179
+ ) -> List[_DeferredDeclarativeConfig]:
2180
+ classes_for_base = [
2181
+ m
2182
+ for m, cls_ in [(m, m.cls) for m in cls._configs.values()]
2183
+ if cls_ is not None and issubclass(cls_, base_cls)
2184
+ ]
2185
+
2186
+ if not sort:
2187
+ return classes_for_base
2188
+
2189
+ all_m_by_cls = {m.cls: m for m in classes_for_base}
2190
+
2191
+ tuples: List[
2192
+ Tuple[_DeferredDeclarativeConfig, _DeferredDeclarativeConfig]
2193
+ ] = []
2194
+ for m_cls in all_m_by_cls:
2195
+ tuples.extend(
2196
+ (all_m_by_cls[base_cls], all_m_by_cls[m_cls])
2197
+ for base_cls in m_cls.__bases__
2198
+ if base_cls in all_m_by_cls
2199
+ )
2200
+ return list(topological.sort(tuples, classes_for_base))
2201
+
2202
+ def map(self, mapper_kw: _MapperKwArgs = util.EMPTY_DICT) -> Mapper[Any]:
2203
+ self._configs.pop(self._cls, None)
2204
+ return super().map(mapper_kw)
2205
+
2206
+
2207
+ def _add_attribute(
2208
+ cls: Type[Any], key: str, value: MapperProperty[Any]
2209
+ ) -> None:
2210
+ """add an attribute to an existing declarative class.
2211
+
2212
+ This runs through the logic to determine MapperProperty,
2213
+ adds it to the Mapper, adds a column to the mapped Table, etc.
2214
+
2215
+ """
2216
+
2217
+ if "__mapper__" in cls.__dict__:
2218
+ mapped_cls = cast("MappedClassProtocol[Any]", cls)
2219
+
2220
+ def _table_or_raise(mc: MappedClassProtocol[Any]) -> Table:
2221
+ if isinstance(mc.__table__, Table):
2222
+ return mc.__table__
2223
+ raise exc.InvalidRequestError(
2224
+ f"Cannot add a new attribute to mapped class {mc.__name__!r} "
2225
+ "because it's not mapped against a table."
2226
+ )
2227
+
2228
+ if isinstance(value, Column):
2229
+ _undefer_column_name(key, value)
2230
+ _table_or_raise(mapped_cls).append_column(
2231
+ value, replace_existing=True
2232
+ )
2233
+ mapped_cls.__mapper__.add_property(key, value)
2234
+ elif isinstance(value, _MapsColumns):
2235
+ mp = value.mapper_property_to_assign
2236
+ for col, _ in value.columns_to_assign:
2237
+ _undefer_column_name(key, col)
2238
+ _table_or_raise(mapped_cls).append_column(
2239
+ col, replace_existing=True
2240
+ )
2241
+ if not mp:
2242
+ mapped_cls.__mapper__.add_property(key, col)
2243
+ if mp:
2244
+ mapped_cls.__mapper__.add_property(key, mp)
2245
+ elif isinstance(value, MapperProperty):
2246
+ mapped_cls.__mapper__.add_property(key, value)
2247
+ elif isinstance(value, QueryableAttribute) and value.key != key:
2248
+ # detect a QueryableAttribute that's already mapped being
2249
+ # assigned elsewhere in userland, turn into a synonym()
2250
+ value = SynonymProperty(value.key)
2251
+ mapped_cls.__mapper__.add_property(key, value)
2252
+ else:
2253
+ type.__setattr__(cls, key, value)
2254
+ mapped_cls.__mapper__._expire_memoizations()
2255
+ else:
2256
+ type.__setattr__(cls, key, value)
2257
+
2258
+
2259
+ def _del_attribute(cls: Type[Any], key: str) -> None:
2260
+ if (
2261
+ "__mapper__" in cls.__dict__
2262
+ and key in cls.__dict__
2263
+ and not cast(
2264
+ "MappedClassProtocol[Any]", cls
2265
+ ).__mapper__._dispose_called
2266
+ ):
2267
+ value = cls.__dict__[key]
2268
+ if isinstance(
2269
+ value, (Column, _MapsColumns, MapperProperty, QueryableAttribute)
2270
+ ):
2271
+ raise NotImplementedError(
2272
+ "Can't un-map individual mapped attributes on a mapped class."
2273
+ )
2274
+ else:
2275
+ type.__delattr__(cls, key)
2276
+ cast(
2277
+ "MappedClassProtocol[Any]", cls
2278
+ ).__mapper__._expire_memoizations()
2279
+ else:
2280
+ type.__delattr__(cls, key)
2281
+
2282
+
2283
+ def _declarative_constructor(self: Any, **kwargs: Any) -> None:
2284
+ """A simple constructor that allows initialization from kwargs.
2285
+
2286
+ Sets attributes on the constructed instance using the names and
2287
+ values in ``kwargs``.
2288
+
2289
+ Only keys that are present as
2290
+ attributes of the instance's class are allowed. These could be,
2291
+ for example, any mapped columns or relationships.
2292
+ """
2293
+ cls_ = type(self)
2294
+ for k in kwargs:
2295
+ if not hasattr(cls_, k):
2296
+ raise TypeError(
2297
+ "%r is an invalid keyword argument for %s" % (k, cls_.__name__)
2298
+ )
2299
+ setattr(self, k, kwargs[k])
2300
+
2301
+
2302
+ _declarative_constructor.__name__ = "__init__"
2303
+
2304
+
2305
+ def _undefer_column_name(key: str, column: Column[Any]) -> None:
2306
+ if column.key is None:
2307
+ column.key = key
2308
+ if column.name is None:
2309
+ column.name = key