summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/orm/mapper.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy/orm/mapper.py')
-rw-r--r--lib/sqlalchemy/orm/mapper.py426
1 files changed, 377 insertions, 49 deletions
diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py
index a169d6d63..31e7e9141 100644
--- a/lib/sqlalchemy/orm/mapper.py
+++ b/lib/sqlalchemy/orm/mapper.py
@@ -22,26 +22,18 @@ from collections import deque
from .. import sql, util, log, exc as sa_exc, event, schema, inspection
from ..sql import expression, visitors, operators, util as sql_util
-from . import instrumentation, attributes, \
- exc as orm_exc, events, loading
+from . import instrumentation, attributes, exc as orm_exc, loading
+from . import properties
from .interfaces import MapperProperty, _InspectionAttr, _MappedAttribute
-from .util import _INSTRUMENTOR, _class_to_mapper, \
- _state_mapper, class_mapper, \
- PathRegistry, state_str
+from .base import _class_to_mapper, _state_mapper, class_mapper, \
+ state_str, _INSTRUMENTOR
+from .path_registry import PathRegistry
+
import sys
-properties = util.importlater("sqlalchemy.orm", "properties")
-descriptor_props = util.importlater("sqlalchemy.orm", "descriptor_props")
-__all__ = (
- 'Mapper',
- '_mapper_registry',
- 'class_mapper',
- 'object_mapper',
- )
_mapper_registry = weakref.WeakKeyDictionary()
-_new_mappers = False
_already_compiling = False
_memoized_configured_property = util.group_expirable_memoized_property()
@@ -90,9 +82,12 @@ class Mapper(_InspectionAttr):
"""
+
+ _new_mappers = False
+
def __init__(self,
class_,
- local_table,
+ local_table=None,
properties=None,
primary_key=None,
non_primary=False,
@@ -119,10 +114,357 @@ class Mapper(_InspectionAttr):
legacy_is_orphan=False,
_compiled_cache_size=100,
):
- """Construct a new mapper.
+ """Return a new :class:`~.Mapper` object.
+
+ This function is typically used behind the scenes
+ via the Declarative extension. When using Declarative,
+ many of the usual :func:`.mapper` arguments are handled
+ by the Declarative extension itself, including ``class_``,
+ ``local_table``, ``properties``, and ``inherits``.
+ Other options are passed to :func:`.mapper` using
+ the ``__mapper_args__`` class variable::
+
+ class MyClass(Base):
+ __tablename__ = 'my_table'
+ id = Column(Integer, primary_key=True)
+ type = Column(String(50))
+ alt = Column("some_alt", Integer)
+
+ __mapper_args__ = {
+ 'polymorphic_on' : type
+ }
+
+
+ Explicit use of :func:`.mapper`
+ is often referred to as *classical mapping*. The above
+ declarative example is equivalent in classical form to::
+
+ my_table = Table("my_table", metadata,
+ Column('id', Integer, primary_key=True),
+ Column('type', String(50)),
+ Column("some_alt", Integer)
+ )
+
+ class MyClass(object):
+ pass
+
+ mapper(MyClass, my_table,
+ polymorphic_on=my_table.c.type,
+ properties={
+ 'alt':my_table.c.some_alt
+ })
+
+ See also:
+
+ :ref:`classical_mapping` - discussion of direct usage of
+ :func:`.mapper`
+
+ :param class\_: The class to be mapped. When using Declarative,
+ this argument is automatically passed as the declared class
+ itself.
+
+ :param local_table: The :class:`.Table` or other selectable
+ to which the class is mapped. May be ``None`` if
+ this mapper inherits from another mapper using single-table
+ inheritance. When using Declarative, this argument is
+ automatically passed by the extension, based on what
+ is configured via the ``__table__`` argument or via the
+ :class:`.Table` produced as a result of the ``__tablename__``
+ and :class:`.Column` arguments present.
+
+ :param always_refresh: If True, all query operations for this mapped
+ class will overwrite all data within object instances that already
+ exist within the session, erasing any in-memory changes with
+ whatever information was loaded from the database. Usage of this
+ flag is highly discouraged; as an alternative, see the method
+ :meth:`.Query.populate_existing`.
+
+ :param allow_partial_pks: Defaults to True. Indicates that a
+ composite primary key with some NULL values should be considered as
+ possibly existing within the database. This affects whether a
+ mapper will assign an incoming row to an existing identity, as well
+ as if :meth:`.Session.merge` will check the database first for a
+ particular primary key value. A "partial primary key" can occur if
+ one has mapped to an OUTER JOIN, for example.
+
+ :param batch: Defaults to ``True``, indicating that save operations
+ of multiple entities can be batched together for efficiency.
+ Setting to False indicates
+ that an instance will be fully saved before saving the next
+ instance. This is used in the extremely rare case that a
+ :class:`.MapperEvents` listener requires being called
+ in between individual row persistence operations.
+
+ :param column_prefix: A string which will be prepended
+ to the mapped attribute name when :class:`.Column`
+ objects are automatically assigned as attributes to the
+ mapped class. Does not affect explicitly specified
+ column-based properties.
+
+ See the section :ref:`column_prefix` for an example.
+
+ :param concrete: If True, indicates this mapper should use concrete
+ table inheritance with its parent mapper.
+
+ See the section :ref:`concrete_inheritance` for an example.
+
+ :param exclude_properties: A list or set of string column names to
+ be excluded from mapping.
+
+ See :ref:`include_exclude_cols` for an example.
+
+ :param extension: A :class:`.MapperExtension` instance or
+ list of :class:`.MapperExtension` instances which will be applied
+ to all operations by this :class:`.Mapper`. **Deprecated.**
+ Please see :class:`.MapperEvents`.
+
+ :param include_properties: An inclusive list or set of string column
+ names to map.
+
+ See :ref:`include_exclude_cols` for an example.
+
+ :param inherits: A mapped class or the corresponding :class:`.Mapper`
+ of one indicating a superclass to which this :class:`.Mapper`
+ should *inherit* from. The mapped class here must be a subclass
+ of the other mapper's class. When using Declarative, this argument
+ is passed automatically as a result of the natural class
+ hierarchy of the declared classes.
+
+ See also:
+
+ :ref:`inheritance_toplevel`
+
+ :param inherit_condition: For joined table inheritance, a SQL
+ expression which will
+ define how the two tables are joined; defaults to a natural join
+ between the two tables.
+
+ :param inherit_foreign_keys: When ``inherit_condition`` is used and the
+ columns present are missing a :class:`.ForeignKey` configuration,
+ this parameter can be used to specify which columns are "foreign".
+ In most cases can be left as ``None``.
+
+ :param legacy_is_orphan: Boolean, defaults to ``False``.
+ When ``True``, specifies that "legacy" orphan consideration
+ is to be applied to objects mapped by this mapper, which means
+ that a pending (that is, not persistent) object is auto-expunged
+ from an owning :class:`.Session` only when it is de-associated
+ from *all* parents that specify a ``delete-orphan`` cascade towards
+ this mapper. The new default behavior is that the object is auto-expunged
+ when it is de-associated with *any* of its parents that specify
+ ``delete-orphan`` cascade. This behavior is more consistent with
+ that of a persistent object, and allows behavior to be consistent
+ in more scenarios independently of whether or not an orphanable
+ object has been flushed yet or not.
+
+ See the change note and example at :ref:`legacy_is_orphan_addition`
+ for more detail on this change.
+
+ .. versionadded:: 0.8 - the consideration of a pending object as
+ an "orphan" has been modified to more closely match the
+ behavior as that of persistent objects, which is that the object
+ is expunged from the :class:`.Session` as soon as it is
+ de-associated from any of its orphan-enabled parents. Previously,
+ the pending object would be expunged only if de-associated
+ from all of its orphan-enabled parents. The new flag ``legacy_is_orphan``
+ is added to :func:`.orm.mapper` which re-establishes the
+ legacy behavior.
+
+ :param non_primary: Specify that this :class:`.Mapper` is in addition
+ to the "primary" mapper, that is, the one used for persistence.
+ The :class:`.Mapper` created here may be used for ad-hoc
+ mapping of the class to an alternate selectable, for loading
+ only.
+
+ The ``non_primary`` feature is rarely needed with modern
+ usage.
+
+ :param order_by: A single :class:`.Column` or list of :class:`.Column`
+ objects for which selection operations should use as the default
+ ordering for entities. By default mappers have no pre-defined
+ ordering.
+
+ :param passive_updates: Indicates UPDATE behavior of foreign key
+ columns when a primary key column changes on a joined-table
+ inheritance mapping. Defaults to ``True``.
+
+ When True, it is assumed that ON UPDATE CASCADE is configured on
+ the foreign key in the database, and that the database will handle
+ propagation of an UPDATE from a source column to dependent columns
+ on joined-table rows.
+
+ When False, it is assumed that the database does not enforce
+ referential integrity and will not be issuing its own CASCADE
+ operation for an update. The :class:`.Mapper` here will
+ emit an UPDATE statement for the dependent columns during a
+ primary key change.
+
+ See also:
+
+ :ref:`passive_updates` - description of a similar feature as
+ used with :func:`.relationship`
+
+ :param polymorphic_on: Specifies the column, attribute, or
+ SQL expression used to determine the target class for an
+ incoming row, when inheriting classes are present.
+
+ This value is commonly a :class:`.Column` object that's
+ present in the mapped :class:`.Table`::
+
+ class Employee(Base):
+ __tablename__ = 'employee'
+
+ id = Column(Integer, primary_key=True)
+ discriminator = Column(String(50))
+
+ __mapper_args__ = {
+ "polymorphic_on":discriminator,
+ "polymorphic_identity":"employee"
+ }
+
+ It may also be specified
+ as a SQL expression, as in this example where we
+ use the :func:`.case` construct to provide a conditional
+ approach::
+
+ class Employee(Base):
+ __tablename__ = 'employee'
+
+ id = Column(Integer, primary_key=True)
+ discriminator = Column(String(50))
+
+ __mapper_args__ = {
+ "polymorphic_on":case([
+ (discriminator == "EN", "engineer"),
+ (discriminator == "MA", "manager"),
+ ], else_="employee"),
+ "polymorphic_identity":"employee"
+ }
+
+ It may also refer to any attribute
+ configured with :func:`.column_property`, or to the
+ string name of one::
+
+ class Employee(Base):
+ __tablename__ = 'employee'
+
+ id = Column(Integer, primary_key=True)
+ discriminator = Column(String(50))
+ employee_type = column_property(
+ case([
+ (discriminator == "EN", "engineer"),
+ (discriminator == "MA", "manager"),
+ ], else_="employee")
+ )
- Mappers are normally constructed via the
- :func:`~sqlalchemy.orm.mapper` function. See for details.
+ __mapper_args__ = {
+ "polymorphic_on":employee_type,
+ "polymorphic_identity":"employee"
+ }
+
+ .. versionchanged:: 0.7.4
+ ``polymorphic_on`` may be specified as a SQL expression,
+ or refer to any attribute configured with
+ :func:`.column_property`, or to the string name of one.
+
+ When setting ``polymorphic_on`` to reference an
+ attribute or expression that's not present in the
+ locally mapped :class:`.Table`, yet the value
+ of the discriminator should be persisted to the database,
+ the value of the
+ discriminator is not automatically set on new
+ instances; this must be handled by the user,
+ either through manual means or via event listeners.
+ A typical approach to establishing such a listener
+ looks like::
+
+ from sqlalchemy import event
+ from sqlalchemy.orm import object_mapper
+
+ @event.listens_for(Employee, "init", propagate=True)
+ def set_identity(instance, *arg, **kw):
+ mapper = object_mapper(instance)
+ instance.discriminator = mapper.polymorphic_identity
+
+ Where above, we assign the value of ``polymorphic_identity``
+ for the mapped class to the ``discriminator`` attribute,
+ thus persisting the value to the ``discriminator`` column
+ in the database.
+
+ See also:
+
+ :ref:`inheritance_toplevel`
+
+ :param polymorphic_identity: Specifies the value which
+ identifies this particular class as returned by the
+ column expression referred to by the ``polymorphic_on``
+ setting. As rows are received, the value corresponding
+ to the ``polymorphic_on`` column expression is compared
+ to this value, indicating which subclass should
+ be used for the newly reconstructed object.
+
+ :param properties: A dictionary mapping the string names of object
+ attributes to :class:`.MapperProperty` instances, which define the
+ persistence behavior of that attribute. Note that :class:`.Column`
+ objects present in
+ the mapped :class:`.Table` are automatically placed into
+ ``ColumnProperty`` instances upon mapping, unless overridden.
+ When using Declarative, this argument is passed automatically,
+ based on all those :class:`.MapperProperty` instances declared
+ in the declared class body.
+
+ :param primary_key: A list of :class:`.Column` objects which define the
+ primary key to be used against this mapper's selectable unit.
+ This is normally simply the primary key of the ``local_table``, but
+ can be overridden here.
+
+ :param version_id_col: A :class:`.Column`
+ that will be used to keep a running version id of mapped entities
+ in the database. This is used during save operations to ensure that
+ no other thread or process has updated the instance during the
+ lifetime of the entity, else a
+ :class:`~sqlalchemy.orm.exc.StaleDataError` exception is
+ thrown. By default the column must be of :class:`.Integer` type,
+ unless ``version_id_generator`` specifies a new generation
+ algorithm.
+
+ :param version_id_generator: A callable which defines the algorithm
+ used to generate new version ids. Defaults to an integer
+ generator. Can be replaced with one that generates timestamps,
+ uuids, etc. e.g.::
+
+ import uuid
+
+ class MyClass(Base):
+ __tablename__ = 'mytable'
+ id = Column(Integer, primary_key=True)
+ version_uuid = Column(String(32))
+
+ __mapper_args__ = {
+ 'version_id_col':version_uuid,
+ 'version_id_generator':lambda version:uuid.uuid4().hex
+ }
+
+ The callable receives the current version identifier as its
+ single argument.
+
+ :param with_polymorphic: A tuple in the form ``(<classes>,
+ <selectable>)`` indicating the default style of "polymorphic"
+ loading, that is, which tables are queried at once. <classes> is
+ any single or list of mappers and/or classes indicating the
+ inherited classes that should be loaded at once. The special value
+ ``'*'`` may be used to indicate all descending classes should be
+ loaded immediately. The second tuple argument <selectable>
+ indicates a selectable that will be used to query for multiple
+ classes.
+
+ See also:
+
+ :ref:`concrete_inheritance` - typically uses ``with_polymorphic``
+ to specify a UNION statement to select from.
+
+ :ref:`with_polymorphic` - usage example of the related
+ :meth:`.Query.with_polymorphic` method
"""
@@ -214,7 +556,7 @@ class Mapper(_InspectionAttr):
# configure_mappers() until construction succeeds)
_CONFIGURE_MUTEX.acquire()
try:
- events._MapperEventsHold.populate(class_, self)
+ self.dispatch._events._new_mapper_instance(class_, self)
self._configure_inheritance()
self._configure_legacy_instrument_class()
self._configure_class_instrumentation()
@@ -222,8 +564,7 @@ class Mapper(_InspectionAttr):
self._configure_properties()
self._configure_polymorphic_setter()
self._configure_pks()
- global _new_mappers
- _new_mappers = True
+ Mapper._new_mappers = True
self._log("constructed")
self._expire_memoizations()
finally:
@@ -474,8 +815,6 @@ class Mapper(_InspectionAttr):
c = None
"""A synonym for :attr:`~.Mapper.columns`."""
- dispatch = event.dispatcher(events.MapperEvents)
-
@util.memoized_property
def _path_registry(self):
return PathRegistry.per_mapper(self)
@@ -740,21 +1079,12 @@ class Mapper(_InspectionAttr):
manager.info[_INSTRUMENTOR] = self
- @util.deprecated("0.7", message=":meth:`.Mapper.compile` "
- "is replaced by :func:`.configure_mappers`")
- def compile(self):
- """Initialize the inter-mapper relationships of all mappers that
- have been constructed thus far.
+ @classmethod
+ def _configure_all(cls):
+ """Class-level path to the :func:`.configure_mappers` call.
"""
configure_mappers()
- return self
-
- @property
- @util.deprecated("0.7", message=":attr:`.Mapper.compiled` "
- "is replaced by :attr:`.Mapper.configured`")
- def compiled(self):
- return self.configured
def dispose(self):
# Disable any attribute-based compilation.
@@ -1365,7 +1695,7 @@ class Mapper(_InspectionAttr):
"""return a MapperProperty associated with the given key.
"""
- if _configure_mappers and _new_mappers:
+ if _configure_mappers and Mapper._new_mappers:
configure_mappers()
try:
@@ -1383,7 +1713,7 @@ class Mapper(_InspectionAttr):
@property
def iterate_properties(self):
"""return an iterator of all MapperProperty objects."""
- if _new_mappers:
+ if Mapper._new_mappers:
configure_mappers()
return iter(self._props.values())
@@ -1457,7 +1787,7 @@ class Mapper(_InspectionAttr):
@_memoized_configured_property
def _with_polymorphic_mappers(self):
- if _new_mappers:
+ if Mapper._new_mappers:
configure_mappers()
if not self.with_polymorphic:
return []
@@ -1564,7 +1894,7 @@ class Mapper(_InspectionAttr):
:attr:`.Mapper.all_orm_descriptors`
"""
- if _new_mappers:
+ if Mapper._new_mappers:
configure_mappers()
return util.ImmutableProperties(self._props)
@@ -1613,7 +1943,7 @@ class Mapper(_InspectionAttr):
objects.
"""
- return self._filter_properties(descriptor_props.SynonymProperty)
+ return self._filter_properties(properties.SynonymProperty)
@_memoized_configured_property
def column_attrs(self):
@@ -1652,10 +1982,10 @@ class Mapper(_InspectionAttr):
objects.
"""
- return self._filter_properties(descriptor_props.CompositeProperty)
+ return self._filter_properties(properties.CompositeProperty)
def _filter_properties(self, type_):
- if _new_mappers:
+ if Mapper._new_mappers:
configure_mappers()
return util.ImmutableProperties(util.OrderedDict(
(k, v) for k, v in self._props.items()
@@ -2109,7 +2439,6 @@ class Mapper(_InspectionAttr):
return result
-
def configure_mappers():
"""Initialize the inter-mapper relationships of all mappers that
have been constructed thus far.
@@ -2119,8 +2448,7 @@ def configure_mappers():
"""
- global _new_mappers
- if not _new_mappers:
+ if not Mapper._new_mappers:
return
_call_configured = None
@@ -2133,7 +2461,7 @@ def configure_mappers():
try:
# double-check inside mutex
- if not _new_mappers:
+ if not Mapper._new_mappers:
return
# initialize properties on all mappers
@@ -2162,7 +2490,7 @@ def configure_mappers():
mapper._configure_failed = exc
raise
- _new_mappers = False
+ Mapper._new_mappers = False
finally:
_already_compiling = False
finally:
@@ -2241,7 +2569,7 @@ def _event_on_first_init(manager, cls):
instrumenting_mapper = manager.info.get(_INSTRUMENTOR)
if instrumenting_mapper:
- if _new_mappers:
+ if Mapper._new_mappers:
configure_mappers()
@@ -2256,7 +2584,7 @@ def _event_on_init(state, args, kwargs):
instrumenting_mapper = state.manager.info.get(_INSTRUMENTOR)
if instrumenting_mapper:
- if _new_mappers:
+ if Mapper._new_mappers:
configure_mappers()
if instrumenting_mapper._set_polymorphic_identity:
instrumenting_mapper._set_polymorphic_identity(state)