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