decl_base.py 82 KB

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