decl_base.py 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210
  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. """Internal implementation for declarative."""
  8. from __future__ import absolute_import
  9. import collections
  10. import weakref
  11. from sqlalchemy.orm import attributes
  12. from sqlalchemy.orm import instrumentation
  13. from . import clsregistry
  14. from . import exc as orm_exc
  15. from . import mapper as mapperlib
  16. from .attributes import InstrumentedAttribute
  17. from .attributes import QueryableAttribute
  18. from .base import _is_mapped_class
  19. from .base import InspectionAttr
  20. from .descriptor_props import CompositeProperty
  21. from .descriptor_props import SynonymProperty
  22. from .interfaces import MapperProperty
  23. from .mapper import Mapper as mapper
  24. from .properties import ColumnProperty
  25. from .util import class_mapper
  26. from .. import event
  27. from .. import exc
  28. from .. import util
  29. from ..sql import expression
  30. from ..sql.schema import Column
  31. from ..sql.schema import Table
  32. from ..util import topological
  33. def _declared_mapping_info(cls):
  34. # deferred mapping
  35. if _DeferredMapperConfig.has_cls(cls):
  36. return _DeferredMapperConfig.config_for_cls(cls)
  37. # regular mapping
  38. elif _is_mapped_class(cls):
  39. return class_mapper(cls, configure=False)
  40. else:
  41. return None
  42. def _resolve_for_abstract_or_classical(cls):
  43. if cls is object:
  44. return None
  45. if cls.__dict__.get("__abstract__", False):
  46. for sup in cls.__bases__:
  47. sup = _resolve_for_abstract_or_classical(sup)
  48. if sup is not None:
  49. return sup
  50. else:
  51. return None
  52. else:
  53. clsmanager = _dive_for_cls_manager(cls)
  54. if clsmanager:
  55. return clsmanager.class_
  56. else:
  57. return cls
  58. def _get_immediate_cls_attr(cls, attrname, strict=False):
  59. """return an attribute of the class that is either present directly
  60. on the class, e.g. not on a superclass, or is from a superclass but
  61. this superclass is a non-mapped mixin, that is, not a descendant of
  62. the declarative base and is also not classically mapped.
  63. This is used to detect attributes that indicate something about
  64. a mapped class independently from any mapped classes that it may
  65. inherit from.
  66. """
  67. # the rules are different for this name than others,
  68. # make sure we've moved it out. transitional
  69. assert attrname != "__abstract__"
  70. if not issubclass(cls, object):
  71. return None
  72. if attrname in cls.__dict__:
  73. return getattr(cls, attrname)
  74. for base in cls.__mro__[1:]:
  75. _is_classicial_inherits = _dive_for_cls_manager(base)
  76. if attrname in base.__dict__ and (
  77. base is cls
  78. or (
  79. (base in cls.__bases__ if strict else True)
  80. and not _is_classicial_inherits
  81. )
  82. ):
  83. return getattr(base, attrname)
  84. else:
  85. return None
  86. def _dive_for_cls_manager(cls):
  87. # because the class manager registration is pluggable,
  88. # we need to do the search for every class in the hierarchy,
  89. # rather than just a simple "cls._sa_class_manager"
  90. # python 2 old style class
  91. if not hasattr(cls, "__mro__"):
  92. return None
  93. for base in cls.__mro__:
  94. manager = attributes.manager_of_class(base)
  95. if manager:
  96. return manager
  97. return None
  98. def _as_declarative(registry, cls, dict_):
  99. # declarative scans the class for attributes. no table or mapper
  100. # args passed separately.
  101. return _MapperConfig.setup_mapping(registry, cls, dict_, None, {})
  102. def _mapper(registry, cls, table, mapper_kw):
  103. _ImperativeMapperConfig(registry, cls, table, mapper_kw)
  104. return cls.__mapper__
  105. @util.preload_module("sqlalchemy.orm.decl_api")
  106. def _is_declarative_props(obj):
  107. declared_attr = util.preloaded.orm_decl_api.declared_attr
  108. return isinstance(obj, (declared_attr, util.classproperty))
  109. def _check_declared_props_nocascade(obj, name, cls):
  110. if _is_declarative_props(obj):
  111. if getattr(obj, "_cascading", False):
  112. util.warn(
  113. "@declared_attr.cascading is not supported on the %s "
  114. "attribute on class %s. This attribute invokes for "
  115. "subclasses in any case." % (name, cls)
  116. )
  117. return True
  118. else:
  119. return False
  120. class _MapperConfig(object):
  121. __slots__ = (
  122. "cls",
  123. "classname",
  124. "properties",
  125. "declared_attr_reg",
  126. "__weakref__",
  127. )
  128. @classmethod
  129. def setup_mapping(cls, registry, cls_, dict_, table, mapper_kw):
  130. manager = attributes.manager_of_class(cls)
  131. if manager and manager.class_ is cls_:
  132. raise exc.InvalidRequestError(
  133. "Class %r already has been " "instrumented declaratively" % cls
  134. )
  135. if cls_.__dict__.get("__abstract__", False):
  136. return
  137. defer_map = _get_immediate_cls_attr(
  138. cls_, "_sa_decl_prepare_nocascade", strict=True
  139. ) or hasattr(cls_, "_sa_decl_prepare")
  140. if defer_map:
  141. cfg_cls = _DeferredMapperConfig
  142. else:
  143. cfg_cls = _ClassScanMapperConfig
  144. return cfg_cls(registry, cls_, dict_, table, mapper_kw)
  145. def __init__(self, registry, cls_, mapper_kw):
  146. self.cls = util.assert_arg_type(cls_, type, "cls_")
  147. self.classname = cls_.__name__
  148. self.properties = util.OrderedDict()
  149. self.declared_attr_reg = {}
  150. if not mapper_kw.get("non_primary", False):
  151. instrumentation.register_class(
  152. self.cls,
  153. finalize=False,
  154. registry=registry,
  155. declarative_scan=self,
  156. init_method=registry.constructor,
  157. )
  158. else:
  159. manager = attributes.manager_of_class(self.cls)
  160. if not manager or not manager.is_mapped:
  161. raise exc.InvalidRequestError(
  162. "Class %s has no primary mapper configured. Configure "
  163. "a primary mapper first before setting up a non primary "
  164. "Mapper." % self.cls
  165. )
  166. def set_cls_attribute(self, attrname, value):
  167. manager = instrumentation.manager_of_class(self.cls)
  168. manager.install_member(attrname, value)
  169. return value
  170. def _early_mapping(self, mapper_kw):
  171. self.map(mapper_kw)
  172. class _ImperativeMapperConfig(_MapperConfig):
  173. __slots__ = ("dict_", "local_table", "inherits")
  174. def __init__(
  175. self,
  176. registry,
  177. cls_,
  178. table,
  179. mapper_kw,
  180. ):
  181. super(_ImperativeMapperConfig, self).__init__(
  182. registry, cls_, mapper_kw
  183. )
  184. self.dict_ = {}
  185. self.local_table = self.set_cls_attribute("__table__", table)
  186. with mapperlib._CONFIGURE_MUTEX:
  187. if not mapper_kw.get("non_primary", False):
  188. clsregistry.add_class(
  189. self.classname, self.cls, registry._class_registry
  190. )
  191. self._setup_inheritance(mapper_kw)
  192. self._early_mapping(mapper_kw)
  193. def map(self, mapper_kw=util.EMPTY_DICT):
  194. mapper_cls = mapper
  195. return self.set_cls_attribute(
  196. "__mapper__",
  197. mapper_cls(self.cls, self.local_table, **mapper_kw),
  198. )
  199. def _setup_inheritance(self, mapper_kw):
  200. cls = self.cls
  201. inherits = mapper_kw.get("inherits", None)
  202. if inherits is None:
  203. # since we search for classical mappings now, search for
  204. # multiple mapped bases as well and raise an error.
  205. inherits_search = []
  206. for c in cls.__bases__:
  207. c = _resolve_for_abstract_or_classical(c)
  208. if c is None:
  209. continue
  210. if _declared_mapping_info(
  211. c
  212. ) is not None and not _get_immediate_cls_attr(
  213. c, "_sa_decl_prepare_nocascade", strict=True
  214. ):
  215. inherits_search.append(c)
  216. if inherits_search:
  217. if len(inherits_search) > 1:
  218. raise exc.InvalidRequestError(
  219. "Class %s has multiple mapped bases: %r"
  220. % (cls, inherits_search)
  221. )
  222. inherits = inherits_search[0]
  223. elif isinstance(inherits, mapper):
  224. inherits = inherits.class_
  225. self.inherits = inherits
  226. class _ClassScanMapperConfig(_MapperConfig):
  227. __slots__ = (
  228. "dict_",
  229. "local_table",
  230. "persist_selectable",
  231. "declared_columns",
  232. "column_copies",
  233. "table_args",
  234. "tablename",
  235. "mapper_args",
  236. "mapper_args_fn",
  237. "inherits",
  238. )
  239. def __init__(
  240. self,
  241. registry,
  242. cls_,
  243. dict_,
  244. table,
  245. mapper_kw,
  246. ):
  247. # grab class dict before the instrumentation manager has been added.
  248. # reduces cycles
  249. self.dict_ = dict(dict_) if dict_ else {}
  250. super(_ClassScanMapperConfig, self).__init__(registry, cls_, mapper_kw)
  251. self.persist_selectable = None
  252. self.declared_columns = set()
  253. self.column_copies = {}
  254. self._setup_declared_events()
  255. self._scan_attributes()
  256. with mapperlib._CONFIGURE_MUTEX:
  257. clsregistry.add_class(
  258. self.classname, self.cls, registry._class_registry
  259. )
  260. self._extract_mappable_attributes()
  261. self._extract_declared_columns()
  262. self._setup_table(table)
  263. self._setup_inheritance(mapper_kw)
  264. self._early_mapping(mapper_kw)
  265. def _setup_declared_events(self):
  266. if _get_immediate_cls_attr(self.cls, "__declare_last__"):
  267. @event.listens_for(mapper, "after_configured")
  268. def after_configured():
  269. self.cls.__declare_last__()
  270. if _get_immediate_cls_attr(self.cls, "__declare_first__"):
  271. @event.listens_for(mapper, "before_configured")
  272. def before_configured():
  273. self.cls.__declare_first__()
  274. def _cls_attr_override_checker(self, cls):
  275. """Produce a function that checks if a class has overridden an
  276. attribute, taking SQLAlchemy-enabled dataclass fields into account.
  277. """
  278. sa_dataclass_metadata_key = _get_immediate_cls_attr(
  279. cls, "__sa_dataclass_metadata_key__", None
  280. )
  281. if sa_dataclass_metadata_key is None:
  282. def attribute_is_overridden(key, obj):
  283. return getattr(cls, key) is not obj
  284. else:
  285. all_datacls_fields = {
  286. f.name: f.metadata[sa_dataclass_metadata_key]
  287. for f in util.dataclass_fields(cls)
  288. if sa_dataclass_metadata_key in f.metadata
  289. }
  290. local_datacls_fields = {
  291. f.name: f.metadata[sa_dataclass_metadata_key]
  292. for f in util.local_dataclass_fields(cls)
  293. if sa_dataclass_metadata_key in f.metadata
  294. }
  295. absent = object()
  296. def attribute_is_overridden(key, obj):
  297. if _is_declarative_props(obj):
  298. obj = obj.fget
  299. # this function likely has some failure modes still if
  300. # someone is doing a deep mixing of the same attribute
  301. # name as plain Python attribute vs. dataclass field.
  302. ret = local_datacls_fields.get(key, absent)
  303. if _is_declarative_props(ret):
  304. ret = ret.fget
  305. if ret is obj:
  306. return False
  307. elif ret is not absent:
  308. return True
  309. all_field = all_datacls_fields.get(key, absent)
  310. ret = getattr(cls, key, obj)
  311. if ret is obj:
  312. return False
  313. # for dataclasses, this could be the
  314. # 'default' of the field. so filter more specifically
  315. # for an already-mapped InstrumentedAttribute
  316. if ret is not absent and isinstance(
  317. ret, InstrumentedAttribute
  318. ):
  319. return True
  320. if all_field is obj:
  321. return False
  322. elif all_field is not absent:
  323. return True
  324. # can't find another attribute
  325. return False
  326. return attribute_is_overridden
  327. def _cls_attr_resolver(self, cls):
  328. """produce a function to iterate the "attributes" of a class,
  329. adjusting for SQLAlchemy fields embedded in dataclass fields.
  330. """
  331. sa_dataclass_metadata_key = _get_immediate_cls_attr(
  332. cls, "__sa_dataclass_metadata_key__", None
  333. )
  334. if sa_dataclass_metadata_key is None:
  335. def local_attributes_for_class():
  336. for name, obj in vars(cls).items():
  337. yield name, obj, False
  338. else:
  339. field_names = set()
  340. def local_attributes_for_class():
  341. for field in util.local_dataclass_fields(cls):
  342. if sa_dataclass_metadata_key in field.metadata:
  343. field_names.add(field.name)
  344. yield field.name, _as_dc_declaredattr(
  345. field.metadata, sa_dataclass_metadata_key
  346. ), True
  347. for name, obj in vars(cls).items():
  348. if name not in field_names:
  349. yield name, obj, False
  350. return local_attributes_for_class
  351. def _scan_attributes(self):
  352. cls = self.cls
  353. dict_ = self.dict_
  354. column_copies = self.column_copies
  355. mapper_args_fn = None
  356. table_args = inherited_table_args = None
  357. tablename = None
  358. attribute_is_overridden = self._cls_attr_override_checker(self.cls)
  359. bases = []
  360. for base in cls.__mro__:
  361. # collect bases and make sure standalone columns are copied
  362. # to be the column they will ultimately be on the class,
  363. # so that declared_attr functions use the right columns.
  364. # need to do this all the way up the hierarchy first
  365. # (see #8190)
  366. class_mapped = (
  367. base is not cls
  368. and _declared_mapping_info(base) is not None
  369. and not _get_immediate_cls_attr(
  370. base, "_sa_decl_prepare_nocascade", strict=True
  371. )
  372. )
  373. local_attributes_for_class = self._cls_attr_resolver(base)
  374. if not class_mapped and base is not cls:
  375. locally_collected_columns = self._produce_column_copies(
  376. local_attributes_for_class,
  377. attribute_is_overridden,
  378. )
  379. else:
  380. locally_collected_columns = {}
  381. bases.append(
  382. (
  383. base,
  384. class_mapped,
  385. local_attributes_for_class,
  386. locally_collected_columns,
  387. )
  388. )
  389. for (
  390. base,
  391. class_mapped,
  392. local_attributes_for_class,
  393. locally_collected_columns,
  394. ) in bases:
  395. # this transfer can also take place as we scan each name
  396. # for finer-grained control of how collected_attributes is
  397. # populated, as this is what impacts column ordering.
  398. # however it's simpler to get it out of the way here.
  399. dict_.update(locally_collected_columns)
  400. for name, obj, is_dataclass in local_attributes_for_class():
  401. if name == "__mapper_args__":
  402. check_decl = _check_declared_props_nocascade(
  403. obj, name, cls
  404. )
  405. if not mapper_args_fn and (not class_mapped or check_decl):
  406. # don't even invoke __mapper_args__ until
  407. # after we've determined everything about the
  408. # mapped table.
  409. # make a copy of it so a class-level dictionary
  410. # is not overwritten when we update column-based
  411. # arguments.
  412. def mapper_args_fn():
  413. return dict(cls.__mapper_args__)
  414. elif name == "__tablename__":
  415. check_decl = _check_declared_props_nocascade(
  416. obj, name, cls
  417. )
  418. if not tablename and (not class_mapped or check_decl):
  419. tablename = cls.__tablename__
  420. elif name == "__table_args__":
  421. check_decl = _check_declared_props_nocascade(
  422. obj, name, cls
  423. )
  424. if not table_args and (not class_mapped or check_decl):
  425. table_args = cls.__table_args__
  426. if not isinstance(
  427. table_args, (tuple, dict, type(None))
  428. ):
  429. raise exc.ArgumentError(
  430. "__table_args__ value must be a tuple, "
  431. "dict, or None"
  432. )
  433. if base is not cls:
  434. inherited_table_args = True
  435. elif class_mapped:
  436. if _is_declarative_props(obj):
  437. util.warn(
  438. "Regular (i.e. not __special__) "
  439. "attribute '%s.%s' uses @declared_attr, "
  440. "but owning class %s is mapped - "
  441. "not applying to subclass %s."
  442. % (base.__name__, name, base, cls)
  443. )
  444. continue
  445. elif base is not cls:
  446. # we're a mixin, abstract base, or something that is
  447. # acting like that for now.
  448. if isinstance(obj, Column):
  449. # already copied columns to the mapped class.
  450. continue
  451. elif isinstance(obj, MapperProperty):
  452. raise exc.InvalidRequestError(
  453. "Mapper properties (i.e. deferred,"
  454. "column_property(), relationship(), etc.) must "
  455. "be declared as @declared_attr callables "
  456. "on declarative mixin classes. For dataclass "
  457. "field() objects, use a lambda:"
  458. )
  459. elif _is_declarative_props(obj):
  460. if obj._cascading:
  461. if name in dict_:
  462. # unfortunately, while we can use the user-
  463. # defined attribute here to allow a clean
  464. # override, if there's another
  465. # subclass below then it still tries to use
  466. # this. not sure if there is enough
  467. # information here to add this as a feature
  468. # later on.
  469. util.warn(
  470. "Attribute '%s' on class %s cannot be "
  471. "processed due to "
  472. "@declared_attr.cascading; "
  473. "skipping" % (name, cls)
  474. )
  475. dict_[name] = column_copies[
  476. obj
  477. ] = ret = obj.__get__(obj, cls)
  478. setattr(cls, name, ret)
  479. else:
  480. if is_dataclass:
  481. # access attribute using normal class access
  482. # first, to see if it's been mapped on a
  483. # superclass. note if the dataclasses.field()
  484. # has "default", this value can be anything.
  485. ret = getattr(cls, name, None)
  486. # so, if it's anything that's not ORM
  487. # mapped, assume we should invoke the
  488. # declared_attr
  489. if not isinstance(ret, InspectionAttr):
  490. ret = obj.fget()
  491. else:
  492. # access attribute using normal class access.
  493. # if the declared attr already took place
  494. # on a superclass that is mapped, then
  495. # this is no longer a declared_attr, it will
  496. # be the InstrumentedAttribute
  497. ret = getattr(cls, name)
  498. # correct for proxies created from hybrid_property
  499. # or similar. note there is no known case that
  500. # produces nested proxies, so we are only
  501. # looking one level deep right now.
  502. if (
  503. isinstance(ret, InspectionAttr)
  504. and ret._is_internal_proxy
  505. and not isinstance(
  506. ret.original_property, MapperProperty
  507. )
  508. ):
  509. ret = ret.descriptor
  510. dict_[name] = column_copies[obj] = ret
  511. if (
  512. isinstance(ret, (Column, MapperProperty))
  513. and ret.doc is None
  514. ):
  515. ret.doc = obj.__doc__
  516. # here, the attribute is some other kind of property that
  517. # we assume is not part of the declarative mapping.
  518. # however, check for some more common mistakes
  519. else:
  520. self._warn_for_decl_attributes(base, name, obj)
  521. elif is_dataclass and (
  522. name not in dict_ or dict_[name] is not obj
  523. ):
  524. # here, we are definitely looking at the target class
  525. # and not a superclass. this is currently a
  526. # dataclass-only path. if the name is only
  527. # a dataclass field and isn't in local cls.__dict__,
  528. # put the object there.
  529. # assert that the dataclass-enabled resolver agrees
  530. # with what we are seeing
  531. assert not attribute_is_overridden(name, obj)
  532. if _is_declarative_props(obj):
  533. obj = obj.fget()
  534. dict_[name] = obj
  535. if inherited_table_args and not tablename:
  536. table_args = None
  537. self.table_args = table_args
  538. self.tablename = tablename
  539. self.mapper_args_fn = mapper_args_fn
  540. def _warn_for_decl_attributes(self, cls, key, c):
  541. if isinstance(c, expression.ColumnClause):
  542. util.warn(
  543. "Attribute '%s' on class %s appears to be a non-schema "
  544. "'sqlalchemy.sql.column()' "
  545. "object; this won't be part of the declarative mapping"
  546. % (key, cls)
  547. )
  548. def _produce_column_copies(
  549. self, attributes_for_class, attribute_is_overridden
  550. ):
  551. cls = self.cls
  552. dict_ = self.dict_
  553. locally_collected_attributes = {}
  554. column_copies = self.column_copies
  555. # copy mixin columns to the mapped class
  556. for name, obj, is_dataclass in attributes_for_class():
  557. if isinstance(obj, Column):
  558. if attribute_is_overridden(name, obj):
  559. # if column has been overridden
  560. # (like by the InstrumentedAttribute of the
  561. # superclass), skip
  562. continue
  563. elif obj.foreign_keys:
  564. raise exc.InvalidRequestError(
  565. "Columns with foreign keys to other columns "
  566. "must be declared as @declared_attr callables "
  567. "on declarative mixin classes. For dataclass "
  568. "field() objects, use a lambda:."
  569. )
  570. elif name not in dict_ and not (
  571. "__table__" in dict_
  572. and (obj.name or name) in dict_["__table__"].c
  573. ):
  574. column_copies[obj] = copy_ = obj._copy()
  575. copy_._creation_order = obj._creation_order
  576. setattr(cls, name, copy_)
  577. locally_collected_attributes[name] = copy_
  578. return locally_collected_attributes
  579. def _extract_mappable_attributes(self):
  580. cls = self.cls
  581. dict_ = self.dict_
  582. our_stuff = self.properties
  583. late_mapped = _get_immediate_cls_attr(
  584. cls, "_sa_decl_prepare_nocascade", strict=True
  585. )
  586. for k in list(dict_):
  587. if k in ("__table__", "__tablename__", "__mapper_args__"):
  588. continue
  589. value = dict_[k]
  590. if _is_declarative_props(value):
  591. if value._cascading:
  592. util.warn(
  593. "Use of @declared_attr.cascading only applies to "
  594. "Declarative 'mixin' and 'abstract' classes. "
  595. "Currently, this flag is ignored on mapped class "
  596. "%s" % self.cls
  597. )
  598. value = getattr(cls, k)
  599. elif (
  600. isinstance(value, QueryableAttribute)
  601. and value.class_ is not cls
  602. and value.key != k
  603. ):
  604. # detect a QueryableAttribute that's already mapped being
  605. # assigned elsewhere in userland, turn into a synonym()
  606. value = SynonymProperty(value.key)
  607. setattr(cls, k, value)
  608. if (
  609. isinstance(value, tuple)
  610. and len(value) == 1
  611. and isinstance(value[0], (Column, MapperProperty))
  612. ):
  613. util.warn(
  614. "Ignoring declarative-like tuple value of attribute "
  615. "'%s': possibly a copy-and-paste error with a comma "
  616. "accidentally placed at the end of the line?" % k
  617. )
  618. continue
  619. elif not isinstance(value, (Column, MapperProperty)):
  620. # using @declared_attr for some object that
  621. # isn't Column/MapperProperty; remove from the dict_
  622. # and place the evaluated value onto the class.
  623. if not k.startswith("__"):
  624. dict_.pop(k)
  625. self._warn_for_decl_attributes(cls, k, value)
  626. if not late_mapped:
  627. setattr(cls, k, value)
  628. continue
  629. # we expect to see the name 'metadata' in some valid cases;
  630. # however at this point we see it's assigned to something trying
  631. # to be mapped, so raise for that.
  632. elif k == "metadata":
  633. raise exc.InvalidRequestError(
  634. "Attribute name 'metadata' is reserved "
  635. "for the MetaData instance when using a "
  636. "declarative base class."
  637. )
  638. our_stuff[k] = value
  639. def _extract_declared_columns(self):
  640. our_stuff = self.properties
  641. # set up attributes in the order they were created
  642. util.sort_dictionary(
  643. our_stuff, key=lambda key: our_stuff[key]._creation_order
  644. )
  645. # extract columns from the class dict
  646. declared_columns = self.declared_columns
  647. name_to_prop_key = collections.defaultdict(set)
  648. for key, c in list(our_stuff.items()):
  649. if isinstance(c, (ColumnProperty, CompositeProperty)):
  650. for col in c.columns:
  651. if isinstance(col, Column) and col.table is None:
  652. _undefer_column_name(key, col)
  653. if not isinstance(c, CompositeProperty):
  654. name_to_prop_key[col.name].add(key)
  655. declared_columns.add(col)
  656. elif isinstance(c, Column):
  657. _undefer_column_name(key, c)
  658. name_to_prop_key[c.name].add(key)
  659. declared_columns.add(c)
  660. # if the column is the same name as the key,
  661. # remove it from the explicit properties dict.
  662. # the normal rules for assigning column-based properties
  663. # will take over, including precedence of columns
  664. # in multi-column ColumnProperties.
  665. if key == c.key:
  666. del our_stuff[key]
  667. for name, keys in name_to_prop_key.items():
  668. if len(keys) > 1:
  669. util.warn(
  670. "On class %r, Column object %r named "
  671. "directly multiple times, "
  672. "only one will be used: %s. "
  673. "Consider using orm.synonym instead"
  674. % (self.classname, name, (", ".join(sorted(keys))))
  675. )
  676. def _setup_table(self, table=None):
  677. cls = self.cls
  678. tablename = self.tablename
  679. table_args = self.table_args
  680. dict_ = self.dict_
  681. declared_columns = self.declared_columns
  682. manager = attributes.manager_of_class(cls)
  683. declared_columns = self.declared_columns = sorted(
  684. declared_columns, key=lambda c: c._creation_order
  685. )
  686. if "__table__" not in dict_ and table is None:
  687. if hasattr(cls, "__table_cls__"):
  688. table_cls = util.unbound_method_to_callable(cls.__table_cls__)
  689. else:
  690. table_cls = Table
  691. if tablename is not None:
  692. args, table_kw = (), {}
  693. if table_args:
  694. if isinstance(table_args, dict):
  695. table_kw = table_args
  696. elif isinstance(table_args, tuple):
  697. if isinstance(table_args[-1], dict):
  698. args, table_kw = table_args[0:-1], table_args[-1]
  699. else:
  700. args = table_args
  701. autoload_with = dict_.get("__autoload_with__")
  702. if autoload_with:
  703. table_kw["autoload_with"] = autoload_with
  704. autoload = dict_.get("__autoload__")
  705. if autoload:
  706. table_kw["autoload"] = True
  707. table = self.set_cls_attribute(
  708. "__table__",
  709. table_cls(
  710. tablename,
  711. self._metadata_for_cls(manager),
  712. *(tuple(declared_columns) + tuple(args)),
  713. **table_kw
  714. ),
  715. )
  716. else:
  717. if table is None:
  718. table = cls.__table__
  719. if declared_columns:
  720. for c in declared_columns:
  721. if not table.c.contains_column(c):
  722. raise exc.ArgumentError(
  723. "Can't add additional column %r when "
  724. "specifying __table__" % c.key
  725. )
  726. self.local_table = table
  727. def _metadata_for_cls(self, manager):
  728. if hasattr(self.cls, "metadata"):
  729. return self.cls.metadata
  730. else:
  731. return manager.registry.metadata
  732. def _setup_inheritance(self, mapper_kw):
  733. table = self.local_table
  734. cls = self.cls
  735. table_args = self.table_args
  736. declared_columns = self.declared_columns
  737. inherits = mapper_kw.get("inherits", None)
  738. if inherits is None:
  739. # since we search for classical mappings now, search for
  740. # multiple mapped bases as well and raise an error.
  741. inherits_search = []
  742. for c in cls.__bases__:
  743. c = _resolve_for_abstract_or_classical(c)
  744. if c is None:
  745. continue
  746. if _declared_mapping_info(
  747. c
  748. ) is not None and not _get_immediate_cls_attr(
  749. c, "_sa_decl_prepare_nocascade", strict=True
  750. ):
  751. if c not in inherits_search:
  752. inherits_search.append(c)
  753. if inherits_search:
  754. if len(inherits_search) > 1:
  755. raise exc.InvalidRequestError(
  756. "Class %s has multiple mapped bases: %r"
  757. % (cls, inherits_search)
  758. )
  759. inherits = inherits_search[0]
  760. elif isinstance(inherits, mapper):
  761. inherits = inherits.class_
  762. self.inherits = inherits
  763. if (
  764. table is None
  765. and self.inherits is None
  766. and not _get_immediate_cls_attr(cls, "__no_table__")
  767. ):
  768. raise exc.InvalidRequestError(
  769. "Class %r does not have a __table__ or __tablename__ "
  770. "specified and does not inherit from an existing "
  771. "table-mapped class." % cls
  772. )
  773. elif self.inherits:
  774. inherited_mapper = _declared_mapping_info(self.inherits)
  775. inherited_table = inherited_mapper.local_table
  776. inherited_persist_selectable = inherited_mapper.persist_selectable
  777. if table is None:
  778. # single table inheritance.
  779. # ensure no table args
  780. if table_args:
  781. raise exc.ArgumentError(
  782. "Can't place __table_args__ on an inherited class "
  783. "with no table."
  784. )
  785. # add any columns declared here to the inherited table.
  786. for c in declared_columns:
  787. if c.name in inherited_table.c:
  788. if inherited_table.c[c.name] is c:
  789. continue
  790. raise exc.ArgumentError(
  791. "Column '%s' on class %s conflicts with "
  792. "existing column '%s'"
  793. % (c, cls, inherited_table.c[c.name])
  794. )
  795. if c.primary_key:
  796. raise exc.ArgumentError(
  797. "Can't place primary key columns on an inherited "
  798. "class with no table."
  799. )
  800. inherited_table.append_column(c)
  801. if (
  802. inherited_persist_selectable is not None
  803. and inherited_persist_selectable is not inherited_table
  804. ):
  805. inherited_persist_selectable._refresh_for_new_column(c)
  806. def _prepare_mapper_arguments(self, mapper_kw):
  807. properties = self.properties
  808. if self.mapper_args_fn:
  809. mapper_args = self.mapper_args_fn()
  810. else:
  811. mapper_args = {}
  812. if mapper_kw:
  813. mapper_args.update(mapper_kw)
  814. if "properties" in mapper_args:
  815. properties = dict(properties)
  816. properties.update(mapper_args["properties"])
  817. # make sure that column copies are used rather
  818. # than the original columns from any mixins
  819. for k in ("version_id_col", "polymorphic_on"):
  820. if k in mapper_args:
  821. v = mapper_args[k]
  822. mapper_args[k] = self.column_copies.get(v, v)
  823. if "inherits" in mapper_args:
  824. inherits_arg = mapper_args["inherits"]
  825. if isinstance(inherits_arg, mapper):
  826. inherits_arg = inherits_arg.class_
  827. if inherits_arg is not self.inherits:
  828. raise exc.InvalidRequestError(
  829. "mapper inherits argument given for non-inheriting "
  830. "class %s" % (mapper_args["inherits"])
  831. )
  832. if self.inherits:
  833. mapper_args["inherits"] = self.inherits
  834. if self.inherits and not mapper_args.get("concrete", False):
  835. # single or joined inheritance
  836. # exclude any cols on the inherited table which are
  837. # not mapped on the parent class, to avoid
  838. # mapping columns specific to sibling/nephew classes
  839. inherited_mapper = _declared_mapping_info(self.inherits)
  840. inherited_table = inherited_mapper.local_table
  841. if "exclude_properties" not in mapper_args:
  842. mapper_args["exclude_properties"] = exclude_properties = set(
  843. [
  844. c.key
  845. for c in inherited_table.c
  846. if c not in inherited_mapper._columntoproperty
  847. ]
  848. ).union(inherited_mapper.exclude_properties or ())
  849. exclude_properties.difference_update(
  850. [c.key for c in self.declared_columns]
  851. )
  852. # look through columns in the current mapper that
  853. # are keyed to a propname different than the colname
  854. # (if names were the same, we'd have popped it out above,
  855. # in which case the mapper makes this combination).
  856. # See if the superclass has a similar column property.
  857. # If so, join them together.
  858. for k, col in list(properties.items()):
  859. if not isinstance(col, expression.ColumnElement):
  860. continue
  861. if k in inherited_mapper._props:
  862. p = inherited_mapper._props[k]
  863. if isinstance(p, ColumnProperty):
  864. # note here we place the subclass column
  865. # first. See [ticket:1892] for background.
  866. properties[k] = [col] + p.columns
  867. result_mapper_args = mapper_args.copy()
  868. result_mapper_args["properties"] = properties
  869. self.mapper_args = result_mapper_args
  870. def map(self, mapper_kw=util.EMPTY_DICT):
  871. self._prepare_mapper_arguments(mapper_kw)
  872. if hasattr(self.cls, "__mapper_cls__"):
  873. mapper_cls = util.unbound_method_to_callable(
  874. self.cls.__mapper_cls__
  875. )
  876. else:
  877. mapper_cls = mapper
  878. return self.set_cls_attribute(
  879. "__mapper__",
  880. mapper_cls(self.cls, self.local_table, **self.mapper_args),
  881. )
  882. @util.preload_module("sqlalchemy.orm.decl_api")
  883. def _as_dc_declaredattr(field_metadata, sa_dataclass_metadata_key):
  884. # wrap lambdas inside dataclass fields inside an ad-hoc declared_attr.
  885. # we can't write it because field.metadata is immutable :( so we have
  886. # to go through extra trouble to compare these
  887. decl_api = util.preloaded.orm_decl_api
  888. obj = field_metadata[sa_dataclass_metadata_key]
  889. if callable(obj) and not isinstance(obj, decl_api.declared_attr):
  890. return decl_api.declared_attr(obj)
  891. else:
  892. return obj
  893. class _DeferredMapperConfig(_ClassScanMapperConfig):
  894. _configs = util.OrderedDict()
  895. def _early_mapping(self, mapper_kw):
  896. pass
  897. @property
  898. def cls(self):
  899. return self._cls()
  900. @cls.setter
  901. def cls(self, class_):
  902. self._cls = weakref.ref(class_, self._remove_config_cls)
  903. self._configs[self._cls] = self
  904. @classmethod
  905. def _remove_config_cls(cls, ref):
  906. cls._configs.pop(ref, None)
  907. @classmethod
  908. def has_cls(cls, class_):
  909. # 2.6 fails on weakref if class_ is an old style class
  910. return isinstance(class_, type) and weakref.ref(class_) in cls._configs
  911. @classmethod
  912. def raise_unmapped_for_cls(cls, class_):
  913. if hasattr(class_, "_sa_raise_deferred_config"):
  914. class_._sa_raise_deferred_config()
  915. raise orm_exc.UnmappedClassError(
  916. class_,
  917. msg="Class %s has a deferred mapping on it. It is not yet "
  918. "usable as a mapped class." % orm_exc._safe_cls_name(class_),
  919. )
  920. @classmethod
  921. def config_for_cls(cls, class_):
  922. return cls._configs[weakref.ref(class_)]
  923. @classmethod
  924. def classes_for_base(cls, base_cls, sort=True):
  925. classes_for_base = [
  926. m
  927. for m, cls_ in [(m, m.cls) for m in cls._configs.values()]
  928. if cls_ is not None and issubclass(cls_, base_cls)
  929. ]
  930. if not sort:
  931. return classes_for_base
  932. all_m_by_cls = dict((m.cls, m) for m in classes_for_base)
  933. tuples = []
  934. for m_cls in all_m_by_cls:
  935. tuples.extend(
  936. (all_m_by_cls[base_cls], all_m_by_cls[m_cls])
  937. for base_cls in m_cls.__bases__
  938. if base_cls in all_m_by_cls
  939. )
  940. return list(topological.sort(tuples, classes_for_base))
  941. def map(self, mapper_kw=util.EMPTY_DICT):
  942. self._configs.pop(self._cls, None)
  943. return super(_DeferredMapperConfig, self).map(mapper_kw)
  944. def _add_attribute(cls, key, value):
  945. """add an attribute to an existing declarative class.
  946. This runs through the logic to determine MapperProperty,
  947. adds it to the Mapper, adds a column to the mapped Table, etc.
  948. """
  949. if "__mapper__" in cls.__dict__:
  950. if isinstance(value, Column):
  951. _undefer_column_name(key, value)
  952. cls.__table__.append_column(value, replace_existing=True)
  953. cls.__mapper__.add_property(key, value)
  954. elif isinstance(value, ColumnProperty):
  955. for col in value.columns:
  956. if isinstance(col, Column) and col.table is None:
  957. _undefer_column_name(key, col)
  958. cls.__table__.append_column(col, replace_existing=True)
  959. cls.__mapper__.add_property(key, value)
  960. elif isinstance(value, MapperProperty):
  961. cls.__mapper__.add_property(key, value)
  962. elif isinstance(value, QueryableAttribute) and value.key != key:
  963. # detect a QueryableAttribute that's already mapped being
  964. # assigned elsewhere in userland, turn into a synonym()
  965. value = SynonymProperty(value.key)
  966. cls.__mapper__.add_property(key, value)
  967. else:
  968. type.__setattr__(cls, key, value)
  969. cls.__mapper__._expire_memoizations()
  970. else:
  971. type.__setattr__(cls, key, value)
  972. def _del_attribute(cls, key):
  973. if (
  974. "__mapper__" in cls.__dict__
  975. and key in cls.__dict__
  976. and not cls.__mapper__._dispose_called
  977. ):
  978. value = cls.__dict__[key]
  979. if isinstance(
  980. value, (Column, ColumnProperty, MapperProperty, QueryableAttribute)
  981. ):
  982. raise NotImplementedError(
  983. "Can't un-map individual mapped attributes on a mapped class."
  984. )
  985. else:
  986. type.__delattr__(cls, key)
  987. cls.__mapper__._expire_memoizations()
  988. else:
  989. type.__delattr__(cls, key)
  990. def _declarative_constructor(self, **kwargs):
  991. """A simple constructor that allows initialization from kwargs.
  992. Sets attributes on the constructed instance using the names and
  993. values in ``kwargs``.
  994. Only keys that are present as
  995. attributes of the instance's class are allowed. These could be,
  996. for example, any mapped columns or relationships.
  997. """
  998. cls_ = type(self)
  999. for k in kwargs:
  1000. if not hasattr(cls_, k):
  1001. raise TypeError(
  1002. "%r is an invalid keyword argument for %s" % (k, cls_.__name__)
  1003. )
  1004. setattr(self, k, kwargs[k])
  1005. _declarative_constructor.__name__ = "__init__"
  1006. def _undefer_column_name(key, column):
  1007. if column.key is None:
  1008. column.key = key
  1009. if column.name is None:
  1010. column.name = key