decl_base.py 43 KB

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