Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/sqlalchemy/ext/declarative/base.py : 70%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# ext/declarative/base.py
2# Copyright (C) 2005-2020 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: http://www.opensource.org/licenses/mit-license.php
7"""Internal implementation for declarative."""
9import collections
10import weakref
12from sqlalchemy.orm import instrumentation
13from . import clsregistry
14from ... import event
15from ... import exc
16from ... import util
17from ...orm import class_mapper
18from ...orm import exc as orm_exc
19from ...orm import mapper
20from ...orm import mapperlib
21from ...orm import synonym
22from ...orm.attributes import QueryableAttribute
23from ...orm.base import _is_mapped_class
24from ...orm.base import InspectionAttr
25from ...orm.interfaces import MapperProperty
26from ...orm.properties import ColumnProperty
27from ...orm.properties import CompositeProperty
28from ...schema import Column
29from ...schema import Table
30from ...sql import expression
31from ...util import topological
34declared_attr = declarative_props = None
37def _declared_mapping_info(cls):
38 # deferred mapping
39 if _DeferredMapperConfig.has_cls(cls):
40 return _DeferredMapperConfig.config_for_cls(cls)
41 # regular mapping
42 elif _is_mapped_class(cls):
43 return class_mapper(cls, configure=False)
44 else:
45 return None
48def _resolve_for_abstract_or_classical(cls):
49 if cls is object:
50 return None
52 if _get_immediate_cls_attr(cls, "__abstract__", strict=True):
53 for sup in cls.__bases__:
54 sup = _resolve_for_abstract_or_classical(sup)
55 if sup is not None:
56 return sup
57 else:
58 return None
59 else:
60 classical = _dive_for_classically_mapped_class(cls)
61 if classical is not None:
62 return classical
63 else:
64 return cls
67def _dive_for_classically_mapped_class(cls):
68 # support issue #4321
70 # if we are within a base hierarchy, don't
71 # search at all for classical mappings
72 if hasattr(cls, "_decl_class_registry"):
73 return None
75 manager = instrumentation.manager_of_class(cls)
76 if manager is not None:
77 return cls
78 else:
79 for sup in cls.__bases__:
80 mapper = _dive_for_classically_mapped_class(sup)
81 if mapper is not None:
82 return sup
83 else:
84 return None
87def _get_immediate_cls_attr(cls, attrname, strict=False):
88 """return an attribute of the class that is either present directly
89 on the class, e.g. not on a superclass, or is from a superclass but
90 this superclass is a non-mapped mixin, that is, not a descendant of
91 the declarative base and is also not classically mapped.
93 This is used to detect attributes that indicate something about
94 a mapped class independently from any mapped classes that it may
95 inherit from.
97 """
98 if not issubclass(cls, object):
99 return None
101 for base in cls.__mro__:
102 _is_declarative_inherits = hasattr(base, "_decl_class_registry")
103 _is_classicial_inherits = (
104 not _is_declarative_inherits
105 and _dive_for_classically_mapped_class(base) is not None
106 )
108 if attrname in base.__dict__ and (
109 base is cls
110 or (
111 (base in cls.__bases__ if strict else True)
112 and not _is_declarative_inherits
113 and not _is_classicial_inherits
114 )
115 ):
116 return getattr(base, attrname)
117 else:
118 return None
121def _as_declarative(cls, classname, dict_):
122 global declared_attr, declarative_props
123 if declared_attr is None:
124 from .api import declared_attr
126 declarative_props = (declared_attr, util.classproperty)
128 if _get_immediate_cls_attr(cls, "__abstract__", strict=True):
129 return
131 _MapperConfig.setup_mapping(cls, classname, dict_)
134def _check_declared_props_nocascade(obj, name, cls):
136 if isinstance(obj, declarative_props):
137 if getattr(obj, "_cascading", False):
138 util.warn(
139 "@declared_attr.cascading is not supported on the %s "
140 "attribute on class %s. This attribute invokes for "
141 "subclasses in any case." % (name, cls)
142 )
143 return True
144 else:
145 return False
148class _MapperConfig(object):
149 @classmethod
150 def setup_mapping(cls, cls_, classname, dict_):
151 defer_map = _get_immediate_cls_attr(
152 cls_, "_sa_decl_prepare_nocascade", strict=True
153 ) or hasattr(cls_, "_sa_decl_prepare")
155 if defer_map:
156 cfg_cls = _DeferredMapperConfig
157 else:
158 cfg_cls = _MapperConfig
160 cfg_cls(cls_, classname, dict_)
162 def __init__(self, cls_, classname, dict_):
164 self.cls = cls_
166 # dict_ will be a dictproxy, which we can't write to, and we need to!
167 self.dict_ = dict(dict_)
168 self.classname = classname
169 self.persist_selectable = None
170 self.properties = util.OrderedDict()
171 self.declared_columns = set()
172 self.column_copies = {}
173 self._setup_declared_events()
175 # temporary registry. While early 1.0 versions
176 # set up the ClassManager here, by API contract
177 # we can't do that until there's a mapper.
178 self.cls._sa_declared_attr_reg = {}
180 self._scan_attributes()
182 mapperlib._CONFIGURE_MUTEX.acquire()
183 try:
184 clsregistry.add_class(self.classname, self.cls)
186 self._extract_mappable_attributes()
188 self._extract_declared_columns()
190 self._setup_table()
192 self._setup_inheritance()
194 self._early_mapping()
195 finally:
196 mapperlib._CONFIGURE_MUTEX.release()
198 def _early_mapping(self):
199 self.map()
201 def _setup_declared_events(self):
202 if _get_immediate_cls_attr(self.cls, "__declare_last__"):
204 @event.listens_for(mapper, "after_configured")
205 def after_configured():
206 self.cls.__declare_last__()
208 if _get_immediate_cls_attr(self.cls, "__declare_first__"):
210 @event.listens_for(mapper, "before_configured")
211 def before_configured():
212 self.cls.__declare_first__()
214 def _scan_attributes(self):
215 cls = self.cls
216 dict_ = self.dict_
217 column_copies = self.column_copies
218 mapper_args_fn = None
219 table_args = inherited_table_args = None
220 tablename = None
222 for base in cls.__mro__:
223 class_mapped = (
224 base is not cls
225 and _declared_mapping_info(base) is not None
226 and not _get_immediate_cls_attr(
227 base, "_sa_decl_prepare_nocascade", strict=True
228 )
229 )
231 if not class_mapped and base is not cls:
232 self._produce_column_copies(base)
234 for name, obj in vars(base).items():
235 if name == "__mapper_args__":
236 check_decl = _check_declared_props_nocascade(
237 obj, name, cls
238 )
239 if not mapper_args_fn and (not class_mapped or check_decl):
240 # don't even invoke __mapper_args__ until
241 # after we've determined everything about the
242 # mapped table.
243 # make a copy of it so a class-level dictionary
244 # is not overwritten when we update column-based
245 # arguments.
246 def mapper_args_fn():
247 return dict(cls.__mapper_args__)
249 elif name == "__tablename__":
250 check_decl = _check_declared_props_nocascade(
251 obj, name, cls
252 )
253 if not tablename and (not class_mapped or check_decl):
254 tablename = cls.__tablename__
255 elif name == "__table_args__":
256 check_decl = _check_declared_props_nocascade(
257 obj, name, cls
258 )
259 if not table_args and (not class_mapped or check_decl):
260 table_args = cls.__table_args__
261 if not isinstance(
262 table_args, (tuple, dict, type(None))
263 ):
264 raise exc.ArgumentError(
265 "__table_args__ value must be a tuple, "
266 "dict, or None"
267 )
268 if base is not cls:
269 inherited_table_args = True
270 elif class_mapped:
271 if isinstance(obj, declarative_props):
272 util.warn(
273 "Regular (i.e. not __special__) "
274 "attribute '%s.%s' uses @declared_attr, "
275 "but owning class %s is mapped - "
276 "not applying to subclass %s."
277 % (base.__name__, name, base, cls)
278 )
279 continue
280 elif base is not cls:
281 # we're a mixin, abstract base, or something that is
282 # acting like that for now.
283 if isinstance(obj, Column):
284 # already copied columns to the mapped class.
285 continue
286 elif isinstance(obj, MapperProperty):
287 raise exc.InvalidRequestError(
288 "Mapper properties (i.e. deferred,"
289 "column_property(), relationship(), etc.) must "
290 "be declared as @declared_attr callables "
291 "on declarative mixin classes."
292 )
293 elif isinstance(obj, declarative_props):
294 oldclassprop = isinstance(obj, util.classproperty)
295 if not oldclassprop and obj._cascading:
296 if name in dict_:
297 # unfortunately, while we can use the user-
298 # defined attribute here to allow a clean
299 # override, if there's another
300 # subclass below then it still tries to use
301 # this. not sure if there is enough
302 # information here to add this as a feature
303 # later on.
304 util.warn(
305 "Attribute '%s' on class %s cannot be "
306 "processed due to "
307 "@declared_attr.cascading; "
308 "skipping" % (name, cls)
309 )
310 dict_[name] = column_copies[
311 obj
312 ] = ret = obj.__get__(obj, cls)
313 setattr(cls, name, ret)
314 else:
315 if oldclassprop:
316 util.warn_deprecated(
317 "Use of sqlalchemy.util.classproperty on "
318 "declarative classes is deprecated."
319 )
320 # access attribute using normal class access
321 ret = getattr(cls, name)
323 # correct for proxies created from hybrid_property
324 # or similar. note there is no known case that
325 # produces nested proxies, so we are only
326 # looking one level deep right now.
327 if (
328 isinstance(ret, InspectionAttr)
329 and ret._is_internal_proxy
330 and not isinstance(
331 ret.original_property, MapperProperty
332 )
333 ):
334 ret = ret.descriptor
336 dict_[name] = column_copies[obj] = ret
337 if (
338 isinstance(ret, (Column, MapperProperty))
339 and ret.doc is None
340 ):
341 ret.doc = obj.__doc__
342 # here, the attribute is some other kind of property that
343 # we assume is not part of the declarative mapping.
344 # however, check for some more common mistakes
345 else:
346 self._warn_for_decl_attributes(base, name, obj)
348 if inherited_table_args and not tablename:
349 table_args = None
351 self.table_args = table_args
352 self.tablename = tablename
353 self.mapper_args_fn = mapper_args_fn
355 def _warn_for_decl_attributes(self, cls, key, c):
356 if isinstance(c, expression.ColumnClause):
357 util.warn(
358 "Attribute '%s' on class %s appears to be a non-schema "
359 "'sqlalchemy.sql.column()' "
360 "object; this won't be part of the declarative mapping"
361 % (key, cls)
362 )
364 def _produce_column_copies(self, base):
365 cls = self.cls
366 dict_ = self.dict_
367 column_copies = self.column_copies
368 # copy mixin columns to the mapped class
369 for name, obj in vars(base).items():
370 if isinstance(obj, Column):
371 if getattr(cls, name) is not obj:
372 # if column has been overridden
373 # (like by the InstrumentedAttribute of the
374 # superclass), skip
375 continue
376 elif obj.foreign_keys:
377 raise exc.InvalidRequestError(
378 "Columns with foreign keys to other columns "
379 "must be declared as @declared_attr callables "
380 "on declarative mixin classes. "
381 )
382 elif name not in dict_ and not (
383 "__table__" in dict_
384 and (obj.name or name) in dict_["__table__"].c
385 ):
386 column_copies[obj] = copy_ = obj.copy()
387 copy_._creation_order = obj._creation_order
388 setattr(cls, name, copy_)
389 dict_[name] = copy_
391 def _extract_mappable_attributes(self):
392 cls = self.cls
393 dict_ = self.dict_
395 our_stuff = self.properties
397 late_mapped = _get_immediate_cls_attr(
398 cls, "_sa_decl_prepare_nocascade", strict=True
399 )
401 for k in list(dict_):
403 if k in ("__table__", "__tablename__", "__mapper_args__"):
404 continue
406 value = dict_[k]
407 if isinstance(value, declarative_props):
408 if isinstance(value, declared_attr) and value._cascading:
409 util.warn(
410 "Use of @declared_attr.cascading only applies to "
411 "Declarative 'mixin' and 'abstract' classes. "
412 "Currently, this flag is ignored on mapped class "
413 "%s" % self.cls
414 )
416 value = getattr(cls, k)
418 elif (
419 isinstance(value, QueryableAttribute)
420 and value.class_ is not cls
421 and value.key != k
422 ):
423 # detect a QueryableAttribute that's already mapped being
424 # assigned elsewhere in userland, turn into a synonym()
425 value = synonym(value.key)
426 setattr(cls, k, value)
428 if (
429 isinstance(value, tuple)
430 and len(value) == 1
431 and isinstance(value[0], (Column, MapperProperty))
432 ):
433 util.warn(
434 "Ignoring declarative-like tuple value of attribute "
435 "'%s': possibly a copy-and-paste error with a comma "
436 "accidentally placed at the end of the line?" % k
437 )
438 continue
439 elif not isinstance(value, (Column, MapperProperty)):
440 # using @declared_attr for some object that
441 # isn't Column/MapperProperty; remove from the dict_
442 # and place the evaluated value onto the class.
443 if not k.startswith("__"):
444 dict_.pop(k)
445 self._warn_for_decl_attributes(cls, k, value)
446 if not late_mapped:
447 setattr(cls, k, value)
448 continue
449 # we expect to see the name 'metadata' in some valid cases;
450 # however at this point we see it's assigned to something trying
451 # to be mapped, so raise for that.
452 elif k == "metadata":
453 raise exc.InvalidRequestError(
454 "Attribute name 'metadata' is reserved "
455 "for the MetaData instance when using a "
456 "declarative base class."
457 )
458 prop = clsregistry._deferred_relationship(cls, value)
459 our_stuff[k] = prop
461 def _extract_declared_columns(self):
462 our_stuff = self.properties
464 # set up attributes in the order they were created
465 our_stuff.sort(key=lambda key: our_stuff[key]._creation_order)
467 # extract columns from the class dict
468 declared_columns = self.declared_columns
469 name_to_prop_key = collections.defaultdict(set)
470 for key, c in list(our_stuff.items()):
471 if isinstance(c, (ColumnProperty, CompositeProperty)):
472 for col in c.columns:
473 if isinstance(col, Column) and col.table is None:
474 _undefer_column_name(key, col)
475 if not isinstance(c, CompositeProperty):
476 name_to_prop_key[col.name].add(key)
477 declared_columns.add(col)
478 elif isinstance(c, Column):
479 _undefer_column_name(key, c)
480 name_to_prop_key[c.name].add(key)
481 declared_columns.add(c)
482 # if the column is the same name as the key,
483 # remove it from the explicit properties dict.
484 # the normal rules for assigning column-based properties
485 # will take over, including precedence of columns
486 # in multi-column ColumnProperties.
487 if key == c.key:
488 del our_stuff[key]
490 for name, keys in name_to_prop_key.items():
491 if len(keys) > 1:
492 util.warn(
493 "On class %r, Column object %r named "
494 "directly multiple times, "
495 "only one will be used: %s. "
496 "Consider using orm.synonym instead"
497 % (self.classname, name, (", ".join(sorted(keys))))
498 )
500 def _setup_table(self):
501 cls = self.cls
502 tablename = self.tablename
503 table_args = self.table_args
504 dict_ = self.dict_
505 declared_columns = self.declared_columns
507 declared_columns = self.declared_columns = sorted(
508 declared_columns, key=lambda c: c._creation_order
509 )
510 table = None
512 if hasattr(cls, "__table_cls__"):
513 table_cls = util.unbound_method_to_callable(cls.__table_cls__)
514 else:
515 table_cls = Table
517 if "__table__" not in dict_:
518 if tablename is not None:
520 args, table_kw = (), {}
521 if table_args:
522 if isinstance(table_args, dict):
523 table_kw = table_args
524 elif isinstance(table_args, tuple):
525 if isinstance(table_args[-1], dict):
526 args, table_kw = table_args[0:-1], table_args[-1]
527 else:
528 args = table_args
530 autoload = dict_.get("__autoload__")
531 if autoload:
532 table_kw["autoload"] = True
534 cls.__table__ = table = table_cls(
535 tablename,
536 cls.metadata,
537 *(tuple(declared_columns) + tuple(args)),
538 **table_kw
539 )
540 else:
541 table = cls.__table__
542 if declared_columns:
543 for c in declared_columns:
544 if not table.c.contains_column(c):
545 raise exc.ArgumentError(
546 "Can't add additional column %r when "
547 "specifying __table__" % c.key
548 )
549 self.local_table = table
551 def _setup_inheritance(self):
552 table = self.local_table
553 cls = self.cls
554 table_args = self.table_args
555 declared_columns = self.declared_columns
557 # since we search for classical mappings now, search for
558 # multiple mapped bases as well and raise an error.
559 inherits = []
560 for c in cls.__bases__:
561 c = _resolve_for_abstract_or_classical(c)
562 if c is None:
563 continue
564 if _declared_mapping_info(
565 c
566 ) is not None and not _get_immediate_cls_attr(
567 c, "_sa_decl_prepare_nocascade", strict=True
568 ):
569 inherits.append(c)
571 if inherits:
572 if len(inherits) > 1:
573 raise exc.InvalidRequestError(
574 "Class %s has multiple mapped bases: %r" % (cls, inherits)
575 )
576 self.inherits = inherits[0]
577 else:
578 self.inherits = None
580 if (
581 table is None
582 and self.inherits is None
583 and not _get_immediate_cls_attr(cls, "__no_table__")
584 ):
586 raise exc.InvalidRequestError(
587 "Class %r does not have a __table__ or __tablename__ "
588 "specified and does not inherit from an existing "
589 "table-mapped class." % cls
590 )
591 elif self.inherits:
592 inherited_mapper = _declared_mapping_info(self.inherits)
593 inherited_table = inherited_mapper.local_table
594 inherited_persist_selectable = inherited_mapper.persist_selectable
596 if table is None:
597 # single table inheritance.
598 # ensure no table args
599 if table_args:
600 raise exc.ArgumentError(
601 "Can't place __table_args__ on an inherited class "
602 "with no table."
603 )
604 # add any columns declared here to the inherited table.
605 for c in declared_columns:
606 if c.name in inherited_table.c:
607 if inherited_table.c[c.name] is c:
608 continue
609 raise exc.ArgumentError(
610 "Column '%s' on class %s conflicts with "
611 "existing column '%s'"
612 % (c, cls, inherited_table.c[c.name])
613 )
614 if c.primary_key:
615 raise exc.ArgumentError(
616 "Can't place primary key columns on an inherited "
617 "class with no table."
618 )
619 inherited_table.append_column(c)
620 if (
621 inherited_persist_selectable is not None
622 and inherited_persist_selectable is not inherited_table
623 ):
624 inherited_persist_selectable._refresh_for_new_column(c)
626 def _prepare_mapper_arguments(self):
627 properties = self.properties
628 if self.mapper_args_fn:
629 mapper_args = self.mapper_args_fn()
630 else:
631 mapper_args = {}
633 # make sure that column copies are used rather
634 # than the original columns from any mixins
635 for k in ("version_id_col", "polymorphic_on"):
636 if k in mapper_args:
637 v = mapper_args[k]
638 mapper_args[k] = self.column_copies.get(v, v)
640 assert (
641 "inherits" not in mapper_args
642 ), "Can't specify 'inherits' explicitly with declarative mappings"
644 if self.inherits:
645 mapper_args["inherits"] = self.inherits
647 if self.inherits and not mapper_args.get("concrete", False):
648 # single or joined inheritance
649 # exclude any cols on the inherited table which are
650 # not mapped on the parent class, to avoid
651 # mapping columns specific to sibling/nephew classes
652 inherited_mapper = _declared_mapping_info(self.inherits)
653 inherited_table = inherited_mapper.local_table
655 if "exclude_properties" not in mapper_args:
656 mapper_args["exclude_properties"] = exclude_properties = set(
657 [
658 c.key
659 for c in inherited_table.c
660 if c not in inherited_mapper._columntoproperty
661 ]
662 ).union(inherited_mapper.exclude_properties or ())
663 exclude_properties.difference_update(
664 [c.key for c in self.declared_columns]
665 )
667 # look through columns in the current mapper that
668 # are keyed to a propname different than the colname
669 # (if names were the same, we'd have popped it out above,
670 # in which case the mapper makes this combination).
671 # See if the superclass has a similar column property.
672 # If so, join them together.
673 for k, col in list(properties.items()):
674 if not isinstance(col, expression.ColumnElement):
675 continue
676 if k in inherited_mapper._props:
677 p = inherited_mapper._props[k]
678 if isinstance(p, ColumnProperty):
679 # note here we place the subclass column
680 # first. See [ticket:1892] for background.
681 properties[k] = [col] + p.columns
682 result_mapper_args = mapper_args.copy()
683 result_mapper_args["properties"] = properties
684 self.mapper_args = result_mapper_args
686 def map(self):
687 self._prepare_mapper_arguments()
688 if hasattr(self.cls, "__mapper_cls__"):
689 mapper_cls = util.unbound_method_to_callable(
690 self.cls.__mapper_cls__
691 )
692 else:
693 mapper_cls = mapper
695 self.cls.__mapper__ = mp_ = mapper_cls(
696 self.cls, self.local_table, **self.mapper_args
697 )
698 del self.cls._sa_declared_attr_reg
699 return mp_
702class _DeferredMapperConfig(_MapperConfig):
703 _configs = util.OrderedDict()
705 def _early_mapping(self):
706 pass
708 @property
709 def cls(self):
710 return self._cls()
712 @cls.setter
713 def cls(self, class_):
714 self._cls = weakref.ref(class_, self._remove_config_cls)
715 self._configs[self._cls] = self
717 @classmethod
718 def _remove_config_cls(cls, ref):
719 cls._configs.pop(ref, None)
721 @classmethod
722 def has_cls(cls, class_):
723 # 2.6 fails on weakref if class_ is an old style class
724 return isinstance(class_, type) and weakref.ref(class_) in cls._configs
726 @classmethod
727 def raise_unmapped_for_cls(cls, class_):
728 if hasattr(class_, "_sa_raise_deferred_config"):
729 class_._sa_raise_deferred_config()
731 raise orm_exc.UnmappedClassError(
732 class_,
733 msg="Class %s has a deferred mapping on it. It is not yet "
734 "usable as a mapped class." % orm_exc._safe_cls_name(class_),
735 )
737 @classmethod
738 def config_for_cls(cls, class_):
739 return cls._configs[weakref.ref(class_)]
741 @classmethod
742 def classes_for_base(cls, base_cls, sort=True):
743 classes_for_base = [
744 m
745 for m, cls_ in [(m, m.cls) for m in cls._configs.values()]
746 if cls_ is not None and issubclass(cls_, base_cls)
747 ]
749 if not sort:
750 return classes_for_base
752 all_m_by_cls = dict((m.cls, m) for m in classes_for_base)
754 tuples = []
755 for m_cls in all_m_by_cls:
756 tuples.extend(
757 (all_m_by_cls[base_cls], all_m_by_cls[m_cls])
758 for base_cls in m_cls.__bases__
759 if base_cls in all_m_by_cls
760 )
761 return list(topological.sort(tuples, classes_for_base))
763 def map(self):
764 self._configs.pop(self._cls, None)
765 return super(_DeferredMapperConfig, self).map()
768def _add_attribute(cls, key, value):
769 """add an attribute to an existing declarative class.
771 This runs through the logic to determine MapperProperty,
772 adds it to the Mapper, adds a column to the mapped Table, etc.
774 """
776 if "__mapper__" in cls.__dict__:
777 if isinstance(value, Column):
778 _undefer_column_name(key, value)
779 cls.__table__.append_column(value)
780 cls.__mapper__.add_property(key, value)
781 elif isinstance(value, ColumnProperty):
782 for col in value.columns:
783 if isinstance(col, Column) and col.table is None:
784 _undefer_column_name(key, col)
785 cls.__table__.append_column(col)
786 cls.__mapper__.add_property(key, value)
787 elif isinstance(value, MapperProperty):
788 cls.__mapper__.add_property(
789 key, clsregistry._deferred_relationship(cls, value)
790 )
791 elif isinstance(value, QueryableAttribute) and value.key != key:
792 # detect a QueryableAttribute that's already mapped being
793 # assigned elsewhere in userland, turn into a synonym()
794 value = synonym(value.key)
795 cls.__mapper__.add_property(
796 key, clsregistry._deferred_relationship(cls, value)
797 )
798 else:
799 type.__setattr__(cls, key, value)
800 cls.__mapper__._expire_memoizations()
801 else:
802 type.__setattr__(cls, key, value)
805def _del_attribute(cls, key):
807 if (
808 "__mapper__" in cls.__dict__
809 and key in cls.__dict__
810 and not cls.__mapper__._dispose_called
811 ):
812 value = cls.__dict__[key]
813 if isinstance(
814 value, (Column, ColumnProperty, MapperProperty, QueryableAttribute)
815 ):
816 raise NotImplementedError(
817 "Can't un-map individual mapped attributes on a mapped class."
818 )
819 else:
820 type.__delattr__(cls, key)
821 cls.__mapper__._expire_memoizations()
822 else:
823 type.__delattr__(cls, key)
826def _declarative_constructor(self, **kwargs):
827 """A simple constructor that allows initialization from kwargs.
829 Sets attributes on the constructed instance using the names and
830 values in ``kwargs``.
832 Only keys that are present as
833 attributes of the instance's class are allowed. These could be,
834 for example, any mapped columns or relationships.
835 """
836 cls_ = type(self)
837 for k in kwargs:
838 if not hasattr(cls_, k):
839 raise TypeError(
840 "%r is an invalid keyword argument for %s" % (k, cls_.__name__)
841 )
842 setattr(self, k, kwargs[k])
845_declarative_constructor.__name__ = "__init__"
848def _undefer_column_name(key, column):
849 if column.key is None:
850 column.key = key
851 if column.name is None:
852 column.name = key