ArgumentError, rather than UnmappedClassError.
[ticket:2196]
+ - New event hook, MapperEvents.after_configured().
+ Called after a configure() step has completed and
+ mappers were in fact affected. Theoretically this
+ event is called once per application, unless new mappings
+ are constructed after existing ones have been used
+ already.
+
+ - New declarative features:
+ - __declare_last__() method, establishes an event
+ listener for the class method that will be called
+ when mappers are completed with the final "configure"
+ step.
+ - __abstract__ flag. The class will not be mapped
+ at all when this flag is present on the class.
+ - New helper classes ConcreteBase, AbstractConcreteBase.
+ Allow concrete mappings using declarative which automatically
+ set up the "polymorphic_union" when the "configure"
+ mapper step is invoked.
+ - The mapper itself has semi-private methods that allow
+ the "with_polymorphic" selectable to be assigned
+ to the mapper after it has already been configured.
+ [ticket:2239]
+
-sql
- Behavioral improvement: empty
conjunctions such as and_() and or_() will be
.. autofunction:: comparable_using
.. autofunction:: instrument_declarative
+
+.. autoclass:: AbstractConcreteBase
+
+.. autoclass:: ConcreteBase
+
__table__ = managers
__mapper_args__ = {'polymorphic_identity':'manager', 'concrete':True}
-There is a recipe which allows the above pattern to be executed
-using the declarative form, via a special base class that defers
-the creation of the mapper. That recipe is available at
-`DeclarativeAbstractConcreteBase <http://www.sqlalchemy.org/trac/wiki/UsageRecipes/DeclarativeAbstractConcreteBase>`_
+Using the Concrete Helpers
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+New helper classes released in 0.7.3 provides a simpler pattern for concrete inheritance.
+With these objects, the ``__declare_last__`` helper is used to configure the "polymorphic"
+loader for the mapper after all subclasses have been declared.
+
+A basic abstract example of the :class:`.AbstractConcreteBase` class::
+
+ from sqlalchemy.ext.declarative import AbstractConcreteBase
+
+ class Employee(AbstractConcreteBase, Base):
+ pass
+
+To have a concrete ``employee`` table, use :class:`.ConcreteBase` instead::
+
+ 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}
+
+
+Either ``Employee`` base can be used in the normal fashion::
+
+ 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}
+
+ class Engineer(Employee):
+ __tablename__ = 'engineer'
+ employee_id = Column(Integer, primary_key=True)
+ name = Column(String(50))
+ engineer_info = Column(String(40))
+ __mapper_args__ = {'polymorphic_identity':'engineer',
+ 'concrete':True}
+
.. _declarative_mixins:
__tablename__ = 'atable'
c = Column(Integer,primary_key=True)
+Special Directives
+==================
+
+``__declare_last__()``
+~~~~~~~~~~~~~~~~~~~~~~
+
+The ``__declare_last__()`` hook, introduced in 0.7.3, allows definition of
+a class level function that is automatically called by the :meth:`.MapperEvents.after_configured`
+event, which occurs after mappings are assumed to be completed and the 'configure' step
+has finished::
+
+ class MyClass(Base):
+ @classmethod
+ def __declare_last__(cls):
+ ""
+ # do something with mappings
+
+
+``__abstract__``
+~~~~~~~~~~~~~~~~~~~
+
+``__abstract__`` is introduced in 0.7.3 and causes declarative to skip the production
+of a table or mapper for the class entirely. A class can be added within a hierarchy
+in the same way as mixin (see :ref:`declarative_mixins`), allowing subclasses to extend
+just from the special class::
+
+ class SomeAbstractBase(Base):
+ __abstract__ = True
+
+ def some_helpful_method(self):
+ ""
+
+ @declared_attr
+ def __mapper_args__(cls):
+ return {"helpful mapper arguments":True}
+
+ class MyMappedClass(SomeAbstractBase):
+ ""
Class Constructor
=================
from sqlalchemy.orm.util import _is_mapped_class
from sqlalchemy import util, exc
from sqlalchemy.sql import util as sql_util, expression
+from sqlalchemy import event
+from sqlalchemy.orm.util import polymorphic_union, _mapper_or_none
__all__ = 'declarative_base', 'synonym_for', \
declarative_props = (declared_attr, util.classproperty)
for base in cls.__mro__:
+ _is_declarative_inherits = hasattr(base, '_decl_class_registry')
+
+ if '__declare_last__' in base.__dict__:
+ @event.listens_for(mapper, "after_configured")
+ def go():
+ cls.__declare_last__()
+ if '__abstract__' in base.__dict__:
+ if (base is cls or
+ (base in cls.__bases__ and not _is_declarative_inherits)
+ ):
+ return
+
class_mapped = _is_mapped_class(base)
if class_mapped:
parent_columns = base.__table__.c.keys()
def __init__(cls, classname, bases, dict_):
if '_decl_class_registry' in cls.__dict__:
return type.__init__(cls, classname, bases, dict_)
-
- _as_declarative(cls, classname, cls.__dict__)
+ else:
+ _as_declarative(cls, classname, cls.__dict__)
return type.__init__(cls, classname, bases, dict_)
def __setattr__(cls, key, value):
column.key = key
if column.name is None:
column.name = key
+
+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 :func:`.MapperEvents.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}
+
+ """
+
+ @classmethod
+ def _create_polymorphic_union(cls, mappers):
+ return polymorphic_union(dict(
+ (mapper.polymorphic_identity, mapper.local_table)
+ for mapper in mappers
+ ), 'type', 'pjoin')
+
+ @classmethod
+ def __declare_last__(cls):
+ m = cls.__mapper__
+ if m.with_polymorphic:
+ return
+ mappers = [ sm for sm in [
+ _mapper_or_none(klass)
+ for klass in cls.__subclasses__()
+ ] if sm is not None] + [m]
+ 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 :func:`.MapperEvents.after_configured` event.
+
+ :class:`.AbstractConcreteBase` does not produce a mapped
+ table for the class itself. Compare to :class:`.ConcreteBase`,
+ which does.
+
+ Example::
+
+ from sqlalchemy.ext.declarative import ConcreteBase
+
+ 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}
+
+ """
+
+ __abstract__ = True
+
+ @classmethod
+ def __declare_last__(cls):
+ if hasattr(cls, '__mapper__'):
+ return
+ table = cls._create_polymorphic_union(
+ m for m in [
+ _mapper_or_none(klass)
+ for klass in cls.__subclasses__()
+ ] if m is not None
+ )
+ cls.__mapper__ = m = mapper(cls, table, polymorphic_on=table.c.type)
+ for scls in cls.__subclasses__():
+ sm = _mapper_or_none(scls)
+ if sm.concrete and cls in scls.__bases__:
+ sm._set_concrete_base(m)
"""
# TODO: need coverage for this event
+ def after_configured(self):
+ """Called after a series of mappers have been configured.
+
+ This corresponds to the :func:`.orm.configure_mappers` call, which
+ note is usually called automatically as mappings are first
+ used.
+
+ Theoretically this event is called once per
+ application, but is actually called any time new mappers
+ have been affected by a :func:`.orm.configure_mappers` call. If new mappings
+ are constructed after existing ones have already been used,
+ this event can be called again.
+
+ """
+
def translate_row(self, mapper, context, row):
"""Perform pre-processing on the given result row and return a
new row instance.
self.allow_partial_pks = allow_partial_pks
- if with_polymorphic == '*':
- self.with_polymorphic = ('*', None)
- elif isinstance(with_polymorphic, (tuple, list)):
- if isinstance(with_polymorphic[0], (basestring, tuple, list)):
- self.with_polymorphic = with_polymorphic
- else:
- self.with_polymorphic = (with_polymorphic, None)
- elif with_polymorphic is not None:
- raise sa_exc.ArgumentError("Invalid setting for with_polymorphic")
- else:
- self.with_polymorphic = None
+ self._set_with_polymorphic(with_polymorphic)
if isinstance(self.local_table, expression._SelectBase):
raise sa_exc.InvalidRequestError(
local_table = None
"""The :class:`.Selectable` which this :class:`.Mapper` manages.
-
+
Typically is an instance of :class:`.Table` or :class:`.Alias`.
May also be ``None``.
-
+
The "local" table is the
selectable that the :class:`.Mapper` is directly responsible for
managing from an attribute access and flush perspective. For
single-table inheriting mapper, local_table will be ``None``.
See also :attr:`~.Mapper.mapped_table`.
-
+
"""
mapped_table = None
"""The :class:`.Selectable` to which this :class:`.Mapper` is mapped.
-
+
Typically an instance of :class:`.Table`, :class:`.Join`, or
- :class:`.Alias`.
-
+ :class:`.Alias`.
+
The "mapped" table is the selectable that
the mapper selects from during queries. For non-inheriting
mappers, the mapped table is the same as the "local" table.
full :class:`.Join` representing full rows for this particular
subclass. For single-table inheritance mappers, mapped_table
references the base table.
-
+
See also :attr:`~.Mapper.local_table`.
-
+
"""
inherits = None
"""References the :class:`.Mapper` which this :class:`.Mapper`
inherits from, if any.
- This is a *read only* attribute determined during mapper construction.
+ This is a *read only* attribute determined during mapper construction.
Behavior is undefined if directly modified.
-
+
"""
configured = None
"""Represent ``True`` if this :class:`.Mapper` has been configured.
- This is a *read only* attribute determined during mapper construction.
+ This is a *read only* attribute determined during mapper construction.
Behavior is undefined if directly modified.
-
+
See also :func:`.configure_mappers`.
-
+
"""
concrete = None
"""Represent ``True`` if this :class:`.Mapper` is a concrete
inheritance mapper.
- This is a *read only* attribute determined during mapper construction.
+ This is a *read only* attribute determined during mapper construction.
Behavior is undefined if directly modified.
-
+
"""
tables = None
"""An iterable containing the collection of :class:`.Table` objects
which this :class:`.Mapper` is aware of.
-
+
If the mapper is mapped to a :class:`.Join`, or an :class:`.Alias`
representing a :class:`.Select`, the individual :class:`.Table`
objects that comprise the full construct will be represented here.
- This is a *read only* attribute determined during mapper construction.
+ This is a *read only* attribute determined during mapper construction.
Behavior is undefined if directly modified.
-
+
"""
primary_key = None
"""An iterable containing the collection of :class:`.Column` objects
which comprise the 'primary key' of the mapped table, from the
perspective of this :class:`.Mapper`.
-
+
This list is against the selectable in :attr:`~.Mapper.mapped_table`. In the
case of inheriting mappers, some columns may be managed by a superclass
mapper. For example, in the case of a :class:`.Join`, the primary
key is determined by all of the primary key columns across all tables
referenced by the :class:`.Join`.
-
+
The list is also not necessarily the same as the primary key column
collection associated with the underlying tables; the :class:`.Mapper`
features a ``primary_key`` argument that can override what the
:class:`.Mapper` considers as primary key columns.
- This is a *read only* attribute determined during mapper construction.
+ This is a *read only* attribute determined during mapper construction.
Behavior is undefined if directly modified.
-
+
"""
class_ = None
"""The Python class which this :class:`.Mapper` maps.
- This is a *read only* attribute determined during mapper construction.
+ This is a *read only* attribute determined during mapper construction.
Behavior is undefined if directly modified.
-
+
"""
class_manager = None
"""The :class:`.ClassManager` which maintains event listeners
and class-bound descriptors for this :class:`.Mapper`.
- This is a *read only* attribute determined during mapper construction.
+ This is a *read only* attribute determined during mapper construction.
Behavior is undefined if directly modified.
"""
single = None
"""Represent ``True`` if this :class:`.Mapper` is a single table
- inheritance mapper.
-
+ inheritance mapper.
+
:attr:`~.Mapper.local_table` will be ``None`` if this flag is set.
-
- This is a *read only* attribute determined during mapper construction.
+
+ This is a *read only* attribute determined during mapper construction.
Behavior is undefined if directly modified.
-
+
"""
non_primary = None
mapper, e.g. a mapper that is used only to selet rows but not for
persistence management.
- This is a *read only* attribute determined during mapper construction.
+ This is a *read only* attribute determined during mapper construction.
Behavior is undefined if directly modified.
-
+
"""
polymorphic_on = None
"""The :class:`.Column` specified as the ``polymorphic_on`` column
for this :class:`.Mapper`, within an inheritance scenario.
-
+
This attribute may also be of other types besides :class:`.Column`
in a future SQLAlchemy release.
- This is a *read only* attribute determined during mapper construction.
+ This is a *read only* attribute determined during mapper construction.
Behavior is undefined if directly modified.
-
+
"""
polymorphic_map = None
"""A mapping of "polymorphic identity" identifiers mapped to :class:`.Mapper`
instances, within an inheritance scenario.
-
+
The identifiers can be of any type which is comparable to the
type of column represented by :attr:`~.Mapper.polymorphic_on`.
-
+
An inheritance chain of mappers will all reference the same
polymorphic map object. The object is used to correlate incoming
result rows to target mappers.
- This is a *read only* attribute determined during mapper construction.
+ This is a *read only* attribute determined during mapper construction.
Behavior is undefined if directly modified.
"""
polymorphic_identity = None
"""Represent an identifier which is matched against the :attr:`~.Mapper.polymorphic_on`
column during result row loading.
-
+
Used only with inheritance, this object can be of any type which is
comparable to the type of column represented by :attr:`~.Mapper.polymorphic_on`.
-
- This is a *read only* attribute determined during mapper construction.
+
+ This is a *read only* attribute determined during mapper construction.
Behavior is undefined if directly modified.
-
+
"""
base_mapper = None
"""The base-most :class:`.Mapper` in an inheritance chain.
-
+
In a non-inheriting scenario, this attribute will always be this
:class:`.Mapper`. In an inheritance scenario, it references
the :class:`.Mapper` which is parent to all other :class:`.Mapper`
objects in the inheritance chain.
- This is a *read only* attribute determined during mapper construction.
+ This is a *read only* attribute determined during mapper construction.
Behavior is undefined if directly modified.
-
+
"""
columns = None
"""A collection of :class:`.Column` or other scalar expression
objects maintained by this :class:`.Mapper`.
-
+
The collection behaves the same as that of the ``c`` attribute on
any :class:`.Table` object, except that only those columns included in
this mapping are present, and are keyed based on the attribute name
:class:`.Column` itself. Additionally, scalar expressions mapped
by :func:`.column_property` are also present here.
- This is a *read only* attribute determined during mapper construction.
+ This is a *read only* attribute determined during mapper construction.
Behavior is undefined if directly modified.
-
+
"""
validators = None
if self.polymorphic_identity is not None:
self.polymorphic_map[self.polymorphic_identity] = self
- if self.polymorphic_on is None:
- for mapper in self.iterate_to_root():
- # try to set up polymorphic on using
- # correesponding_column(); else leave
- # as None
- if mapper.polymorphic_on is not None:
- self.polymorphic_on = \
- self.mapped_table.corresponding_column(
- mapper.polymorphic_on)
- break
else:
self._all_tables = set()
self.base_mapper = self
"Mapper '%s' does not have a mapped_table specified."
% self)
+ def _set_with_polymorphic(self, with_polymorphic):
+ if with_polymorphic == '*':
+ self.with_polymorphic = ('*', None)
+ elif isinstance(with_polymorphic, (tuple, list)):
+ if isinstance(with_polymorphic[0], (basestring, tuple, list)):
+ self.with_polymorphic = with_polymorphic
+ else:
+ self.with_polymorphic = (with_polymorphic, None)
+ elif with_polymorphic is not None:
+ raise sa_exc.ArgumentError("Invalid setting for with_polymorphic")
+ else:
+ self.with_polymorphic = None
+
+ if isinstance(self.local_table, expression._SelectBase):
+ raise sa_exc.InvalidRequestError(
+ "When mapping against a select() construct, map against "
+ "an alias() of the construct instead."
+ "This because several databases don't allow a "
+ "SELECT from a subquery that does not have an alias."
+ )
+
+ if self.with_polymorphic and \
+ isinstance(self.with_polymorphic[1],
+ expression._SelectBase):
+ self.with_polymorphic = (self.with_polymorphic[0],
+ self.with_polymorphic[1].alias())
+ if self.configured:
+ self._expire_memoizations()
+
+ def _set_concrete_base(self, mapper):
+ """Set the given :class:`.Mapper` as the 'inherits' for this :class:`.Mapper`,
+ assuming this :class:`.Mapper` is concrete and does not already have
+ an inherits."""
+
+ assert self.concrete
+ assert not self.inherits
+ assert isinstance(mapper, Mapper)
+ self.inherits = mapper
+ self.inherits.polymorphic_map.update(self.polymorphic_map)
+ self.polymorphic_map = self.inherits.polymorphic_map
+ for mapper in self.iterate_to_root():
+ if mapper.polymorphic_on is not None:
+ mapper._requires_row_aliasing = True
+ self.batch = self.inherits.batch
+ self.base_mapper = self.inherits.base_mapper
+ self.inherits._inheriting_mappers.add(self)
+ self.passive_updates = self.inherits.passive_updates
+ self._all_tables = self.inherits._all_tables
+
+ def _set_polymorphic_on(self, polymorphic_on):
+ self.polymorphic_on = polymorphic_on
+ self._configure_polymorphic_setter(True)
+
def _configure_legacy_instrument_class(self):
if self.inherits:
init=False,
setparent=True)
- def _configure_polymorphic_setter(self):
+ def _configure_polymorphic_setter(self, init=False):
"""Configure an attribute on the mapper representing the
'polymorphic_on' column, if applicable, and not
already generated by _configure_properties (which is typical).
# but we need it for the base mapper
setter = False
+ if self.polymorphic_on is None:
+ for mapper in self.iterate_to_root():
+ # try to set up polymorphic on using
+ # correesponding_column(); else leave
+ # as None
+ if mapper.polymorphic_on is not None:
+ self.polymorphic_on = \
+ self.mapped_table.corresponding_column(
+ mapper.polymorphic_on)
+ break
+
if self.polymorphic_on is not None:
setter = True
self._configure_property(
col.key,
properties.ColumnProperty(col, _instrument=instrument),
- init=False, setparent=True)
+ init=init, setparent=True)
polymorphic_key = col.key
else:
polymorphic_key = self._columntoproperty[self.polymorphic_on].key
if not _new_mappers:
return
+ _call_configured = None
_COMPILE_MUTEX.acquire()
try:
global _already_compiling
mapper._post_configure_properties()
mapper._expire_memoizations()
mapper.dispatch.mapper_configured(mapper, mapper.class_)
+ _call_configured = mapper
except:
exc = sys.exc_info()[1]
if not hasattr(exc, '_configure_failed'):
_already_compiling = False
finally:
_COMPILE_MUTEX.release()
-
+ if _call_configured is not None:
+ _call_configured.dispatch.after_configured()
def reconstructor(fn):
"""Decorate a method as the 'reconstructor' hook.
return state.has_identity
def _is_mapped_class(cls):
+ """Return True if the given object is a mapped class,
+ :class:`.Mapper`, or :class:`.AliasedClass`."""
+
if isinstance(cls, (AliasedClass, mapperlib.Mapper)):
return True
if isinstance(cls, expression.ClauseElement):
return manager and _INSTRUMENTOR in manager.info
return False
+def _mapper_or_none(cls):
+ """Return the :class:`.Mapper` for the given class or None if the
+ class is not mapped."""
+
+ manager = attributes.manager_of_class(cls)
+ if manager is not None and _INSTRUMENTOR in manager.info:
+ return manager.info[_INSTRUMENTOR]
+ else:
+ return None
+
def instance_str(instance):
"""Return a string describing an instance."""
Session
from test.lib.testing import eq_
from sqlalchemy.util import classproperty
-from sqlalchemy.ext.declarative import declared_attr
+from sqlalchemy.ext.declarative import declared_attr, AbstractConcreteBase, ConcreteBase
from test.lib import fixtures
class DeclarativeTestBase(fixtures.TestBase, testing.AssertsExecutionResults):
assert A.__mapper__.inherits is a_1.__mapper__
- def test_concrete(self):
+from test.orm.test_events import _RemoveListeners
+class ConcreteInhTest(_RemoveListeners, DeclarativeTestBase):
+ def _roundtrip(self, Employee, Manager, Engineer, polymorphic=True):
+ Base.metadata.create_all()
+ sess = create_session()
+ e1 = Engineer(name='dilbert', primary_language='java')
+ e2 = Engineer(name='wally', primary_language='c++')
+ m1 = Manager(name='dogbert', golf_swing='fore!')
+ e3 = Engineer(name='vlad', primary_language='cobol')
+ sess.add_all([e1, e2, m1, e3])
+ sess.flush()
+ sess.expunge_all()
+ if polymorphic:
+ eq_(sess.query(Employee).order_by(Employee.name).all(),
+ [Engineer(name='dilbert'), Manager(name='dogbert'),
+ Engineer(name='vlad'), Engineer(name='wally')])
+ else:
+ eq_(sess.query(Engineer).order_by(Engineer.name).all(),
+ [Engineer(name='dilbert'), Engineer(name='vlad'),
+ Engineer(name='wally')])
+ eq_(sess.query(Manager).all(), [Manager(name='dogbert')])
+
+
+ def test_explicit(self):
engineers = Table('engineers', Base.metadata, Column('id',
Integer, primary_key=True,
test_needs_autoincrement=True), Column('name'
punion = polymorphic_union({'engineer': engineers, 'manager'
: managers}, 'type', 'punion')
- class Person(Base, fixtures.ComparableEntity):
+ class Employee(Base, fixtures.ComparableEntity):
__table__ = punion
__mapper_args__ = {'polymorphic_on': punion.c.type}
- class Engineer(Person):
+ class Engineer(Employee):
__table__ = engineers
__mapper_args__ = {'polymorphic_identity': 'engineer',
'concrete': True}
- class Manager(Person):
+ class Manager(Employee):
__table__ = managers
__mapper_args__ = {'polymorphic_identity': 'manager',
'concrete': True}
-
- Base.metadata.create_all()
- sess = create_session()
- e1 = Engineer(name='dilbert', primary_language='java')
- e2 = Engineer(name='wally', primary_language='c++')
- m1 = Manager(name='dogbert', golf_swing='fore!')
- e3 = Engineer(name='vlad', primary_language='cobol')
- sess.add_all([e1, e2, m1, e3])
- sess.flush()
- sess.expunge_all()
- eq_(sess.query(Person).order_by(Person.name).all(),
- [Engineer(name='dilbert'), Manager(name='dogbert'),
- Engineer(name='vlad'), Engineer(name='wally')])
+ self._roundtrip(Employee, Manager, Engineer)
def test_concrete_inline_non_polymorphic(self):
"""test the example from the declarative docs."""
- class Person(Base, fixtures.ComparableEntity):
+ class Employee(Base, fixtures.ComparableEntity):
__tablename__ = 'people'
id = Column(Integer, primary_key=True,
test_needs_autoincrement=True)
name = Column(String(50))
- class Engineer(Person):
+ class Engineer(Employee):
__tablename__ = 'engineers'
__mapper_args__ = {'concrete': True}
primary_language = Column(String(50))
name = Column(String(50))
- class Manager(Person):
+ class Manager(Employee):
__tablename__ = 'manager'
__mapper_args__ = {'concrete': True}
test_needs_autoincrement=True)
golf_swing = Column(String(50))
name = Column(String(50))
+ self._roundtrip(Employee, Manager, Engineer, polymorphic=False)
+
+ def test_abstract_concrete_extension(self):
+ class Employee(AbstractConcreteBase, Base, fixtures.ComparableEntity):
+ pass
+
+ class Manager(Employee):
+ __tablename__ = 'manager'
+ employee_id = Column(Integer, primary_key=True)
+ name = Column(String(50))
+ golf_swing = Column(String(40))
+ __mapper_args__ = {
+ 'polymorphic_identity':'manager',
+ 'concrete':True}
+
+ class Engineer(Employee):
+ __tablename__ = 'engineer'
+ employee_id = Column(Integer, primary_key=True)
+ name = Column(String(50))
+ primary_language = Column(String(40))
+ __mapper_args__ = {'polymorphic_identity':'engineer',
+ 'concrete':True}
+
+ self._roundtrip(Employee, Manager, Engineer)
+
+ def test_concrete_extension(self):
+ class Employee(ConcreteBase, Base, fixtures.ComparableEntity):
+ __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))
+ golf_swing = Column(String(40))
+ __mapper_args__ = {
+ 'polymorphic_identity':'manager',
+ 'concrete':True}
+
+ class Engineer(Employee):
+ __tablename__ = 'engineer'
+ employee_id = Column(Integer, primary_key=True)
+ name = Column(String(50))
+ primary_language = Column(String(40))
+ __mapper_args__ = {'polymorphic_identity':'engineer',
+ 'concrete':True}
+ self._roundtrip(Employee, Manager, Engineer)
- Base.metadata.create_all()
- sess = create_session()
- e1 = Engineer(name='dilbert', primary_language='java')
- e2 = Engineer(name='wally', primary_language='c++')
- m1 = Manager(name='dogbert', golf_swing='fore!')
- e3 = Engineer(name='vlad', primary_language='cobol')
- sess.add_all([e1, e2, m1, e3])
- sess.flush()
- sess.expunge_all()
- eq_(sess.query(Engineer).order_by(Engineer.name).all(),
- [Engineer(name='dilbert'), Engineer(name='vlad'),
- Engineer(name='wally')])
- eq_(sess.query(Manager).all(), [Manager(name='dogbert')])
def _produce_test(inline, stringbased):