827 lines
27 KiB
Python
827 lines
27 KiB
Python
|
# ext/declarative/api.py
|
||
|
# Copyright (C) 2005-2019 the SQLAlchemy authors and contributors
|
||
|
# <see AUTHORS file>
|
||
|
#
|
||
|
# This module is part of SQLAlchemy and is released under
|
||
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||
|
"""Public API functions and helpers for declarative."""
|
||
|
|
||
|
|
||
|
import re
|
||
|
import weakref
|
||
|
|
||
|
from .base import _add_attribute
|
||
|
from .base import _as_declarative
|
||
|
from .base import _declarative_constructor
|
||
|
from .base import _DeferredMapperConfig
|
||
|
from .base import _del_attribute
|
||
|
from .clsregistry import _class_resolver
|
||
|
from ... import exc
|
||
|
from ... import inspection
|
||
|
from ... import util
|
||
|
from ...orm import attributes
|
||
|
from ...orm import comparable_property
|
||
|
from ...orm import exc as orm_exc
|
||
|
from ...orm import interfaces
|
||
|
from ...orm import properties
|
||
|
from ...orm import synonym as _orm_synonym
|
||
|
from ...orm.base import _inspect_mapped_class
|
||
|
from ...orm.base import _mapper_or_none
|
||
|
from ...orm.util import polymorphic_union
|
||
|
from ...schema import MetaData
|
||
|
from ...schema import Table
|
||
|
from ...util import hybridmethod
|
||
|
from ...util import hybridproperty
|
||
|
from ...util import OrderedDict
|
||
|
|
||
|
|
||
|
def instrument_declarative(cls, registry, metadata):
|
||
|
"""Given a class, configure the class declaratively,
|
||
|
using the given registry, which can be any dictionary, and
|
||
|
MetaData object.
|
||
|
|
||
|
"""
|
||
|
if "_decl_class_registry" in cls.__dict__:
|
||
|
raise exc.InvalidRequestError(
|
||
|
"Class %r already has been " "instrumented declaratively" % cls
|
||
|
)
|
||
|
cls._decl_class_registry = registry
|
||
|
cls.metadata = metadata
|
||
|
_as_declarative(cls, cls.__name__, cls.__dict__)
|
||
|
|
||
|
|
||
|
def has_inherited_table(cls):
|
||
|
"""Given a class, return True if any of the classes it inherits from has a
|
||
|
mapped table, otherwise return False.
|
||
|
|
||
|
This is used in declarative mixins to build attributes that behave
|
||
|
differently for the base class vs. a subclass in an inheritance
|
||
|
hierarchy.
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:ref:`decl_mixin_inheritance`
|
||
|
|
||
|
"""
|
||
|
for class_ in cls.__mro__[1:]:
|
||
|
if getattr(class_, "__table__", None) is not None:
|
||
|
return True
|
||
|
return False
|
||
|
|
||
|
|
||
|
class DeclarativeMeta(type):
|
||
|
def __init__(cls, classname, bases, dict_):
|
||
|
if "_decl_class_registry" not in cls.__dict__:
|
||
|
_as_declarative(cls, classname, cls.__dict__)
|
||
|
type.__init__(cls, classname, bases, dict_)
|
||
|
|
||
|
def __setattr__(cls, key, value):
|
||
|
_add_attribute(cls, key, value)
|
||
|
|
||
|
def __delattr__(cls, key):
|
||
|
_del_attribute(cls, key)
|
||
|
|
||
|
|
||
|
def synonym_for(name, map_column=False):
|
||
|
"""Decorator that produces an :func:`.orm.synonym` attribute in conjunction
|
||
|
with a Python descriptor.
|
||
|
|
||
|
The function being decorated is passed to :func:`.orm.synonym` as the
|
||
|
:paramref:`.orm.synonym.descriptor` parameter::
|
||
|
|
||
|
class MyClass(Base):
|
||
|
__tablename__ = 'my_table'
|
||
|
|
||
|
id = Column(Integer, primary_key=True)
|
||
|
_job_status = Column("job_status", String(50))
|
||
|
|
||
|
@synonym_for("job_status")
|
||
|
@property
|
||
|
def job_status(self):
|
||
|
return "Status: %s" % self._job_status
|
||
|
|
||
|
The :ref:`hybrid properties <mapper_hybrids>` feature of SQLAlchemy
|
||
|
is typically preferred instead of synonyms, which is a more legacy
|
||
|
feature.
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:ref:`synonyms` - Overview of synonyms
|
||
|
|
||
|
:func:`.orm.synonym` - the mapper-level function
|
||
|
|
||
|
:ref:`mapper_hybrids` - The Hybrid Attribute extension provides an
|
||
|
updated approach to augmenting attribute behavior more flexibly than
|
||
|
can be achieved with synonyms.
|
||
|
|
||
|
"""
|
||
|
|
||
|
def decorate(fn):
|
||
|
return _orm_synonym(name, map_column=map_column, descriptor=fn)
|
||
|
|
||
|
return decorate
|
||
|
|
||
|
|
||
|
def comparable_using(comparator_factory):
|
||
|
"""Decorator, allow a Python @property to be used in query criteria.
|
||
|
|
||
|
This is a decorator front end to
|
||
|
:func:`~sqlalchemy.orm.comparable_property` that passes
|
||
|
through the comparator_factory and the function being decorated::
|
||
|
|
||
|
@comparable_using(MyComparatorType)
|
||
|
@property
|
||
|
def prop(self):
|
||
|
return 'special sauce'
|
||
|
|
||
|
The regular ``comparable_property()`` is also usable directly in a
|
||
|
declarative setting and may be convenient for read/write properties::
|
||
|
|
||
|
prop = comparable_property(MyComparatorType)
|
||
|
|
||
|
"""
|
||
|
|
||
|
def decorate(fn):
|
||
|
return comparable_property(comparator_factory, fn)
|
||
|
|
||
|
return decorate
|
||
|
|
||
|
|
||
|
class declared_attr(interfaces._MappedAttribute, property):
|
||
|
"""Mark a class-level method as representing the definition of
|
||
|
a mapped property or special declarative member name.
|
||
|
|
||
|
@declared_attr turns the attribute into a scalar-like
|
||
|
property that can be invoked from the uninstantiated class.
|
||
|
Declarative treats attributes specifically marked with
|
||
|
@declared_attr as returning a construct that is specific
|
||
|
to mapping or declarative table configuration. The name
|
||
|
of the attribute is that of what the non-dynamic version
|
||
|
of the attribute would be.
|
||
|
|
||
|
@declared_attr is more often than not applicable to mixins,
|
||
|
to define relationships that are to be applied to different
|
||
|
implementors of the class::
|
||
|
|
||
|
class ProvidesUser(object):
|
||
|
"A mixin that adds a 'user' relationship to classes."
|
||
|
|
||
|
@declared_attr
|
||
|
def user(self):
|
||
|
return relationship("User")
|
||
|
|
||
|
It also can be applied to mapped classes, such as to provide
|
||
|
a "polymorphic" scheme for inheritance::
|
||
|
|
||
|
class Employee(Base):
|
||
|
id = Column(Integer, primary_key=True)
|
||
|
type = Column(String(50), nullable=False)
|
||
|
|
||
|
@declared_attr
|
||
|
def __tablename__(cls):
|
||
|
return cls.__name__.lower()
|
||
|
|
||
|
@declared_attr
|
||
|
def __mapper_args__(cls):
|
||
|
if cls.__name__ == 'Employee':
|
||
|
return {
|
||
|
"polymorphic_on":cls.type,
|
||
|
"polymorphic_identity":"Employee"
|
||
|
}
|
||
|
else:
|
||
|
return {"polymorphic_identity":cls.__name__}
|
||
|
|
||
|
"""
|
||
|
|
||
|
def __init__(self, fget, cascading=False):
|
||
|
super(declared_attr, self).__init__(fget)
|
||
|
self.__doc__ = fget.__doc__
|
||
|
self._cascading = cascading
|
||
|
|
||
|
def __get__(desc, self, cls):
|
||
|
reg = cls.__dict__.get("_sa_declared_attr_reg", None)
|
||
|
if reg is None:
|
||
|
if (
|
||
|
not re.match(r"^__.+__$", desc.fget.__name__)
|
||
|
and attributes.manager_of_class(cls) is None
|
||
|
):
|
||
|
util.warn(
|
||
|
"Unmanaged access of declarative attribute %s from "
|
||
|
"non-mapped class %s" % (desc.fget.__name__, cls.__name__)
|
||
|
)
|
||
|
return desc.fget(cls)
|
||
|
elif desc in reg:
|
||
|
return reg[desc]
|
||
|
else:
|
||
|
reg[desc] = obj = desc.fget(cls)
|
||
|
return obj
|
||
|
|
||
|
@hybridmethod
|
||
|
def _stateful(cls, **kw):
|
||
|
return _stateful_declared_attr(**kw)
|
||
|
|
||
|
@hybridproperty
|
||
|
def cascading(cls):
|
||
|
"""Mark a :class:`.declared_attr` as cascading.
|
||
|
|
||
|
This is a special-use modifier which indicates that a column
|
||
|
or MapperProperty-based declared attribute should be configured
|
||
|
distinctly per mapped subclass, within a mapped-inheritance scenario.
|
||
|
|
||
|
.. warning::
|
||
|
|
||
|
The :attr:`.declared_attr.cascading` modifier has several
|
||
|
limitations:
|
||
|
|
||
|
* The flag **only** applies to the use of :class:`.declared_attr`
|
||
|
on declarative mixin classes and ``__abstract__`` classes; it
|
||
|
currently has no effect when used on a mapped class directly.
|
||
|
|
||
|
* The flag **only** applies to normally-named attributes, e.g.
|
||
|
not any special underscore attributes such as ``__tablename__``.
|
||
|
On these attributes it has **no** effect.
|
||
|
|
||
|
* The flag currently **does not allow further overrides** down
|
||
|
the class hierarchy; if a subclass tries to override the
|
||
|
attribute, a warning is emitted and the overridden attribute
|
||
|
is skipped. This is a limitation that it is hoped will be
|
||
|
resolved at some point.
|
||
|
|
||
|
Below, both MyClass as well as MySubClass will have a distinct
|
||
|
``id`` Column object established::
|
||
|
|
||
|
class HasIdMixin(object):
|
||
|
@declared_attr.cascading
|
||
|
def id(cls):
|
||
|
if has_inherited_table(cls):
|
||
|
return Column(
|
||
|
ForeignKey('myclass.id'), primary_key=True)
|
||
|
else:
|
||
|
return Column(Integer, primary_key=True)
|
||
|
|
||
|
class MyClass(HasIdMixin, Base):
|
||
|
__tablename__ = 'myclass'
|
||
|
# ...
|
||
|
|
||
|
class MySubClass(MyClass):
|
||
|
""
|
||
|
# ...
|
||
|
|
||
|
The behavior of the above configuration is that ``MySubClass``
|
||
|
will refer to both its own ``id`` column as well as that of
|
||
|
``MyClass`` underneath the attribute named ``some_id``.
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:ref:`declarative_inheritance`
|
||
|
|
||
|
:ref:`mixin_inheritance_columns`
|
||
|
|
||
|
|
||
|
"""
|
||
|
return cls._stateful(cascading=True)
|
||
|
|
||
|
|
||
|
class _stateful_declared_attr(declared_attr):
|
||
|
def __init__(self, **kw):
|
||
|
self.kw = kw
|
||
|
|
||
|
def _stateful(self, **kw):
|
||
|
new_kw = self.kw.copy()
|
||
|
new_kw.update(kw)
|
||
|
return _stateful_declared_attr(**new_kw)
|
||
|
|
||
|
def __call__(self, fn):
|
||
|
return declared_attr(fn, **self.kw)
|
||
|
|
||
|
|
||
|
def declarative_base(
|
||
|
bind=None,
|
||
|
metadata=None,
|
||
|
mapper=None,
|
||
|
cls=object,
|
||
|
name="Base",
|
||
|
constructor=_declarative_constructor,
|
||
|
class_registry=None,
|
||
|
metaclass=DeclarativeMeta,
|
||
|
):
|
||
|
r"""Construct a base class for declarative class definitions.
|
||
|
|
||
|
The new base class will be given a metaclass that produces
|
||
|
appropriate :class:`~sqlalchemy.schema.Table` objects and makes
|
||
|
the appropriate :func:`~sqlalchemy.orm.mapper` calls based on the
|
||
|
information provided declaratively in the class and any subclasses
|
||
|
of the class.
|
||
|
|
||
|
:param bind: An optional
|
||
|
:class:`~sqlalchemy.engine.Connectable`, will be assigned
|
||
|
the ``bind`` attribute on the :class:`~sqlalchemy.schema.MetaData`
|
||
|
instance.
|
||
|
|
||
|
:param metadata:
|
||
|
An optional :class:`~sqlalchemy.schema.MetaData` instance. All
|
||
|
:class:`~sqlalchemy.schema.Table` objects implicitly declared by
|
||
|
subclasses of the base will share this MetaData. A MetaData instance
|
||
|
will be created if none is provided. The
|
||
|
:class:`~sqlalchemy.schema.MetaData` instance will be available via the
|
||
|
`metadata` attribute of the generated declarative base class.
|
||
|
|
||
|
:param mapper:
|
||
|
An optional callable, defaults to :func:`~sqlalchemy.orm.mapper`. Will
|
||
|
be used to map subclasses to their Tables.
|
||
|
|
||
|
:param cls:
|
||
|
Defaults to :class:`object`. A type to use as the base for the generated
|
||
|
declarative base class. May be a class or tuple of classes.
|
||
|
|
||
|
:param name:
|
||
|
Defaults to ``Base``. The display name for the generated
|
||
|
class. Customizing this is not required, but can improve clarity in
|
||
|
tracebacks and debugging.
|
||
|
|
||
|
:param constructor:
|
||
|
Defaults to
|
||
|
:func:`~sqlalchemy.ext.declarative.base._declarative_constructor`, an
|
||
|
__init__ implementation that assigns \**kwargs for declared
|
||
|
fields and relationships to an instance. If ``None`` is supplied,
|
||
|
no __init__ will be provided and construction will fall back to
|
||
|
cls.__init__ by way of the normal Python semantics.
|
||
|
|
||
|
:param class_registry: optional dictionary that will serve as the
|
||
|
registry of class names-> mapped classes when string names
|
||
|
are used to identify classes inside of :func:`.relationship`
|
||
|
and others. Allows two or more declarative base classes
|
||
|
to share the same registry of class names for simplified
|
||
|
inter-base relationships.
|
||
|
|
||
|
:param metaclass:
|
||
|
Defaults to :class:`.DeclarativeMeta`. A metaclass or __metaclass__
|
||
|
compatible callable to use as the meta type of the generated
|
||
|
declarative base class.
|
||
|
|
||
|
.. versionchanged:: 1.1 if :paramref:`.declarative_base.cls` is a
|
||
|
single class (rather than a tuple), the constructed base class will
|
||
|
inherit its docstring.
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:func:`.as_declarative`
|
||
|
|
||
|
"""
|
||
|
lcl_metadata = metadata or MetaData()
|
||
|
if bind:
|
||
|
lcl_metadata.bind = bind
|
||
|
|
||
|
if class_registry is None:
|
||
|
class_registry = weakref.WeakValueDictionary()
|
||
|
|
||
|
bases = not isinstance(cls, tuple) and (cls,) or cls
|
||
|
class_dict = dict(
|
||
|
_decl_class_registry=class_registry, metadata=lcl_metadata
|
||
|
)
|
||
|
|
||
|
if isinstance(cls, type):
|
||
|
class_dict["__doc__"] = cls.__doc__
|
||
|
|
||
|
if constructor:
|
||
|
class_dict["__init__"] = constructor
|
||
|
if mapper:
|
||
|
class_dict["__mapper_cls__"] = mapper
|
||
|
|
||
|
return metaclass(name, bases, class_dict)
|
||
|
|
||
|
|
||
|
def as_declarative(**kw):
|
||
|
"""
|
||
|
Class decorator for :func:`.declarative_base`.
|
||
|
|
||
|
Provides a syntactical shortcut to the ``cls`` argument
|
||
|
sent to :func:`.declarative_base`, allowing the base class
|
||
|
to be converted in-place to a "declarative" base::
|
||
|
|
||
|
from sqlalchemy.ext.declarative import as_declarative
|
||
|
|
||
|
@as_declarative()
|
||
|
class Base(object):
|
||
|
@declared_attr
|
||
|
def __tablename__(cls):
|
||
|
return cls.__name__.lower()
|
||
|
id = Column(Integer, primary_key=True)
|
||
|
|
||
|
class MyMappedClass(Base):
|
||
|
# ...
|
||
|
|
||
|
All keyword arguments passed to :func:`.as_declarative` are passed
|
||
|
along to :func:`.declarative_base`.
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:func:`.declarative_base`
|
||
|
|
||
|
"""
|
||
|
|
||
|
def decorate(cls):
|
||
|
kw["cls"] = cls
|
||
|
kw["name"] = cls.__name__
|
||
|
return declarative_base(**kw)
|
||
|
|
||
|
return decorate
|
||
|
|
||
|
|
||
|
class ConcreteBase(object):
|
||
|
"""A helper class for 'concrete' declarative mappings.
|
||
|
|
||
|
:class:`.ConcreteBase` will use the :func:`.polymorphic_union`
|
||
|
function automatically, against all tables mapped as a subclass
|
||
|
to this class. The function is called via the
|
||
|
``__declare_last__()`` function, which is essentially
|
||
|
a hook for the :meth:`.after_configured` event.
|
||
|
|
||
|
:class:`.ConcreteBase` produces a mapped
|
||
|
table for the class itself. Compare to :class:`.AbstractConcreteBase`,
|
||
|
which does not.
|
||
|
|
||
|
Example::
|
||
|
|
||
|
from sqlalchemy.ext.declarative import ConcreteBase
|
||
|
|
||
|
class Employee(ConcreteBase, Base):
|
||
|
__tablename__ = 'employee'
|
||
|
employee_id = Column(Integer, primary_key=True)
|
||
|
name = Column(String(50))
|
||
|
__mapper_args__ = {
|
||
|
'polymorphic_identity':'employee',
|
||
|
'concrete':True}
|
||
|
|
||
|
class Manager(Employee):
|
||
|
__tablename__ = 'manager'
|
||
|
employee_id = Column(Integer, primary_key=True)
|
||
|
name = Column(String(50))
|
||
|
manager_data = Column(String(40))
|
||
|
__mapper_args__ = {
|
||
|
'polymorphic_identity':'manager',
|
||
|
'concrete':True}
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:class:`.AbstractConcreteBase`
|
||
|
|
||
|
:ref:`concrete_inheritance`
|
||
|
|
||
|
:ref:`inheritance_concrete_helpers`
|
||
|
|
||
|
|
||
|
"""
|
||
|
|
||
|
@classmethod
|
||
|
def _create_polymorphic_union(cls, mappers):
|
||
|
return polymorphic_union(
|
||
|
OrderedDict(
|
||
|
(mp.polymorphic_identity, mp.local_table) for mp in mappers
|
||
|
),
|
||
|
"type",
|
||
|
"pjoin",
|
||
|
)
|
||
|
|
||
|
@classmethod
|
||
|
def __declare_first__(cls):
|
||
|
m = cls.__mapper__
|
||
|
if m.with_polymorphic:
|
||
|
return
|
||
|
|
||
|
mappers = list(m.self_and_descendants)
|
||
|
pjoin = cls._create_polymorphic_union(mappers)
|
||
|
m._set_with_polymorphic(("*", pjoin))
|
||
|
m._set_polymorphic_on(pjoin.c.type)
|
||
|
|
||
|
|
||
|
class AbstractConcreteBase(ConcreteBase):
|
||
|
"""A helper class for 'concrete' declarative mappings.
|
||
|
|
||
|
:class:`.AbstractConcreteBase` will use the :func:`.polymorphic_union`
|
||
|
function automatically, against all tables mapped as a subclass
|
||
|
to this class. The function is called via the
|
||
|
``__declare_last__()`` function, which is essentially
|
||
|
a hook for the :meth:`.after_configured` event.
|
||
|
|
||
|
:class:`.AbstractConcreteBase` does produce a mapped class
|
||
|
for the base class, however it is not persisted to any table; it
|
||
|
is instead mapped directly to the "polymorphic" selectable directly
|
||
|
and is only used for selecting. Compare to :class:`.ConcreteBase`,
|
||
|
which does create a persisted table for the base class.
|
||
|
|
||
|
.. note::
|
||
|
|
||
|
The :class:`.AbstractConcreteBase` class does not intend to set up the
|
||
|
mapping for the base class until all the subclasses have been defined,
|
||
|
as it needs to create a mapping against a selectable that will include
|
||
|
all subclass tables. In order to achieve this, it waits for the
|
||
|
**mapper configuration event** to occur, at which point it scans
|
||
|
through all the configured subclasses and sets up a mapping that will
|
||
|
query against all subclasses at once.
|
||
|
|
||
|
While this event is normally invoked automatically, in the case of
|
||
|
:class:`.AbstractConcreteBase`, it may be necessary to invoke it
|
||
|
explicitly after **all** subclass mappings are defined, if the first
|
||
|
operation is to be a query against this base class. To do so, invoke
|
||
|
:func:`.configure_mappers` once all the desired classes have been
|
||
|
configured::
|
||
|
|
||
|
from sqlalchemy.orm import configure_mappers
|
||
|
|
||
|
configure_mappers()
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:func:`.orm.configure_mappers`
|
||
|
|
||
|
|
||
|
Example::
|
||
|
|
||
|
from sqlalchemy.ext.declarative import AbstractConcreteBase
|
||
|
|
||
|
class Employee(AbstractConcreteBase, Base):
|
||
|
pass
|
||
|
|
||
|
class Manager(Employee):
|
||
|
__tablename__ = 'manager'
|
||
|
employee_id = Column(Integer, primary_key=True)
|
||
|
name = Column(String(50))
|
||
|
manager_data = Column(String(40))
|
||
|
|
||
|
__mapper_args__ = {
|
||
|
'polymorphic_identity':'manager',
|
||
|
'concrete':True}
|
||
|
|
||
|
configure_mappers()
|
||
|
|
||
|
The abstract base class is handled by declarative in a special way;
|
||
|
at class configuration time, it behaves like a declarative mixin
|
||
|
or an ``__abstract__`` base class. Once classes are configured
|
||
|
and mappings are produced, it then gets mapped itself, but
|
||
|
after all of its descendants. This is a very unique system of mapping
|
||
|
not found in any other SQLAlchemy system.
|
||
|
|
||
|
Using this approach, we can specify columns and properties
|
||
|
that will take place on mapped subclasses, in the way that
|
||
|
we normally do as in :ref:`declarative_mixins`::
|
||
|
|
||
|
class Company(Base):
|
||
|
__tablename__ = 'company'
|
||
|
id = Column(Integer, primary_key=True)
|
||
|
|
||
|
class Employee(AbstractConcreteBase, Base):
|
||
|
employee_id = Column(Integer, primary_key=True)
|
||
|
|
||
|
@declared_attr
|
||
|
def company_id(cls):
|
||
|
return Column(ForeignKey('company.id'))
|
||
|
|
||
|
@declared_attr
|
||
|
def company(cls):
|
||
|
return relationship("Company")
|
||
|
|
||
|
class Manager(Employee):
|
||
|
__tablename__ = 'manager'
|
||
|
|
||
|
name = Column(String(50))
|
||
|
manager_data = Column(String(40))
|
||
|
|
||
|
__mapper_args__ = {
|
||
|
'polymorphic_identity':'manager',
|
||
|
'concrete':True}
|
||
|
|
||
|
configure_mappers()
|
||
|
|
||
|
When we make use of our mappings however, both ``Manager`` and
|
||
|
``Employee`` will have an independently usable ``.company`` attribute::
|
||
|
|
||
|
session.query(Employee).filter(Employee.company.has(id=5))
|
||
|
|
||
|
.. versionchanged:: 1.0.0 - The mechanics of :class:`.AbstractConcreteBase`
|
||
|
have been reworked to support relationships established directly
|
||
|
on the abstract base, without any special configurational steps.
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:class:`.ConcreteBase`
|
||
|
|
||
|
:ref:`concrete_inheritance`
|
||
|
|
||
|
:ref:`inheritance_concrete_helpers`
|
||
|
|
||
|
"""
|
||
|
|
||
|
__no_table__ = True
|
||
|
|
||
|
@classmethod
|
||
|
def __declare_first__(cls):
|
||
|
cls._sa_decl_prepare_nocascade()
|
||
|
|
||
|
@classmethod
|
||
|
def _sa_decl_prepare_nocascade(cls):
|
||
|
if getattr(cls, "__mapper__", None):
|
||
|
return
|
||
|
|
||
|
to_map = _DeferredMapperConfig.config_for_cls(cls)
|
||
|
|
||
|
# can't rely on 'self_and_descendants' here
|
||
|
# since technically an immediate subclass
|
||
|
# might not be mapped, but a subclass
|
||
|
# may be.
|
||
|
mappers = []
|
||
|
stack = list(cls.__subclasses__())
|
||
|
while stack:
|
||
|
klass = stack.pop()
|
||
|
stack.extend(klass.__subclasses__())
|
||
|
mn = _mapper_or_none(klass)
|
||
|
if mn is not None:
|
||
|
mappers.append(mn)
|
||
|
pjoin = cls._create_polymorphic_union(mappers)
|
||
|
|
||
|
# For columns that were declared on the class, these
|
||
|
# are normally ignored with the "__no_table__" mapping,
|
||
|
# unless they have a different attribute key vs. col name
|
||
|
# and are in the properties argument.
|
||
|
# In that case, ensure we update the properties entry
|
||
|
# to the correct column from the pjoin target table.
|
||
|
declared_cols = set(to_map.declared_columns)
|
||
|
for k, v in list(to_map.properties.items()):
|
||
|
if v in declared_cols:
|
||
|
to_map.properties[k] = pjoin.c[v.key]
|
||
|
|
||
|
to_map.local_table = pjoin
|
||
|
|
||
|
m_args = to_map.mapper_args_fn or dict
|
||
|
|
||
|
def mapper_args():
|
||
|
args = m_args()
|
||
|
args["polymorphic_on"] = pjoin.c.type
|
||
|
return args
|
||
|
|
||
|
to_map.mapper_args_fn = mapper_args
|
||
|
|
||
|
m = to_map.map()
|
||
|
|
||
|
for scls in cls.__subclasses__():
|
||
|
sm = _mapper_or_none(scls)
|
||
|
if sm and sm.concrete and cls in scls.__bases__:
|
||
|
sm._set_concrete_base(m)
|
||
|
|
||
|
@classmethod
|
||
|
def _sa_raise_deferred_config(cls):
|
||
|
raise orm_exc.UnmappedClassError(
|
||
|
cls,
|
||
|
msg="Class %s is a subclass of AbstractConcreteBase and "
|
||
|
"has a mapping pending until all subclasses are defined. "
|
||
|
"Call the sqlalchemy.orm.configure_mappers() function after "
|
||
|
"all subclasses have been defined to "
|
||
|
"complete the mapping of this class."
|
||
|
% orm_exc._safe_cls_name(cls),
|
||
|
)
|
||
|
|
||
|
|
||
|
class DeferredReflection(object):
|
||
|
"""A helper class for construction of mappings based on
|
||
|
a deferred reflection step.
|
||
|
|
||
|
Normally, declarative can be used with reflection by
|
||
|
setting a :class:`.Table` object using autoload=True
|
||
|
as the ``__table__`` attribute on a declarative class.
|
||
|
The caveat is that the :class:`.Table` must be fully
|
||
|
reflected, or at the very least have a primary key column,
|
||
|
at the point at which a normal declarative mapping is
|
||
|
constructed, meaning the :class:`.Engine` must be available
|
||
|
at class declaration time.
|
||
|
|
||
|
The :class:`.DeferredReflection` mixin moves the construction
|
||
|
of mappers to be at a later point, after a specific
|
||
|
method is called which first reflects all :class:`.Table`
|
||
|
objects created so far. Classes can define it as such::
|
||
|
|
||
|
from sqlalchemy.ext.declarative import declarative_base
|
||
|
from sqlalchemy.ext.declarative import DeferredReflection
|
||
|
Base = declarative_base()
|
||
|
|
||
|
class MyClass(DeferredReflection, Base):
|
||
|
__tablename__ = 'mytable'
|
||
|
|
||
|
Above, ``MyClass`` is not yet mapped. After a series of
|
||
|
classes have been defined in the above fashion, all tables
|
||
|
can be reflected and mappings created using
|
||
|
:meth:`.prepare`::
|
||
|
|
||
|
engine = create_engine("someengine://...")
|
||
|
DeferredReflection.prepare(engine)
|
||
|
|
||
|
The :class:`.DeferredReflection` mixin can be applied to individual
|
||
|
classes, used as the base for the declarative base itself,
|
||
|
or used in a custom abstract class. Using an abstract base
|
||
|
allows that only a subset of classes to be prepared for a
|
||
|
particular prepare step, which is necessary for applications
|
||
|
that use more than one engine. For example, if an application
|
||
|
has two engines, you might use two bases, and prepare each
|
||
|
separately, e.g.::
|
||
|
|
||
|
class ReflectedOne(DeferredReflection, Base):
|
||
|
__abstract__ = True
|
||
|
|
||
|
class ReflectedTwo(DeferredReflection, Base):
|
||
|
__abstract__ = True
|
||
|
|
||
|
class MyClass(ReflectedOne):
|
||
|
__tablename__ = 'mytable'
|
||
|
|
||
|
class MyOtherClass(ReflectedOne):
|
||
|
__tablename__ = 'myothertable'
|
||
|
|
||
|
class YetAnotherClass(ReflectedTwo):
|
||
|
__tablename__ = 'yetanothertable'
|
||
|
|
||
|
# ... etc.
|
||
|
|
||
|
Above, the class hierarchies for ``ReflectedOne`` and
|
||
|
``ReflectedTwo`` can be configured separately::
|
||
|
|
||
|
ReflectedOne.prepare(engine_one)
|
||
|
ReflectedTwo.prepare(engine_two)
|
||
|
|
||
|
"""
|
||
|
|
||
|
@classmethod
|
||
|
def prepare(cls, engine):
|
||
|
"""Reflect all :class:`.Table` objects for all current
|
||
|
:class:`.DeferredReflection` subclasses"""
|
||
|
|
||
|
to_map = _DeferredMapperConfig.classes_for_base(cls)
|
||
|
for thingy in to_map:
|
||
|
cls._sa_decl_prepare(thingy.local_table, engine)
|
||
|
thingy.map()
|
||
|
mapper = thingy.cls.__mapper__
|
||
|
metadata = mapper.class_.metadata
|
||
|
for rel in mapper._props.values():
|
||
|
if (
|
||
|
isinstance(rel, properties.RelationshipProperty)
|
||
|
and rel.secondary is not None
|
||
|
):
|
||
|
if isinstance(rel.secondary, Table):
|
||
|
cls._reflect_table(rel.secondary, engine)
|
||
|
elif isinstance(rel.secondary, _class_resolver):
|
||
|
rel.secondary._resolvers += (
|
||
|
cls._sa_deferred_table_resolver(engine, metadata),
|
||
|
)
|
||
|
|
||
|
@classmethod
|
||
|
def _sa_deferred_table_resolver(cls, engine, metadata):
|
||
|
def _resolve(key):
|
||
|
t1 = Table(key, metadata)
|
||
|
cls._reflect_table(t1, engine)
|
||
|
return t1
|
||
|
|
||
|
return _resolve
|
||
|
|
||
|
@classmethod
|
||
|
def _sa_decl_prepare(cls, local_table, engine):
|
||
|
# autoload Table, which is already
|
||
|
# present in the metadata. This
|
||
|
# will fill in db-loaded columns
|
||
|
# into the existing Table object.
|
||
|
if local_table is not None:
|
||
|
cls._reflect_table(local_table, engine)
|
||
|
|
||
|
@classmethod
|
||
|
def _sa_raise_deferred_config(cls):
|
||
|
raise orm_exc.UnmappedClassError(
|
||
|
cls,
|
||
|
msg="Class %s is a subclass of DeferredReflection. "
|
||
|
"Mappings are not produced until the .prepare() "
|
||
|
"method is called on the class hierarchy."
|
||
|
% orm_exc._safe_cls_name(cls),
|
||
|
)
|
||
|
|
||
|
@classmethod
|
||
|
def _reflect_table(cls, table, engine):
|
||
|
Table(
|
||
|
table.name,
|
||
|
table.metadata,
|
||
|
extend_existing=True,
|
||
|
autoload_replace=False,
|
||
|
autoload=True,
|
||
|
autoload_with=engine,
|
||
|
schema=table.schema,
|
||
|
)
|
||
|
|
||
|
|
||
|
@inspection._inspects(DeclarativeMeta)
|
||
|
def _inspect_decl_meta(cls):
|
||
|
mp = _inspect_mapped_class(cls)
|
||
|
if mp is None:
|
||
|
if _DeferredMapperConfig.has_cls(cls):
|
||
|
_DeferredMapperConfig.raise_unmapped_for_cls(cls)
|
||
|
raise orm_exc.UnmappedClassError(
|
||
|
cls,
|
||
|
msg="Class %s has a deferred mapping on it. It is not yet "
|
||
|
"usable as a mapped class." % orm_exc._safe_cls_name(cls),
|
||
|
)
|
||
|
return mp
|