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

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/api.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"""Public API functions and helpers for declarative."""
10import re
11import weakref
13from .base import _add_attribute
14from .base import _as_declarative
15from .base import _declarative_constructor
16from .base import _DeferredMapperConfig
17from .base import _del_attribute
18from .clsregistry import _class_resolver
19from ... import exc
20from ... import inspection
21from ... import util
22from ...orm import attributes
23from ...orm import comparable_property
24from ...orm import exc as orm_exc
25from ...orm import interfaces
26from ...orm import properties
27from ...orm import synonym as _orm_synonym
28from ...orm.base import _inspect_mapped_class
29from ...orm.base import _mapper_or_none
30from ...orm.util import polymorphic_union
31from ...schema import MetaData
32from ...schema import Table
33from ...util import hybridmethod
34from ...util import hybridproperty
35from ...util import OrderedDict
38def instrument_declarative(cls, registry, metadata):
39 """Given a class, configure the class declaratively,
40 using the given registry, which can be any dictionary, and
41 MetaData object.
43 """
44 if "_decl_class_registry" in cls.__dict__:
45 raise exc.InvalidRequestError(
46 "Class %r already has been " "instrumented declaratively" % cls
47 )
48 cls._decl_class_registry = registry
49 cls.metadata = metadata
50 _as_declarative(cls, cls.__name__, cls.__dict__)
53def has_inherited_table(cls):
54 """Given a class, return True if any of the classes it inherits from has a
55 mapped table, otherwise return False.
57 This is used in declarative mixins to build attributes that behave
58 differently for the base class vs. a subclass in an inheritance
59 hierarchy.
61 .. seealso::
63 :ref:`decl_mixin_inheritance`
65 """
66 for class_ in cls.__mro__[1:]:
67 if getattr(class_, "__table__", None) is not None:
68 return True
69 return False
72class DeclarativeMeta(type):
73 def __init__(cls, classname, bases, dict_):
74 if "_decl_class_registry" not in cls.__dict__:
75 _as_declarative(cls, classname, cls.__dict__)
76 type.__init__(cls, classname, bases, dict_)
78 def __setattr__(cls, key, value):
79 _add_attribute(cls, key, value)
81 def __delattr__(cls, key):
82 _del_attribute(cls, key)
85def synonym_for(name, map_column=False):
86 """Decorator that produces an :func:`_orm.synonym`
87 attribute in conjunction
88 with a Python descriptor.
90 The function being decorated is passed to :func:`_orm.synonym` as the
91 :paramref:`.orm.synonym.descriptor` parameter::
93 class MyClass(Base):
94 __tablename__ = 'my_table'
96 id = Column(Integer, primary_key=True)
97 _job_status = Column("job_status", String(50))
99 @synonym_for("job_status")
100 @property
101 def job_status(self):
102 return "Status: %s" % self._job_status
104 The :ref:`hybrid properties <mapper_hybrids>` feature of SQLAlchemy
105 is typically preferred instead of synonyms, which is a more legacy
106 feature.
108 .. seealso::
110 :ref:`synonyms` - Overview of synonyms
112 :func:`_orm.synonym` - the mapper-level function
114 :ref:`mapper_hybrids` - The Hybrid Attribute extension provides an
115 updated approach to augmenting attribute behavior more flexibly than
116 can be achieved with synonyms.
118 """
120 def decorate(fn):
121 return _orm_synonym(name, map_column=map_column, descriptor=fn)
123 return decorate
126def comparable_using(comparator_factory):
127 """Decorator, allow a Python @property to be used in query criteria.
129 This is a decorator front end to
130 :func:`~sqlalchemy.orm.comparable_property` that passes
131 through the comparator_factory and the function being decorated::
133 @comparable_using(MyComparatorType)
134 @property
135 def prop(self):
136 return 'special sauce'
138 The regular ``comparable_property()`` is also usable directly in a
139 declarative setting and may be convenient for read/write properties::
141 prop = comparable_property(MyComparatorType)
143 """
145 def decorate(fn):
146 return comparable_property(comparator_factory, fn)
148 return decorate
151class declared_attr(interfaces._MappedAttribute, property):
152 """Mark a class-level method as representing the definition of
153 a mapped property or special declarative member name.
155 @declared_attr turns the attribute into a scalar-like
156 property that can be invoked from the uninstantiated class.
157 Declarative treats attributes specifically marked with
158 @declared_attr as returning a construct that is specific
159 to mapping or declarative table configuration. The name
160 of the attribute is that of what the non-dynamic version
161 of the attribute would be.
163 @declared_attr is more often than not applicable to mixins,
164 to define relationships that are to be applied to different
165 implementors of the class::
167 class ProvidesUser(object):
168 "A mixin that adds a 'user' relationship to classes."
170 @declared_attr
171 def user(self):
172 return relationship("User")
174 It also can be applied to mapped classes, such as to provide
175 a "polymorphic" scheme for inheritance::
177 class Employee(Base):
178 id = Column(Integer, primary_key=True)
179 type = Column(String(50), nullable=False)
181 @declared_attr
182 def __tablename__(cls):
183 return cls.__name__.lower()
185 @declared_attr
186 def __mapper_args__(cls):
187 if cls.__name__ == 'Employee':
188 return {
189 "polymorphic_on":cls.type,
190 "polymorphic_identity":"Employee"
191 }
192 else:
193 return {"polymorphic_identity":cls.__name__}
195 """
197 def __init__(self, fget, cascading=False):
198 super(declared_attr, self).__init__(fget)
199 self.__doc__ = fget.__doc__
200 self._cascading = cascading
202 def __get__(desc, self, cls):
203 reg = cls.__dict__.get("_sa_declared_attr_reg", None)
204 if reg is None:
205 if (
206 not re.match(r"^__.+__$", desc.fget.__name__)
207 and attributes.manager_of_class(cls) is None
208 ):
209 util.warn(
210 "Unmanaged access of declarative attribute %s from "
211 "non-mapped class %s" % (desc.fget.__name__, cls.__name__)
212 )
213 return desc.fget(cls)
214 elif desc in reg:
215 return reg[desc]
216 else:
217 reg[desc] = obj = desc.fget(cls)
218 return obj
220 @hybridmethod
221 def _stateful(cls, **kw):
222 return _stateful_declared_attr(**kw)
224 @hybridproperty
225 def cascading(cls):
226 """Mark a :class:`.declared_attr` as cascading.
228 This is a special-use modifier which indicates that a column
229 or MapperProperty-based declared attribute should be configured
230 distinctly per mapped subclass, within a mapped-inheritance scenario.
232 .. warning::
234 The :attr:`.declared_attr.cascading` modifier has several
235 limitations:
237 * The flag **only** applies to the use of :class:`.declared_attr`
238 on declarative mixin classes and ``__abstract__`` classes; it
239 currently has no effect when used on a mapped class directly.
241 * The flag **only** applies to normally-named attributes, e.g.
242 not any special underscore attributes such as ``__tablename__``.
243 On these attributes it has **no** effect.
245 * The flag currently **does not allow further overrides** down
246 the class hierarchy; if a subclass tries to override the
247 attribute, a warning is emitted and the overridden attribute
248 is skipped. This is a limitation that it is hoped will be
249 resolved at some point.
251 Below, both MyClass as well as MySubClass will have a distinct
252 ``id`` Column object established::
254 class HasIdMixin(object):
255 @declared_attr.cascading
256 def id(cls):
257 if has_inherited_table(cls):
258 return Column(
259 ForeignKey('myclass.id'), primary_key=True)
260 else:
261 return Column(Integer, primary_key=True)
263 class MyClass(HasIdMixin, Base):
264 __tablename__ = 'myclass'
265 # ...
267 class MySubClass(MyClass):
268 ""
269 # ...
271 The behavior of the above configuration is that ``MySubClass``
272 will refer to both its own ``id`` column as well as that of
273 ``MyClass`` underneath the attribute named ``some_id``.
275 .. seealso::
277 :ref:`declarative_inheritance`
279 :ref:`mixin_inheritance_columns`
282 """
283 return cls._stateful(cascading=True)
286class _stateful_declared_attr(declared_attr):
287 def __init__(self, **kw):
288 self.kw = kw
290 def _stateful(self, **kw):
291 new_kw = self.kw.copy()
292 new_kw.update(kw)
293 return _stateful_declared_attr(**new_kw)
295 def __call__(self, fn):
296 return declared_attr(fn, **self.kw)
299def declarative_base(
300 bind=None,
301 metadata=None,
302 mapper=None,
303 cls=object,
304 name="Base",
305 constructor=_declarative_constructor,
306 class_registry=None,
307 metaclass=DeclarativeMeta,
308):
309 r"""Construct a base class for declarative class definitions.
311 The new base class will be given a metaclass that produces
312 appropriate :class:`~sqlalchemy.schema.Table` objects and makes
313 the appropriate :func:`~sqlalchemy.orm.mapper` calls based on the
314 information provided declaratively in the class and any subclasses
315 of the class.
317 :param bind: An optional
318 :class:`~sqlalchemy.engine.Connectable`, will be assigned
319 the ``bind`` attribute on the :class:`~sqlalchemy.schema.MetaData`
320 instance.
322 :param metadata:
323 An optional :class:`~sqlalchemy.schema.MetaData` instance. All
324 :class:`~sqlalchemy.schema.Table` objects implicitly declared by
325 subclasses of the base will share this MetaData. A MetaData instance
326 will be created if none is provided. The
327 :class:`~sqlalchemy.schema.MetaData` instance will be available via the
328 `metadata` attribute of the generated declarative base class.
330 :param mapper:
331 An optional callable, defaults to :func:`~sqlalchemy.orm.mapper`. Will
332 be used to map subclasses to their Tables.
334 :param cls:
335 Defaults to :class:`object`. A type to use as the base for the generated
336 declarative base class. May be a class or tuple of classes.
338 :param name:
339 Defaults to ``Base``. The display name for the generated
340 class. Customizing this is not required, but can improve clarity in
341 tracebacks and debugging.
343 :param constructor:
344 Defaults to
345 :func:`~sqlalchemy.ext.declarative.base._declarative_constructor`, an
346 __init__ implementation that assigns \**kwargs for declared
347 fields and relationships to an instance. If ``None`` is supplied,
348 no __init__ will be provided and construction will fall back to
349 cls.__init__ by way of the normal Python semantics.
351 :param class_registry: optional dictionary that will serve as the
352 registry of class names-> mapped classes when string names
353 are used to identify classes inside of :func:`_orm.relationship`
354 and others. Allows two or more declarative base classes
355 to share the same registry of class names for simplified
356 inter-base relationships.
358 :param metaclass:
359 Defaults to :class:`.DeclarativeMeta`. A metaclass or __metaclass__
360 compatible callable to use as the meta type of the generated
361 declarative base class.
363 .. versionchanged:: 1.1 if :paramref:`.declarative_base.cls` is a
364 single class (rather than a tuple), the constructed base class will
365 inherit its docstring.
367 .. seealso::
369 :func:`.as_declarative`
371 """
372 lcl_metadata = metadata or MetaData()
373 if bind:
374 lcl_metadata.bind = bind
376 if class_registry is None:
377 class_registry = weakref.WeakValueDictionary()
379 bases = not isinstance(cls, tuple) and (cls,) or cls
380 class_dict = dict(
381 _decl_class_registry=class_registry, metadata=lcl_metadata
382 )
384 if isinstance(cls, type):
385 class_dict["__doc__"] = cls.__doc__
387 if constructor:
388 class_dict["__init__"] = constructor
389 if mapper:
390 class_dict["__mapper_cls__"] = mapper
392 return metaclass(name, bases, class_dict)
395def as_declarative(**kw):
396 """
397 Class decorator for :func:`.declarative_base`.
399 Provides a syntactical shortcut to the ``cls`` argument
400 sent to :func:`.declarative_base`, allowing the base class
401 to be converted in-place to a "declarative" base::
403 from sqlalchemy.ext.declarative import as_declarative
405 @as_declarative()
406 class Base(object):
407 @declared_attr
408 def __tablename__(cls):
409 return cls.__name__.lower()
410 id = Column(Integer, primary_key=True)
412 class MyMappedClass(Base):
413 # ...
415 All keyword arguments passed to :func:`.as_declarative` are passed
416 along to :func:`.declarative_base`.
418 .. seealso::
420 :func:`.declarative_base`
422 """
424 def decorate(cls):
425 kw["cls"] = cls
426 kw["name"] = cls.__name__
427 return declarative_base(**kw)
429 return decorate
432class ConcreteBase(object):
433 """A helper class for 'concrete' declarative mappings.
435 :class:`.ConcreteBase` will use the :func:`.polymorphic_union`
436 function automatically, against all tables mapped as a subclass
437 to this class. The function is called via the
438 ``__declare_last__()`` function, which is essentially
439 a hook for the :meth:`.after_configured` event.
441 :class:`.ConcreteBase` produces a mapped
442 table for the class itself. Compare to :class:`.AbstractConcreteBase`,
443 which does not.
445 Example::
447 from sqlalchemy.ext.declarative import ConcreteBase
449 class Employee(ConcreteBase, Base):
450 __tablename__ = 'employee'
451 employee_id = Column(Integer, primary_key=True)
452 name = Column(String(50))
453 __mapper_args__ = {
454 'polymorphic_identity':'employee',
455 'concrete':True}
457 class Manager(Employee):
458 __tablename__ = 'manager'
459 employee_id = Column(Integer, primary_key=True)
460 name = Column(String(50))
461 manager_data = Column(String(40))
462 __mapper_args__ = {
463 'polymorphic_identity':'manager',
464 'concrete':True}
466 .. seealso::
468 :class:`.AbstractConcreteBase`
470 :ref:`concrete_inheritance`
473 """
475 @classmethod
476 def _create_polymorphic_union(cls, mappers):
477 return polymorphic_union(
478 OrderedDict(
479 (mp.polymorphic_identity, mp.local_table) for mp in mappers
480 ),
481 "type",
482 "pjoin",
483 )
485 @classmethod
486 def __declare_first__(cls):
487 m = cls.__mapper__
488 if m.with_polymorphic:
489 return
491 mappers = list(m.self_and_descendants)
492 pjoin = cls._create_polymorphic_union(mappers)
493 m._set_with_polymorphic(("*", pjoin))
494 m._set_polymorphic_on(pjoin.c.type)
497class AbstractConcreteBase(ConcreteBase):
498 """A helper class for 'concrete' declarative mappings.
500 :class:`.AbstractConcreteBase` will use the :func:`.polymorphic_union`
501 function automatically, against all tables mapped as a subclass
502 to this class. The function is called via the
503 ``__declare_last__()`` function, which is essentially
504 a hook for the :meth:`.after_configured` event.
506 :class:`.AbstractConcreteBase` does produce a mapped class
507 for the base class, however it is not persisted to any table; it
508 is instead mapped directly to the "polymorphic" selectable directly
509 and is only used for selecting. Compare to :class:`.ConcreteBase`,
510 which does create a persisted table for the base class.
512 .. note::
514 The :class:`.AbstractConcreteBase` class does not intend to set up the
515 mapping for the base class until all the subclasses have been defined,
516 as it needs to create a mapping against a selectable that will include
517 all subclass tables. In order to achieve this, it waits for the
518 **mapper configuration event** to occur, at which point it scans
519 through all the configured subclasses and sets up a mapping that will
520 query against all subclasses at once.
522 While this event is normally invoked automatically, in the case of
523 :class:`.AbstractConcreteBase`, it may be necessary to invoke it
524 explicitly after **all** subclass mappings are defined, if the first
525 operation is to be a query against this base class. To do so, invoke
526 :func:`.configure_mappers` once all the desired classes have been
527 configured::
529 from sqlalchemy.orm import configure_mappers
531 configure_mappers()
533 .. seealso::
535 :func:`_orm.configure_mappers`
538 Example::
540 from sqlalchemy.ext.declarative import AbstractConcreteBase
542 class Employee(AbstractConcreteBase, Base):
543 pass
545 class Manager(Employee):
546 __tablename__ = 'manager'
547 employee_id = Column(Integer, primary_key=True)
548 name = Column(String(50))
549 manager_data = Column(String(40))
551 __mapper_args__ = {
552 'polymorphic_identity':'manager',
553 'concrete':True}
555 configure_mappers()
557 The abstract base class is handled by declarative in a special way;
558 at class configuration time, it behaves like a declarative mixin
559 or an ``__abstract__`` base class. Once classes are configured
560 and mappings are produced, it then gets mapped itself, but
561 after all of its descendants. This is a very unique system of mapping
562 not found in any other SQLAlchemy system.
564 Using this approach, we can specify columns and properties
565 that will take place on mapped subclasses, in the way that
566 we normally do as in :ref:`declarative_mixins`::
568 class Company(Base):
569 __tablename__ = 'company'
570 id = Column(Integer, primary_key=True)
572 class Employee(AbstractConcreteBase, Base):
573 employee_id = Column(Integer, primary_key=True)
575 @declared_attr
576 def company_id(cls):
577 return Column(ForeignKey('company.id'))
579 @declared_attr
580 def company(cls):
581 return relationship("Company")
583 class Manager(Employee):
584 __tablename__ = 'manager'
586 name = Column(String(50))
587 manager_data = Column(String(40))
589 __mapper_args__ = {
590 'polymorphic_identity':'manager',
591 'concrete':True}
593 configure_mappers()
595 When we make use of our mappings however, both ``Manager`` and
596 ``Employee`` will have an independently usable ``.company`` attribute::
598 session.query(Employee).filter(Employee.company.has(id=5))
600 .. versionchanged:: 1.0.0 - The mechanics of :class:`.AbstractConcreteBase`
601 have been reworked to support relationships established directly
602 on the abstract base, without any special configurational steps.
604 .. seealso::
606 :class:`.ConcreteBase`
608 :ref:`concrete_inheritance`
610 """
612 __no_table__ = True
614 @classmethod
615 def __declare_first__(cls):
616 cls._sa_decl_prepare_nocascade()
618 @classmethod
619 def _sa_decl_prepare_nocascade(cls):
620 if getattr(cls, "__mapper__", None):
621 return
623 to_map = _DeferredMapperConfig.config_for_cls(cls)
625 # can't rely on 'self_and_descendants' here
626 # since technically an immediate subclass
627 # might not be mapped, but a subclass
628 # may be.
629 mappers = []
630 stack = list(cls.__subclasses__())
631 while stack:
632 klass = stack.pop()
633 stack.extend(klass.__subclasses__())
634 mn = _mapper_or_none(klass)
635 if mn is not None:
636 mappers.append(mn)
637 pjoin = cls._create_polymorphic_union(mappers)
639 # For columns that were declared on the class, these
640 # are normally ignored with the "__no_table__" mapping,
641 # unless they have a different attribute key vs. col name
642 # and are in the properties argument.
643 # In that case, ensure we update the properties entry
644 # to the correct column from the pjoin target table.
645 declared_cols = set(to_map.declared_columns)
646 for k, v in list(to_map.properties.items()):
647 if v in declared_cols:
648 to_map.properties[k] = pjoin.c[v.key]
650 to_map.local_table = pjoin
652 m_args = to_map.mapper_args_fn or dict
654 def mapper_args():
655 args = m_args()
656 args["polymorphic_on"] = pjoin.c.type
657 return args
659 to_map.mapper_args_fn = mapper_args
661 m = to_map.map()
663 for scls in cls.__subclasses__():
664 sm = _mapper_or_none(scls)
665 if sm and sm.concrete and cls in scls.__bases__:
666 sm._set_concrete_base(m)
668 @classmethod
669 def _sa_raise_deferred_config(cls):
670 raise orm_exc.UnmappedClassError(
671 cls,
672 msg="Class %s is a subclass of AbstractConcreteBase and "
673 "has a mapping pending until all subclasses are defined. "
674 "Call the sqlalchemy.orm.configure_mappers() function after "
675 "all subclasses have been defined to "
676 "complete the mapping of this class."
677 % orm_exc._safe_cls_name(cls),
678 )
681class DeferredReflection(object):
682 """A helper class for construction of mappings based on
683 a deferred reflection step.
685 Normally, declarative can be used with reflection by
686 setting a :class:`_schema.Table` object using autoload=True
687 as the ``__table__`` attribute on a declarative class.
688 The caveat is that the :class:`_schema.Table` must be fully
689 reflected, or at the very least have a primary key column,
690 at the point at which a normal declarative mapping is
691 constructed, meaning the :class:`_engine.Engine` must be available
692 at class declaration time.
694 The :class:`.DeferredReflection` mixin moves the construction
695 of mappers to be at a later point, after a specific
696 method is called which first reflects all :class:`_schema.Table`
697 objects created so far. Classes can define it as such::
699 from sqlalchemy.ext.declarative import declarative_base
700 from sqlalchemy.ext.declarative import DeferredReflection
701 Base = declarative_base()
703 class MyClass(DeferredReflection, Base):
704 __tablename__ = 'mytable'
706 Above, ``MyClass`` is not yet mapped. After a series of
707 classes have been defined in the above fashion, all tables
708 can be reflected and mappings created using
709 :meth:`.prepare`::
711 engine = create_engine("someengine://...")
712 DeferredReflection.prepare(engine)
714 The :class:`.DeferredReflection` mixin can be applied to individual
715 classes, used as the base for the declarative base itself,
716 or used in a custom abstract class. Using an abstract base
717 allows that only a subset of classes to be prepared for a
718 particular prepare step, which is necessary for applications
719 that use more than one engine. For example, if an application
720 has two engines, you might use two bases, and prepare each
721 separately, e.g.::
723 class ReflectedOne(DeferredReflection, Base):
724 __abstract__ = True
726 class ReflectedTwo(DeferredReflection, Base):
727 __abstract__ = True
729 class MyClass(ReflectedOne):
730 __tablename__ = 'mytable'
732 class MyOtherClass(ReflectedOne):
733 __tablename__ = 'myothertable'
735 class YetAnotherClass(ReflectedTwo):
736 __tablename__ = 'yetanothertable'
738 # ... etc.
740 Above, the class hierarchies for ``ReflectedOne`` and
741 ``ReflectedTwo`` can be configured separately::
743 ReflectedOne.prepare(engine_one)
744 ReflectedTwo.prepare(engine_two)
746 """
748 @classmethod
749 def prepare(cls, engine):
750 """Reflect all :class:`_schema.Table` objects for all current
751 :class:`.DeferredReflection` subclasses"""
753 to_map = _DeferredMapperConfig.classes_for_base(cls)
754 for thingy in to_map:
755 cls._sa_decl_prepare(thingy.local_table, engine)
756 thingy.map()
757 mapper = thingy.cls.__mapper__
758 metadata = mapper.class_.metadata
759 for rel in mapper._props.values():
760 if (
761 isinstance(rel, properties.RelationshipProperty)
762 and rel.secondary is not None
763 ):
764 if isinstance(rel.secondary, Table):
765 cls._reflect_table(rel.secondary, engine)
766 elif isinstance(rel.secondary, _class_resolver):
767 rel.secondary._resolvers += (
768 cls._sa_deferred_table_resolver(engine, metadata),
769 )
771 @classmethod
772 def _sa_deferred_table_resolver(cls, engine, metadata):
773 def _resolve(key):
774 t1 = Table(key, metadata)
775 cls._reflect_table(t1, engine)
776 return t1
778 return _resolve
780 @classmethod
781 def _sa_decl_prepare(cls, local_table, engine):
782 # autoload Table, which is already
783 # present in the metadata. This
784 # will fill in db-loaded columns
785 # into the existing Table object.
786 if local_table is not None:
787 cls._reflect_table(local_table, engine)
789 @classmethod
790 def _sa_raise_deferred_config(cls):
791 raise orm_exc.UnmappedClassError(
792 cls,
793 msg="Class %s is a subclass of DeferredReflection. "
794 "Mappings are not produced until the .prepare() "
795 "method is called on the class hierarchy."
796 % orm_exc._safe_cls_name(cls),
797 )
799 @classmethod
800 def _reflect_table(cls, table, engine):
801 Table(
802 table.name,
803 table.metadata,
804 extend_existing=True,
805 autoload_replace=False,
806 autoload=True,
807 autoload_with=engine,
808 schema=table.schema,
809 )
812@inspection._inspects(DeclarativeMeta)
813def _inspect_decl_meta(cls):
814 mp = _inspect_mapped_class(cls)
815 if mp is None:
816 if _DeferredMapperConfig.has_cls(cls):
817 _DeferredMapperConfig.raise_unmapped_for_cls(cls)
818 raise orm_exc.UnmappedClassError(
819 cls,
820 msg="Class %s has a deferred mapping on it. It is not yet "
821 "usable as a mapped class." % orm_exc._safe_cls_name(cls),
822 )
823 return mp