summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2013-08-14 19:58:34 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2013-08-14 19:58:34 -0400
commit59141d360e70d1a762719206e3cb0220b4c53fef (patch)
tree954d39dfa15a5c7b3970549dd77ec96a72444876
parent688d799814fff2642926d3bce93b45965cf262da (diff)
downloadsqlalchemy-59141d360e70d1a762719206e3cb0220b4c53fef.tar.gz
- apply an import refactoring to the ORM as well
- rework the event system so that event modules load after their targets, dependencies are reversed - create an improved strategy lookup system for the ORM - rework the ORM to have very few import cycles - move out "importlater" to just util.dependency - other tricks to cross-populate modules in as clear a way as possible
-rw-r--r--doc/build/changelog/changelog_09.rst33
-rw-r--r--lib/sqlalchemy/__init__.py20
-rw-r--r--lib/sqlalchemy/engine/interfaces.py4
-rw-r--r--lib/sqlalchemy/event/__init__.py4
-rw-r--r--lib/sqlalchemy/event/attr.py17
-rw-r--r--lib/sqlalchemy/event/base.py44
-rw-r--r--lib/sqlalchemy/events.py43
-rw-r--r--lib/sqlalchemy/ext/declarative/api.py3
-rw-r--r--lib/sqlalchemy/ext/declarative/base.py2
-rw-r--r--lib/sqlalchemy/ext/instrumentation.py8
-rw-r--r--lib/sqlalchemy/orm/__init__.py1271
-rw-r--r--lib/sqlalchemy/orm/attributes.py120
-rw-r--r--lib/sqlalchemy/orm/base.py419
-rw-r--r--lib/sqlalchemy/orm/collections.py11
-rw-r--r--lib/sqlalchemy/orm/deprecated_interfaces.py4
-rw-r--r--lib/sqlalchemy/orm/descriptor_props.py163
-rw-r--r--lib/sqlalchemy/orm/dynamic.py3
-rw-r--r--lib/sqlalchemy/orm/events.py94
-rw-r--r--lib/sqlalchemy/orm/exc.py18
-rw-r--r--lib/sqlalchemy/orm/instrumentation.py77
-rw-r--r--lib/sqlalchemy/orm/interfaces.py131
-rw-r--r--lib/sqlalchemy/orm/loading.py8
-rw-r--r--lib/sqlalchemy/orm/mapper.py426
-rw-r--r--lib/sqlalchemy/orm/path_registry.py220
-rw-r--r--lib/sqlalchemy/orm/persistence.py2
-rw-r--r--lib/sqlalchemy/orm/properties.py1205
-rw-r--r--lib/sqlalchemy/orm/query.py44
-rw-r--r--lib/sqlalchemy/orm/relationships.py1592
-rw-r--r--lib/sqlalchemy/orm/session.py72
-rw-r--r--lib/sqlalchemy/orm/state.py84
-rw-r--r--lib/sqlalchemy/orm/strategies.py12
-rw-r--r--lib/sqlalchemy/orm/unitofwork.py8
-rw-r--r--lib/sqlalchemy/orm/util.py429
-rw-r--r--lib/sqlalchemy/pool.py4
-rw-r--r--lib/sqlalchemy/sql/__init__.py19
-rw-r--r--lib/sqlalchemy/sql/base.py18
-rw-r--r--lib/sqlalchemy/sql/elements.py1
-rw-r--r--lib/sqlalchemy/sql/expression.py63
-rw-r--r--lib/sqlalchemy/sql/schema.py13
-rw-r--r--lib/sqlalchemy/sql/sqltypes.py6
-rw-r--r--lib/sqlalchemy/sql/util.py4
-rw-r--r--lib/sqlalchemy/util/__init__.py2
-rw-r--r--lib/sqlalchemy/util/langhelpers.py188
-rw-r--r--test/ext/declarative/test_basic.py4
-rw-r--r--test/orm/inheritance/test_assorted_poly.py1
-rw-r--r--test/orm/test_mapper.py16
46 files changed, 3481 insertions, 3449 deletions
diff --git a/doc/build/changelog/changelog_09.rst b/doc/build/changelog/changelog_09.rst
index ea04ee024..16d0b63a2 100644
--- a/doc/build/changelog/changelog_09.rst
+++ b/doc/build/changelog/changelog_09.rst
@@ -14,28 +14,17 @@
when used against an empty collection. Also in 0.8.3.
.. change::
- :tags: general, sql
-
- A large refactoring of the ``sqlalchemy.sql`` package has reorganized
- the import structure of many core modules.
- ``sqlalchemy.schema`` and ``sqlalchemy.types``
- remain in the top-level package, but are now just lists of names
- that pull from within ``sqlalchemy.sql``. Their implementations
- are now broken out among ``sqlalchemy.sql.type_api``, ``sqlalchemy.sql.sqltypes``,
- ``sqlalchemy.sql.schema`` and ``sqlalchemy.sql.ddl``, the last of which was
- moved from ``sqlalchemy.engine``. ``sqlalchemy.sql.expression`` is also
- a namespace now which pulls implementations mostly from ``sqlalchemy.sql.elements``,
- ``sqlalchemy.sql.selectable``, and ``sqlalchemy.sql.dml``.
- Most of the "factory" functions
- used to create SQL expression objects have been moved to classmethods
- or constructors, which are exposed in ``sqlalchemy.sql.expression``
- using a programmatic system. Care has been taken such that all the
- original import namespaces remain intact and there should be no impact
- on any existing applications. The rationale here was to break out these
- very large modules into smaller ones, provide more manageable lists
- of function names, to greatly reduce "import cycles" and clarify the
- up-front importing of names, and to remove the need for redundant
- functions and documentation throughout the expression package.
+ :tags: general
+
+ A large refactoring of packages has reorganized
+ the import structure of many Core modules as well as some aspects
+ of the ORM modules. In particular ``sqlalchemy.sql`` has been broken
+ out into several more modules than before so that the very large size
+ of ``sqlalchemy.sql.expression`` is now pared down. The effort
+ has focused on a large reduction in import cycles. Additionally,
+ the system of API functions in ``sqlalchemy.sql.expression`` and
+ ``sqlalchemy.orm`` has been reorganized to eliminate redundancy
+ in documentation between the functions vs. the objects they produce.
.. change::
:tags: orm, feature, orm
diff --git a/lib/sqlalchemy/__init__.py b/lib/sqlalchemy/__init__.py
index d21d0fbb9..98156cdc9 100644
--- a/lib/sqlalchemy/__init__.py
+++ b/lib/sqlalchemy/__init__.py
@@ -4,8 +4,6 @@
# This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
-import inspect as _inspect
-import sys
from .sql import (
alias,
@@ -114,16 +112,20 @@ from .schema import (
from .inspection import inspect
-
from .engine import create_engine, engine_from_config
+__version__ = '0.9.0'
-__all__ = sorted(name for name, obj in locals().items()
- if not (name.startswith('_') or _inspect.ismodule(obj)))
+def __go(lcls):
+ global __all__
-__version__ = '0.9.0'
+ from . import events
+ from . import util as _sa_util
+
+ import inspect as _inspect
-del _inspect, sys
+ __all__ = sorted(name for name, obj in lcls.items()
+ if not (name.startswith('_') or _inspect.ismodule(obj)))
-from . import util as _sa_util
-_sa_util.importlater.resolve_all("sqlalchemy") \ No newline at end of file
+ _sa_util.dependencies.resolve_all("sqlalchemy")
+__go(locals()) \ No newline at end of file
diff --git a/lib/sqlalchemy/engine/interfaces.py b/lib/sqlalchemy/engine/interfaces.py
index 8f4af6db5..c94af5b1c 100644
--- a/lib/sqlalchemy/engine/interfaces.py
+++ b/lib/sqlalchemy/engine/interfaces.py
@@ -6,7 +6,7 @@
"""Define core interfaces used by the engine system."""
-from .. import util, event, events
+from .. import util, event
# backwards compat
from ..sql.compiler import Compiled, TypeCompiler
@@ -782,8 +782,6 @@ class Connectable(object):
"""
- dispatch = event.dispatcher(events.ConnectionEvents)
-
def connect(self, **kwargs):
"""Return a :class:`.Connection` object.
diff --git a/lib/sqlalchemy/event/__init__.py b/lib/sqlalchemy/event/__init__.py
index b996d0bbe..0a0131e23 100644
--- a/lib/sqlalchemy/event/__init__.py
+++ b/lib/sqlalchemy/event/__init__.py
@@ -5,6 +5,6 @@
# the MIT License: http://www.opensource.org/licenses/mit-license.php
from .api import CANCEL, NO_RETVAL, listen, listens_for, remove, contains
-from .base import Events
-from .attr import dispatcher, RefCollection
+from .base import Events, dispatcher
+from .attr import RefCollection
from .legacy import _legacy_signature
diff --git a/lib/sqlalchemy/event/attr.py b/lib/sqlalchemy/event/attr.py
index d667736a1..629ea5800 100644
--- a/lib/sqlalchemy/event/attr.py
+++ b/lib/sqlalchemy/event/attr.py
@@ -363,20 +363,3 @@ class _JoinedListener(_CompoundListener):
raise NotImplementedError()
-class dispatcher(object):
- """Descriptor used by target classes to
- deliver the _Dispatch class at the class level
- and produce new _Dispatch instances for target
- instances.
-
- """
- def __init__(self, events):
- self.dispatch_cls = events.dispatch
- self.events = events
-
- def __get__(self, obj, cls):
- if obj is None:
- return self.dispatch_cls
- obj.__dict__['dispatch'] = disp = self.dispatch_cls(cls)
- return disp
-
diff --git a/lib/sqlalchemy/event/base.py b/lib/sqlalchemy/event/base.py
index bb7b3b1b4..1d7bb9cd1 100644
--- a/lib/sqlalchemy/event/base.py
+++ b/lib/sqlalchemy/event/base.py
@@ -53,9 +53,17 @@ class _Dispatch(object):
"""
+ _events = None
+ """reference the :class:`.Events` class which this
+ :class:`._Dispatch` is created for."""
+
def __init__(self, _parent_cls):
self._parent_cls = _parent_cls
+ @util.classproperty
+ def _listen(cls):
+ return cls._events._listen
+
def _join(self, other):
"""Create a 'join' of this :class:`._Dispatch` and another.
@@ -115,15 +123,18 @@ def _create_dispatcher_class(cls, classname, bases, dict_):
# i.e. make a Dispatch class that shares the '_listen' method
# of the Event class, this is the straight monkeypatch.
dispatch_base = getattr(cls, 'dispatch', _Dispatch)
- cls.dispatch = dispatch_cls = type("%sDispatch" % classname,
+ dispatch_cls = type("%sDispatch" % classname,
(dispatch_base, ), {})
- dispatch_cls._listen = cls._listen
+ cls._set_dispatch(cls, dispatch_cls)
for k in dict_:
if _is_event_name(k):
setattr(dispatch_cls, k, _DispatchDescriptor(cls, dict_[k]))
_registrars[k].append(cls)
+ if getattr(cls, '_dispatch_target', None):
+ cls._dispatch_target.dispatch = dispatcher(cls)
+
def _remove_dispatcher(cls):
for k in dir(cls):
@@ -135,6 +146,17 @@ def _remove_dispatcher(cls):
class Events(util.with_metaclass(_EventMeta, object)):
"""Define event listening functions for a particular target type."""
+ @staticmethod
+ def _set_dispatch(cls, dispatch_cls):
+ # this allows an Events subclass to define additional utility
+ # methods made available to the target via
+ # "self.dispatch._events.<utilitymethod>"
+ # @staticemethod to allow easy "super" calls while in a metaclass
+ # constructor.
+ cls.dispatch = dispatch_cls
+ dispatch_cls._events = cls
+
+
@classmethod
def _accept_with(cls, target):
# Mapper, ClassManager, Session override this to
@@ -169,3 +191,21 @@ class _JoinedDispatcher(object):
self.parent = parent
self._parent_cls = local._parent_cls
+
+class dispatcher(object):
+ """Descriptor used by target classes to
+ deliver the _Dispatch class at the class level
+ and produce new _Dispatch instances for target
+ instances.
+
+ """
+ def __init__(self, events):
+ self.dispatch_cls = events.dispatch
+ self.events = events
+
+ def __get__(self, obj, cls):
+ if obj is None:
+ return self.dispatch_cls
+ obj.__dict__['dispatch'] = disp = self.dispatch_cls(cls)
+ return disp
+
diff --git a/lib/sqlalchemy/events.py b/lib/sqlalchemy/events.py
index a5dc6e326..b58b53916 100644
--- a/lib/sqlalchemy/events.py
+++ b/lib/sqlalchemy/events.py
@@ -6,8 +6,10 @@
"""Core event interfaces."""
-from . import event, exc, util
-
+from . import event, exc
+from .pool import Pool
+from .engine import Connectable, Engine
+from .sql.base import SchemaEventTarget
class DDLEvents(event.Events):
"""
@@ -69,6 +71,7 @@ class DDLEvents(event.Events):
"""
_target_class_doc = "SomeSchemaClassOrObject"
+ _dispatch_target = SchemaEventTarget
def before_create(self, target, connection, **kw):
"""Called before CREATE statments are emitted.
@@ -217,25 +220,6 @@ class DDLEvents(event.Events):
"""
-class SchemaEventTarget(object):
- """Base class for elements that are the targets of :class:`.DDLEvents`
- events.
-
- This includes :class:`.SchemaItem` as well as :class:`.SchemaType`.
-
- """
- dispatch = event.dispatcher(DDLEvents)
-
- def _set_parent(self, parent):
- """Associate with this SchemaEvent's parent object."""
-
- raise NotImplementedError()
-
- def _set_parent_with_dispatch(self, parent):
- self.dispatch.before_parent_attach(self, parent)
- self._set_parent(parent)
- self.dispatch.after_parent_attach(self, parent)
-
class PoolEvents(event.Events):
"""Available events for :class:`.Pool`.
@@ -267,19 +251,16 @@ class PoolEvents(event.Events):
"""
_target_class_doc = "SomeEngineOrPool"
+ _dispatch_target = Pool
@classmethod
- @util.dependencies(
- "sqlalchemy.engine",
- "sqlalchemy.pool"
- )
- def _accept_with(cls, engine, pool, target):
+ def _accept_with(cls, target):
if isinstance(target, type):
- if issubclass(target, engine.Engine):
- return pool.Pool
- elif issubclass(target, pool.Pool):
+ if issubclass(target, Engine):
+ return Pool
+ elif issubclass(target, Pool):
return target
- elif isinstance(target, engine.Engine):
+ elif isinstance(target, Engine):
return target.pool
else:
return target
@@ -450,6 +431,8 @@ class ConnectionEvents(event.Events):
"""
_target_class_doc = "SomeEngine"
+ _dispatch_target = Connectable
+
@classmethod
def _listen(cls, event_key, retval=False):
diff --git a/lib/sqlalchemy/ext/declarative/api.py b/lib/sqlalchemy/ext/declarative/api.py
index 90076fdbb..b309a783a 100644
--- a/lib/sqlalchemy/ext/declarative/api.py
+++ b/lib/sqlalchemy/ext/declarative/api.py
@@ -10,7 +10,8 @@ from ...schema import Table, MetaData
from ...orm import synonym as _orm_synonym, mapper,\
comparable_property,\
interfaces
-from ...orm.util import polymorphic_union, _mapper_or_none
+from ...orm.util import polymorphic_union
+from ...orm.base import _mapper_or_none
from ... import exc
import weakref
diff --git a/lib/sqlalchemy/ext/declarative/base.py b/lib/sqlalchemy/ext/declarative/base.py
index 5a2b88db4..820c0874b 100644
--- a/lib/sqlalchemy/ext/declarative/base.py
+++ b/lib/sqlalchemy/ext/declarative/base.py
@@ -9,7 +9,7 @@ from ...schema import Table, Column
from ...orm import mapper, class_mapper
from ...orm.interfaces import MapperProperty
from ...orm.properties import ColumnProperty, CompositeProperty
-from ...orm.util import _is_mapped_class
+from ...orm.base import _is_mapped_class
from ... import util, exc
from ...sql import expression
from ... import event
diff --git a/lib/sqlalchemy/ext/instrumentation.py b/lib/sqlalchemy/ext/instrumentation.py
index bb44a492c..714a76e68 100644
--- a/lib/sqlalchemy/ext/instrumentation.py
+++ b/lib/sqlalchemy/ext/instrumentation.py
@@ -31,7 +31,7 @@ from ..orm.instrumentation import (
ClassManager, InstrumentationFactory, _default_state_getter,
_default_dict_getter, _default_manager_getter
)
-from ..orm import attributes, collections
+from ..orm import attributes, collections, base as orm_base
from .. import util
from ..orm import exc as orm_exc
import weakref
@@ -399,9 +399,9 @@ def _install_lookups(lookups):
instance_state = lookups['instance_state']
instance_dict = lookups['instance_dict']
manager_of_class = lookups['manager_of_class']
- attributes.instance_state = \
+ orm_base.instance_state = attributes.instance_state = \
orm_instrumentation.instance_state = instance_state
- attributes.instance_dict = \
+ orm_base.instance_dict = attributes.instance_dict = \
orm_instrumentation.instance_dict = instance_dict
- attributes.manager_of_class = \
+ orm_base.manager_of_class = attributes.manager_of_class = \
orm_instrumentation.manager_of_class = manager_of_class
diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py
index 112035ac5..e70cc1c55 100644
--- a/lib/sqlalchemy/orm/__init__.py
+++ b/lib/sqlalchemy/orm/__init__.py
@@ -24,11 +24,13 @@ from .mapper import (
from .interfaces import (
EXT_CONTINUE,
EXT_STOP,
- MapperExtension,
PropComparator,
+ )
+from .deprecated_interfaces import (
+ MapperExtension,
SessionExtension,
AttributeExtension,
- )
+)
from .util import (
aliased,
join,
@@ -39,14 +41,13 @@ from .util import (
with_parent,
with_polymorphic,
)
-from .properties import (
- ColumnProperty,
+from .properties import ColumnProperty
+from .relationships import RelationshipProperty
+from .descriptor_props import (
ComparableProperty,
CompositeProperty,
- RelationshipProperty,
- PropertyLoader,
SynonymProperty,
- )
+ )
from .relationships import (
foreign,
remote,
@@ -61,75 +62,10 @@ from .scoping import (
scoped_session
)
from . import mapper as mapperlib
-from . import strategies
from .query import AliasOption, Query
-from ..sql import util as sql_util
-from .. import util as sa_util
-
-from . import interfaces
-
-# here, we can establish InstrumentationManager back
-# in sqlalchemy.orm and sqlalchemy.orm.interfaces, which
-# also re-establishes the extended instrumentation system.
-#from ..ext import instrumentation as _ext_instrumentation
-#InstrumentationManager = \
-# interfaces.InstrumentationManager = \
-# _ext_instrumentation.InstrumentationManager
-
-__all__ = (
- 'EXT_CONTINUE',
- 'EXT_STOP',
- 'MapperExtension',
- 'AttributeExtension',
- 'PropComparator',
- 'Query',
- 'Session',
- 'aliased',
- 'backref',
- 'class_mapper',
- 'clear_mappers',
- 'column_property',
- 'comparable_property',
- 'compile_mappers',
- 'configure_mappers',
- 'composite',
- 'contains_alias',
- 'contains_eager',
- 'create_session',
- 'defer',
- 'deferred',
- 'dynamic_loader',
- 'eagerload',
- 'eagerload_all',
- 'foreign',
- 'immediateload',
- 'join',
- 'joinedload',
- 'joinedload_all',
- 'lazyload',
- 'mapper',
- 'make_transient',
- 'noload',
- 'object_mapper',
- 'object_session',
- 'outerjoin',
- 'polymorphic_union',
- 'reconstructor',
- 'relationship',
- 'relation',
- 'remote',
- 'scoped_session',
- 'sessionmaker',
- 'subqueryload',
- 'subqueryload_all',
- 'synonym',
- 'undefer',
- 'undefer_group',
- 'validates',
- 'was_deleted',
- 'with_polymorphic'
- )
-
+from ..util.langhelpers import public_factory
+from .. import util as _sa_util
+from . import strategies as _strategies
def create_session(bind=None, **kwargs):
"""Create a new :class:`.Session`
@@ -167,501 +103,7 @@ def create_session(bind=None, **kwargs):
kwargs.setdefault('expire_on_commit', False)
return Session(bind=bind, **kwargs)
-
-def relationship(argument, secondary=None, **kwargs):
- """Provide a relationship of a primary Mapper to a secondary Mapper.
-
- This corresponds to a parent-child or associative table relationship. The
- constructed class is an instance of :class:`.RelationshipProperty`.
-
- A typical :func:`.relationship`, used in a classical mapping::
-
- mapper(Parent, properties={
- 'children': relationship(Child)
- })
-
- Some arguments accepted by :func:`.relationship` optionally accept a
- callable function, which when called produces the desired value.
- The callable is invoked by the parent :class:`.Mapper` at "mapper
- initialization" time, which happens only when mappers are first used, and
- is assumed to be after all mappings have been constructed. This can be
- used to resolve order-of-declaration and other dependency issues, such as
- if ``Child`` is declared below ``Parent`` in the same file::
-
- mapper(Parent, properties={
- "children":relationship(lambda: Child,
- order_by=lambda: Child.id)
- })
-
- When using the :ref:`declarative_toplevel` extension, the Declarative
- initializer allows string arguments to be passed to :func:`.relationship`.
- These string arguments are converted into callables that evaluate
- the string as Python code, using the Declarative
- class-registry as a namespace. This allows the lookup of related
- classes to be automatic via their string name, and removes the need to
- import related classes at all into the local module space::
-
- from sqlalchemy.ext.declarative import declarative_base
-
- Base = declarative_base()
-
- class Parent(Base):
- __tablename__ = 'parent'
- id = Column(Integer, primary_key=True)
- children = relationship("Child", order_by="Child.id")
-
- A full array of examples and reference documentation regarding
- :func:`.relationship` is at :ref:`relationship_config_toplevel`.
-
- :param argument:
- a mapped class, or actual :class:`.Mapper` instance, representing the
- target of the relationship.
-
- ``argument`` may also be passed as a callable function
- which is evaluated at mapper initialization time, and may be passed as a
- Python-evaluable string when using Declarative.
-
- :param secondary:
- for a many-to-many relationship, specifies the intermediary
- table, and is an instance of :class:`.Table`. The ``secondary`` keyword
- argument should generally only
- be used for a table that is not otherwise expressed in any class
- mapping, unless this relationship is declared as view only, otherwise
- conflicting persistence operations can occur.
-
- ``secondary`` may
- also be passed as a callable function which is evaluated at
- mapper initialization time.
-
- :param active_history=False:
- When ``True``, indicates that the "previous" value for a
- many-to-one reference should be loaded when replaced, if
- not already loaded. Normally, history tracking logic for
- simple many-to-ones only needs to be aware of the "new"
- value in order to perform a flush. This flag is available
- for applications that make use of
- :func:`.attributes.get_history` which also need to know
- the "previous" value of the attribute.
-
- :param backref:
- indicates the string name of a property to be placed on the related
- mapper's class that will handle this relationship in the other
- direction. The other property will be created automatically
- when the mappers are configured. Can also be passed as a
- :func:`backref` object to control the configuration of the
- new relationship.
-
- :param back_populates:
- Takes a string name and has the same meaning as ``backref``,
- except the complementing property is **not** created automatically,
- and instead must be configured explicitly on the other mapper. The
- complementing property should also indicate ``back_populates``
- to this relationship to ensure proper functioning.
-
- :param cascade:
- a comma-separated list of cascade rules which determines how
- Session operations should be "cascaded" from parent to child.
- This defaults to ``False``, which means the default cascade
- should be used. The default value is ``"save-update, merge"``.
-
- Available cascades are:
-
- * ``save-update`` - cascade the :meth:`.Session.add`
- operation. This cascade applies both to future and
- past calls to :meth:`~sqlalchemy.orm.session.Session.add`,
- meaning new items added to a collection or scalar relationship
- get placed into the same session as that of the parent, and
- also applies to items which have been removed from this
- relationship but are still part of unflushed history.
-
- * ``merge`` - cascade the :meth:`~sqlalchemy.orm.session.Session.merge`
- operation
-
- * ``expunge`` - cascade the :meth:`.Session.expunge`
- operation
-
- * ``delete`` - cascade the :meth:`.Session.delete`
- operation
-
- * ``delete-orphan`` - if an item of the child's type is
- detached from its parent, mark it for deletion.
-
- .. versionchanged:: 0.7
- This option does not prevent
- a new instance of the child object from being persisted
- without a parent to start with; to constrain against
- that case, ensure the child's foreign key column(s)
- is configured as NOT NULL
-
- * ``refresh-expire`` - cascade the :meth:`.Session.expire`
- and :meth:`~sqlalchemy.orm.session.Session.refresh` operations
-
- * ``all`` - shorthand for "save-update,merge, refresh-expire,
- expunge, delete"
-
- See the section :ref:`unitofwork_cascades` for more background
- on configuring cascades.
-
- :param cascade_backrefs=True:
- a boolean value indicating if the ``save-update`` cascade should
- operate along an assignment event intercepted by a backref.
- When set to ``False``,
- the attribute managed by this relationship will not cascade
- an incoming transient object into the session of a
- persistent parent, if the event is received via backref.
-
- That is::
-
- mapper(A, a_table, properties={
- 'bs':relationship(B, backref="a", cascade_backrefs=False)
- })
-
- If an ``A()`` is present in the session, assigning it to
- the "a" attribute on a transient ``B()`` will not place
- the ``B()`` into the session. To set the flag in the other
- direction, i.e. so that ``A().bs.append(B())`` won't add
- a transient ``A()`` into the session for a persistent ``B()``::
-
- mapper(A, a_table, properties={
- 'bs':relationship(B,
- backref=backref("a", cascade_backrefs=False)
- )
- })
-
- See the section :ref:`unitofwork_cascades` for more background
- on configuring cascades.
-
- :param collection_class:
- a class or callable that returns a new list-holding object. will
- be used in place of a plain list for storing elements.
- Behavior of this attribute is described in detail at
- :ref:`custom_collections`.
-
- :param comparator_factory:
- a class which extends :class:`.RelationshipProperty.Comparator` which
- provides custom SQL clause generation for comparison operations.
-
- :param doc:
- docstring which will be applied to the resulting descriptor.
-
- :param extension:
- an :class:`.AttributeExtension` instance, or list of extensions,
- which will be prepended to the list of attribute listeners for
- the resulting descriptor placed on the class.
- **Deprecated.** Please see :class:`.AttributeEvents`.
-
- :param foreign_keys:
- a list of columns which are to be used as "foreign key" columns,
- or columns which refer to the value in a remote column, within the
- context of this :func:`.relationship` object's ``primaryjoin``
- condition. That is, if the ``primaryjoin`` condition of this
- :func:`.relationship` is ``a.id == b.a_id``, and the values in ``b.a_id``
- are required to be present in ``a.id``, then the "foreign key" column
- of this :func:`.relationship` is ``b.a_id``.
-
- In normal cases, the ``foreign_keys`` parameter is **not required.**
- :func:`.relationship` will **automatically** determine which columns
- in the ``primaryjoin`` conditition are to be considered "foreign key"
- columns based on those :class:`.Column` objects that specify
- :class:`.ForeignKey`, or are otherwise listed as referencing columns
- in a :class:`.ForeignKeyConstraint` construct. ``foreign_keys`` is only
- needed when:
-
- 1. There is more than one way to construct a join from the local
- table to the remote table, as there are multiple foreign key
- references present. Setting ``foreign_keys`` will limit the
- :func:`.relationship` to consider just those columns specified
- here as "foreign".
-
- .. versionchanged:: 0.8
- A multiple-foreign key join ambiguity can be resolved by
- setting the ``foreign_keys`` parameter alone, without the
- need to explicitly set ``primaryjoin`` as well.
-
- 2. The :class:`.Table` being mapped does not actually have
- :class:`.ForeignKey` or :class:`.ForeignKeyConstraint`
- constructs present, often because the table
- was reflected from a database that does not support foreign key
- reflection (MySQL MyISAM).
-
- 3. The ``primaryjoin`` argument is used to construct a non-standard
- join condition, which makes use of columns or expressions that do
- not normally refer to their "parent" column, such as a join condition
- expressed by a complex comparison using a SQL function.
-
- The :func:`.relationship` construct will raise informative error messages
- that suggest the use of the ``foreign_keys`` parameter when presented
- with an ambiguous condition. In typical cases, if :func:`.relationship`
- doesn't raise any exceptions, the ``foreign_keys`` parameter is usually
- not needed.
-
- ``foreign_keys`` may also be passed as a callable function
- which is evaluated at mapper initialization time, and may be passed as a
- Python-evaluable string when using Declarative.
-
- .. seealso::
-
- :ref:`relationship_foreign_keys`
-
- :ref:`relationship_custom_foreign`
-
- :func:`.foreign` - allows direct annotation of the "foreign" columns
- within a ``primaryjoin`` condition.
-
- .. versionadded:: 0.8
- The :func:`.foreign` annotation can also be applied
- directly to the ``primaryjoin`` expression, which is an alternate,
- more specific system of describing which columns in a particular
- ``primaryjoin`` should be considered "foreign".
-
- :param info: Optional data dictionary which will be populated into the
- :attr:`.MapperProperty.info` attribute of this object.
-
- .. versionadded:: 0.8
-
- :param innerjoin=False:
- when ``True``, joined eager loads will use an inner join to join
- against related tables instead of an outer join. The purpose
- of this option is generally one of performance, as inner joins
- generally perform better than outer joins. Another reason can be
- the use of ``with_lockmode``, which does not support outer joins.
-
- This flag can be set to ``True`` when the relationship references an
- object via many-to-one using local foreign keys that are not nullable,
- or when the reference is one-to-one or a collection that is guaranteed
- to have one or at least one entry.
-
- :param join_depth:
- when non-``None``, an integer value indicating how many levels
- deep "eager" loaders should join on a self-referring or cyclical
- relationship. The number counts how many times the same Mapper
- shall be present in the loading condition along a particular join
- branch. When left at its default of ``None``, eager loaders
- will stop chaining when they encounter a the same target mapper
- which is already higher up in the chain. This option applies
- both to joined- and subquery- eager loaders.
-
- :param lazy='select': specifies
- how the related items should be loaded. Default value is
- ``select``. Values include:
-
- * ``select`` - items should be loaded lazily when the property is first
- accessed, using a separate SELECT statement, or identity map
- fetch for simple many-to-one references.
-
- * ``immediate`` - items should be loaded as the parents are loaded,
- using a separate SELECT statement, or identity map fetch for
- simple many-to-one references.
-
- .. versionadded:: 0.6.5
-
- * ``joined`` - items should be loaded "eagerly" in the same query as
- that of the parent, using a JOIN or LEFT OUTER JOIN. Whether
- the join is "outer" or not is determined by the ``innerjoin``
- parameter.
-
- * ``subquery`` - items should be loaded "eagerly" as the parents are
- loaded, using one additional SQL statement, which issues a JOIN to a
- subquery of the original statement, for each collection requested.
-
- * ``noload`` - no loading should occur at any time. This is to
- support "write-only" attributes, or attributes which are
- populated in some manner specific to the application.
-
- * ``dynamic`` - the attribute will return a pre-configured
- :class:`~sqlalchemy.orm.query.Query` object for all read
- operations, onto which further filtering operations can be
- applied before iterating the results. See
- the section :ref:`dynamic_relationship` for more details.
-
- * True - a synonym for 'select'
-
- * False - a synonym for 'joined'
-
- * None - a synonym for 'noload'
-
- Detailed discussion of loader strategies is at :doc:`/orm/loading`.
-
- :param load_on_pending=False:
- Indicates loading behavior for transient or pending parent objects.
-
- .. versionchanged:: 0.8
- load_on_pending is superseded by
- :meth:`.Session.enable_relationship_loading`.
-
- When set to ``True``, causes the lazy-loader to
- issue a query for a parent object that is not persistent, meaning it has
- never been flushed. This may take effect for a pending object when
- autoflush is disabled, or for a transient object that has been
- "attached" to a :class:`.Session` but is not part of its pending
- collection.
-
- The load_on_pending flag does not improve behavior
- when the ORM is used normally - object references should be constructed
- at the object level, not at the foreign key level, so that they
- are present in an ordinary way before flush() proceeds. This flag
- is not not intended for general use.
-
- .. versionadded:: 0.6.5
-
- :param order_by:
- indicates the ordering that should be applied when loading these
- items. ``order_by`` is expected to refer to one of the :class:`.Column`
- objects to which the target class is mapped, or
- the attribute itself bound to the target class which refers
- to the column.
-
- ``order_by`` may also be passed as a callable function
- which is evaluated at mapper initialization time, and may be passed as a
- Python-evaluable string when using Declarative.
-
- :param passive_deletes=False:
- Indicates loading behavior during delete operations.
-
- A value of True indicates that unloaded child items should not
- be loaded during a delete operation on the parent. Normally,
- when a parent item is deleted, all child items are loaded so
- that they can either be marked as deleted, or have their
- foreign key to the parent set to NULL. Marking this flag as
- True usually implies an ON DELETE <CASCADE|SET NULL> rule is in
- place which will handle updating/deleting child rows on the
- database side.
-
- Additionally, setting the flag to the string value 'all' will
- disable the "nulling out" of the child foreign keys, when there
- is no delete or delete-orphan cascade enabled. This is
- typically used when a triggering or error raise scenario is in
- place on the database side. Note that the foreign key
- attributes on in-session child objects will not be changed
- after a flush occurs so this is a very special use-case
- setting.
-
- :param passive_updates=True:
- Indicates loading and INSERT/UPDATE/DELETE behavior when the
- source of a foreign key value changes (i.e. an "on update"
- cascade), which are typically the primary key columns of the
- source row.
-
- 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 rows. Note that with databases which enforce
- referential integrity (i.e. PostgreSQL, MySQL with InnoDB tables),
- ON UPDATE CASCADE is required for this operation. The
- relationship() will update the value of the attribute on related
- items which are locally present in the session during a flush.
-
- 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 relationship() will issue the
- appropriate UPDATE statements to the database in response to the
- change of a referenced key, and items locally present in the
- session during a flush will also be refreshed.
-
- This flag should probably be set to False if primary key changes
- are expected and the database in use doesn't support CASCADE
- (i.e. SQLite, MySQL MyISAM tables).
-
- Also see the passive_updates flag on ``mapper()``.
-
- A future SQLAlchemy release will provide a "detect" feature for
- this flag.
-
- :param post_update:
- this indicates that the relationship should be handled by a
- second UPDATE statement after an INSERT or before a
- DELETE. Currently, it also will issue an UPDATE after the
- instance was UPDATEd as well, although this technically should
- be improved. This flag is used to handle saving bi-directional
- dependencies between two individual rows (i.e. each row
- references the other), where it would otherwise be impossible to
- INSERT or DELETE both rows fully since one row exists before the
- other. Use this flag when a particular mapping arrangement will
- incur two rows that are dependent on each other, such as a table
- that has a one-to-many relationship to a set of child rows, and
- also has a column that references a single child row within that
- list (i.e. both tables contain a foreign key to each other). If
- a ``flush()`` operation returns an error that a "cyclical
- dependency" was detected, this is a cue that you might want to
- use ``post_update`` to "break" the cycle.
-
- :param primaryjoin:
- a SQL expression that will be used as the primary
- join of this child object against the parent object, or in a
- many-to-many relationship the join of the primary object to the
- association table. By default, this value is computed based on the
- foreign key relationships of the parent and child tables (or association
- table).
-
- ``primaryjoin`` may also be passed as a callable function
- which is evaluated at mapper initialization time, and may be passed as a
- Python-evaluable string when using Declarative.
-
- :param remote_side:
- used for self-referential relationships, indicates the column or
- list of columns that form the "remote side" of the relationship.
-
- ``remote_side`` may also be passed as a callable function
- which is evaluated at mapper initialization time, and may be passed as a
- Python-evaluable string when using Declarative.
-
- .. versionchanged:: 0.8
- The :func:`.remote` annotation can also be applied
- directly to the ``primaryjoin`` expression, which is an alternate,
- more specific system of describing which columns in a particular
- ``primaryjoin`` should be considered "remote".
-
- :param query_class:
- a :class:`.Query` subclass that will be used as the base of the
- "appender query" returned by a "dynamic" relationship, that
- is, a relationship that specifies ``lazy="dynamic"`` or was
- otherwise constructed using the :func:`.orm.dynamic_loader`
- function.
-
- :param secondaryjoin:
- a SQL expression that will be used as the join of
- an association table to the child object. By default, this value is
- computed based on the foreign key relationships of the association and
- child tables.
-
- ``secondaryjoin`` may also be passed as a callable function
- which is evaluated at mapper initialization time, and may be passed as a
- Python-evaluable string when using Declarative.
-
- :param single_parent=(True|False):
- when True, installs a validator which will prevent objects
- from being associated with more than one parent at a time.
- This is used for many-to-one or many-to-many relationships that
- should be treated either as one-to-one or one-to-many. Its
- usage is optional unless delete-orphan cascade is also
- set on this relationship(), in which case its required.
-
- :param uselist=(True|False):
- a boolean that indicates if this property should be loaded as a
- list or a scalar. In most cases, this value is determined
- automatically by ``relationship()``, based on the type and direction
- of the relationship - one to many forms a list, many to one
- forms a scalar, many to many is a list. If a scalar is desired
- where normally a list would be present, such as a bi-directional
- one-to-one relationship, set uselist to False.
-
- :param viewonly=False:
- when set to True, the relationship is used only for loading objects
- within the relationship, and has no effect on the unit-of-work
- flush process. Relationships with viewonly can specify any kind of
- join conditions to provide additional views of related objects
- onto a parent object. Note that the functionality of a viewonly
- relationship has its limits - complicated join conditions may
- not compile into eager or lazy loaders properly. If this is the
- case, use an alternative method.
-
- .. versionchanged:: 0.6
- :func:`relationship` was renamed from its previous name
- :func:`relation`.
-
- """
- return RelationshipProperty(argument, secondary=secondary, **kwargs)
-
+relationship = public_factory(RelationshipProperty, ".orm.relationship")
def relation(*arg, **kw):
"""A synonym for :func:`relationship`."""
@@ -689,138 +131,8 @@ def dynamic_loader(argument, **kw):
return relationship(argument, **kw)
-def column_property(*cols, **kw):
- """Provide a column-level property for use with a Mapper.
-
- Column-based properties can normally be applied to the mapper's
- ``properties`` dictionary using the :class:`.Column` element directly.
- Use this function when the given column is not directly present within the
- mapper's selectable; examples include SQL expressions, functions, and
- scalar SELECT queries.
-
- Columns that aren't present in the mapper's selectable won't be persisted
- by the mapper and are effectively "read-only" attributes.
-
- :param \*cols:
- list of Column objects to be mapped.
-
- :param active_history=False:
- When ``True``, indicates that the "previous" value for a
- scalar attribute should be loaded when replaced, if not
- already loaded. Normally, history tracking logic for
- simple non-primary-key scalar values only needs to be
- aware of the "new" value in order to perform a flush. This
- flag is available for applications that make use of
- :func:`.attributes.get_history` or :meth:`.Session.is_modified`
- which also need to know
- the "previous" value of the attribute.
-
- .. versionadded:: 0.6.6
-
- :param comparator_factory: a class which extends
- :class:`.ColumnProperty.Comparator` which provides custom SQL clause
- generation for comparison operations.
-
- :param group:
- a group name for this property when marked as deferred.
-
- :param deferred:
- when True, the column property is "deferred", meaning that
- it does not load immediately, and is instead loaded when the
- attribute is first accessed on an instance. See also
- :func:`~sqlalchemy.orm.deferred`.
-
- :param doc:
- optional string that will be applied as the doc on the
- class-bound descriptor.
-
- :param expire_on_flush=True:
- Disable expiry on flush. A column_property() which refers
- to a SQL expression (and not a single table-bound column)
- is considered to be a "read only" property; populating it
- has no effect on the state of data, and it can only return
- database state. For this reason a column_property()'s value
- is expired whenever the parent object is involved in a
- flush, that is, has any kind of "dirty" state within a flush.
- Setting this parameter to ``False`` will have the effect of
- leaving any existing value present after the flush proceeds.
- Note however that the :class:`.Session` with default expiration
- settings still expires
- all attributes after a :meth:`.Session.commit` call, however.
-
- .. versionadded:: 0.7.3
-
- :param info: Optional data dictionary which will be populated into the
- :attr:`.MapperProperty.info` attribute of this object.
-
- .. versionadded:: 0.8
-
- :param extension:
- an
- :class:`.AttributeExtension`
- instance, or list of extensions, which will be prepended
- to the list of attribute listeners for the resulting
- descriptor placed on the class.
- **Deprecated.** Please see :class:`.AttributeEvents`.
-
- """
-
- return ColumnProperty(*cols, **kw)
-
-
-def composite(class_, *cols, **kwargs):
- """Return a composite column-based property for use with a Mapper.
-
- See the mapping documentation section :ref:`mapper_composite` for a full
- usage example.
-
- The :class:`.MapperProperty` returned by :func:`.composite`
- is the :class:`.CompositeProperty`.
-
- :param class\_:
- The "composite type" class.
-
- :param \*cols:
- List of Column objects to be mapped.
-
- :param active_history=False:
- When ``True``, indicates that the "previous" value for a
- scalar attribute should be loaded when replaced, if not
- already loaded. See the same flag on :func:`.column_property`.
-
- .. versionchanged:: 0.7
- This flag specifically becomes meaningful
- - previously it was a placeholder.
-
- :param group:
- A group name for this property when marked as deferred.
-
- :param deferred:
- When True, the column property is "deferred", meaning that it does not
- load immediately, and is instead loaded when the attribute is first
- accessed on an instance. See also :func:`~sqlalchemy.orm.deferred`.
-
- :param comparator_factory: a class which extends
- :class:`.CompositeProperty.Comparator` which provides custom SQL clause
- generation for comparison operations.
-
- :param doc:
- optional string that will be applied as the doc on the
- class-bound descriptor.
-
- :param info: Optional data dictionary which will be populated into the
- :attr:`.MapperProperty.info` attribute of this object.
-
- .. versionadded:: 0.8
-
- :param extension:
- an :class:`.AttributeExtension` instance,
- or list of extensions, which will be prepended to the list of
- attribute listeners for the resulting descriptor placed on the class.
- **Deprecated.** Please see :class:`.AttributeEvents`.
-
- """
- return CompositeProperty(class_, *cols, **kwargs)
+column_property = public_factory(ColumnProperty, ".orm.column_property")
+composite = public_factory(CompositeProperty, ".orm.composite")
def backref(name, **kwargs):
@@ -851,473 +163,15 @@ def deferred(*columns, **kwargs):
return ColumnProperty(deferred=True, *columns, **kwargs)
-def mapper(class_, local_table=None, *args, **params):
- """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")
- )
-
- __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
-
- """
- return Mapper(class_, local_table, *args, **params)
-
-
-def synonym(name, map_column=False, descriptor=None,
- comparator_factory=None, doc=None):
- """Denote an attribute name as a synonym to a mapped property.
-
- .. versionchanged:: 0.7
- :func:`.synonym` is superseded by the :mod:`~sqlalchemy.ext.hybrid`
- extension. See the documentation for hybrids
- at :ref:`hybrids_toplevel`.
-
- Used with the ``properties`` dictionary sent to
- :func:`~sqlalchemy.orm.mapper`::
-
- class MyClass(object):
- def _get_status(self):
- return self._status
- def _set_status(self, value):
- self._status = value
- status = property(_get_status, _set_status)
-
- mapper(MyClass, sometable, properties={
- "status":synonym("_status", map_column=True)
- })
-
- Above, the ``status`` attribute of MyClass will produce
- expression behavior against the table column named ``status``,
- using the Python attribute ``_status`` on the mapped class
- to represent the underlying value.
-
- :param name: the name of the existing mapped property, which can be
- any other ``MapperProperty`` including column-based properties and
- relationships.
-
- :param map_column: if ``True``, an additional ``ColumnProperty`` is created
- on the mapper automatically, using the synonym's name as the keyname of
- the property, and the keyname of this ``synonym()`` as the name of the
- column to map.
-
- """
- return SynonymProperty(name, map_column=map_column,
- descriptor=descriptor,
- comparator_factory=comparator_factory,
- doc=doc)
-
-
-def comparable_property(comparator_factory, descriptor=None):
- """Provides a method of applying a :class:`.PropComparator`
- to any Python descriptor attribute.
-
- .. versionchanged:: 0.7
- :func:`.comparable_property` is superseded by
- the :mod:`~sqlalchemy.ext.hybrid` extension. See the example
- at :ref:`hybrid_custom_comparators`.
-
- Allows any Python descriptor to behave like a SQL-enabled
- attribute when used at the class level in queries, allowing
- redefinition of expression operator behavior.
-
- In the example below we redefine :meth:`.PropComparator.operate`
- to wrap both sides of an expression in ``func.lower()`` to produce
- case-insensitive comparison::
-
- from sqlalchemy.orm import comparable_property
- from sqlalchemy.orm.interfaces import PropComparator
- from sqlalchemy.sql import func
- from sqlalchemy import Integer, String, Column
- from sqlalchemy.ext.declarative import declarative_base
-
- class CaseInsensitiveComparator(PropComparator):
- def __clause_element__(self):
- return self.prop
-
- def operate(self, op, other):
- return op(
- func.lower(self.__clause_element__()),
- func.lower(other)
- )
-
- Base = declarative_base()
-
- class SearchWord(Base):
- __tablename__ = 'search_word'
- id = Column(Integer, primary_key=True)
- word = Column(String)
- word_insensitive = comparable_property(lambda prop, mapper:
- CaseInsensitiveComparator(mapper.c.word, mapper)
- )
+mapper = public_factory(Mapper, ".orm.mapper")
+synonym = public_factory(SynonymProperty, ".orm.synonym")
- A mapping like the above allows the ``word_insensitive`` attribute
- to render an expression like::
+comparable_property = public_factory(ComparableProperty,
+ ".orm.comparable_property")
- >>> print SearchWord.word_insensitive == "Trucks"
- lower(search_word.word) = lower(:lower_1)
- :param comparator_factory:
- A PropComparator subclass or factory that defines operator behavior
- for this property.
-
- :param descriptor:
- Optional when used in a ``properties={}`` declaration. The Python
- descriptor or property to layer comparison behavior on top of.
-
- The like-named descriptor will be automatically retrieved from the
- mapped class if left blank in a ``properties`` declaration.
-
- """
- return ComparableProperty(comparator_factory, descriptor)
-
-
-@sa_util.deprecated("0.7", message=":func:`.compile_mappers` "
+@_sa_util.deprecated("0.7", message=":func:`.compile_mappers` "
"is renamed to :func:`.configure_mappers`")
def compile_mappers():
"""Initialize the inter-mapper relationships of all mappers that have
@@ -1413,11 +267,11 @@ def joinedload(*keys, **kw):
innerjoin = kw.pop('innerjoin', None)
if innerjoin is not None:
return (
- strategies.EagerLazyOption(keys, lazy='joined'),
- strategies.EagerJoinOption(keys, innerjoin)
+ _strategies.EagerLazyOption(keys, lazy='joined'),
+ _strategies.EagerJoinOption(keys, innerjoin)
)
else:
- return strategies.EagerLazyOption(keys, lazy='joined')
+ return _strategies.EagerLazyOption(keys, lazy='joined')
def joinedload_all(*keys, **kw):
@@ -1454,11 +308,11 @@ def joinedload_all(*keys, **kw):
innerjoin = kw.pop('innerjoin', None)
if innerjoin is not None:
return (
- strategies.EagerLazyOption(keys, lazy='joined', chained=True),
- strategies.EagerJoinOption(keys, innerjoin, chained=True)
+ _strategies.EagerLazyOption(keys, lazy='joined', chained=True),
+ _strategies.EagerJoinOption(keys, innerjoin, chained=True)
)
else:
- return strategies.EagerLazyOption(keys, lazy='joined', chained=True)
+ return _strategies.EagerLazyOption(keys, lazy='joined', chained=True)
def eagerload(*args, **kwargs):
@@ -1497,7 +351,7 @@ def subqueryload(*keys):
See also: :func:`joinedload`, :func:`lazyload`
"""
- return strategies.EagerLazyOption(keys, lazy="subquery")
+ return _strategies.EagerLazyOption(keys, lazy="subquery")
def subqueryload_all(*keys):
@@ -1522,7 +376,7 @@ def subqueryload_all(*keys):
See also: :func:`joinedload_all`, :func:`lazyload`, :func:`immediateload`
"""
- return strategies.EagerLazyOption(keys, lazy="subquery", chained=True)
+ return _strategies.EagerLazyOption(keys, lazy="subquery", chained=True)
def lazyload(*keys):
@@ -1534,7 +388,7 @@ def lazyload(*keys):
See also: :func:`eagerload`, :func:`subqueryload`, :func:`immediateload`
"""
- return strategies.EagerLazyOption(keys, lazy=True)
+ return _strategies.EagerLazyOption(keys, lazy=True)
def lazyload_all(*keys):
@@ -1547,7 +401,7 @@ def lazyload_all(*keys):
See also: :func:`eagerload`, :func:`subqueryload`, :func:`immediateload`
"""
- return strategies.EagerLazyOption(keys, lazy=True, chained=True)
+ return _strategies.EagerLazyOption(keys, lazy=True, chained=True)
def noload(*keys):
@@ -1560,7 +414,7 @@ def noload(*keys):
:func:`subqueryload`, :func:`immediateload`
"""
- return strategies.EagerLazyOption(keys, lazy=None)
+ return _strategies.EagerLazyOption(keys, lazy=None)
def immediateload(*keys):
@@ -1585,42 +439,9 @@ def immediateload(*keys):
.. versionadded:: 0.6.5
"""
- return strategies.EagerLazyOption(keys, lazy='immediate')
-
-
-def contains_alias(alias):
- """Return a :class:`.MapperOption` that will indicate to the query that
- the main table has been aliased.
-
- This is used in the very rare case that :func:`.contains_eager`
- is being used in conjunction with a user-defined SELECT
- statement that aliases the parent table. E.g.::
-
- # define an aliased UNION called 'ulist'
- statement = users.select(users.c.user_id==7).\\
- union(users.select(users.c.user_id>7)).\\
- alias('ulist')
-
- # add on an eager load of "addresses"
- statement = statement.outerjoin(addresses).\\
- select().apply_labels()
+ return _strategies.EagerLazyOption(keys, lazy='immediate')
- # create query, indicating "ulist" will be an
- # alias for the main table, "addresses"
- # property should be eager loaded
- query = session.query(User).options(
- contains_alias('ulist'),
- contains_eager('addresses'))
-
- # then get results via the statement
- results = query.from_statement(statement).all()
-
- :param alias: is the string name of an alias, or a
- :class:`~.sql.expression.Alias` object representing
- the alias.
-
- """
- return AliasOption(alias)
+contains_alias = public_factory(AliasOption, ".orm.contains_alias")
def contains_eager(*keys, **kwargs):
@@ -1662,9 +483,9 @@ def contains_eager(*keys, **kwargs):
if kwargs:
raise exc.ArgumentError(
'Invalid kwargs for contains_eager: %r' % list(kwargs.keys()))
- return strategies.EagerLazyOption(keys, lazy='joined',
+ return _strategies.EagerLazyOption(keys, lazy='joined',
propagate_to_loaders=False, chained=True), \
- strategies.LoadEagerFromAliasOption(keys, alias=alias, chained=True)
+ _strategies.LoadEagerFromAliasOption(keys, alias=alias, chained=True)
def defer(*key):
@@ -1709,7 +530,7 @@ def defer(*key):
multiple targets.
"""
- return strategies.DeferredOption(key, defer=True)
+ return _strategies.DeferredOption(key, defer=True)
def undefer(*key):
@@ -1758,7 +579,7 @@ def undefer(*key):
multiple targets.
"""
- return strategies.DeferredOption(key, defer=False)
+ return _strategies.DeferredOption(key, defer=False)
def undefer_group(name):
@@ -1780,7 +601,21 @@ def undefer_group(name):
configurational function.
"""
- return strategies.UndeferGroupOption(name)
+ return _strategies.UndeferGroupOption(name)
+
+
+
+def __go(lcls):
+ global __all__
+ from .. import util as sa_util
+ from . import dynamic
+ from . import events
+ import inspect as _inspect
+
+ __all__ = sorted(name for name, obj in lcls.items()
+ if not (name.startswith('_') or _inspect.ismodule(obj)))
+
+ _sa_util.dependencies.resolve_all("sqlalchemy.orm")
+
+__go(locals())
-from .. import util as _sa_util
-_sa_util.importlater.resolve_all("sqlalchemy.orm") \ No newline at end of file
diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py
index c25792354..949eafca4 100644
--- a/lib/sqlalchemy/orm/attributes.py
+++ b/lib/sqlalchemy/orm/attributes.py
@@ -14,108 +14,17 @@ defines a large part of the ORM's interactivity.
"""
import operator
-from operator import itemgetter
-
from .. import util, event, inspection
-from . import interfaces, collections, events, exc as orm_exc
-from .instrumentation import instance_state, instance_dict, manager_of_class
+from . import interfaces, collections, exc as orm_exc
-orm_util = util.importlater("sqlalchemy.orm", "util")
-
-PASSIVE_NO_RESULT = util.symbol('PASSIVE_NO_RESULT',
-"""Symbol returned by a loader callable or other attribute/history
-retrieval operation when a value could not be determined, based
-on loader callable flags.
-"""
-)
-
-ATTR_WAS_SET = util.symbol('ATTR_WAS_SET',
-"""Symbol returned by a loader callable to indicate the
-retrieved value, or values, were assigned to their attributes
-on the target object.
-""")
-
-ATTR_EMPTY = util.symbol('ATTR_EMPTY',
-"""Symbol used internally to indicate an attribute had no callable.
-""")
-
-NO_VALUE = util.symbol('NO_VALUE',
-"""Symbol which may be placed as the 'previous' value of an attribute,
-indicating no value was loaded for an attribute when it was modified,
-and flags indicated we were not to load it.
-"""
-)
-
-NEVER_SET = util.symbol('NEVER_SET',
-"""Symbol which may be placed as the 'previous' value of an attribute
-indicating that the attribute had not been assigned to previously.
-"""
-)
-
-NO_CHANGE = util.symbol("NO_CHANGE",
-"""No callables or SQL should be emitted on attribute access
-and no state should change""", canonical=0
-)
-
-CALLABLES_OK = util.symbol("CALLABLES_OK",
-"""Loader callables can be fired off if a value
-is not present.""", canonical=1
-)
-
-SQL_OK = util.symbol("SQL_OK",
-"""Loader callables can emit SQL at least on scalar value
-attributes.""", canonical=2)
-
-RELATED_OBJECT_OK = util.symbol("RELATED_OBJECT_OK",
-"""callables can use SQL to load related objects as well
-as scalar value attributes.
-""", canonical=4
-)
-
-INIT_OK = util.symbol("INIT_OK",
-"""Attributes should be initialized with a blank
-value (None or an empty collection) upon get, if no other
-value can be obtained.
-""", canonical=8
-)
-
-NON_PERSISTENT_OK = util.symbol("NON_PERSISTENT_OK",
-"""callables can be emitted if the parent is not persistent.""",
-canonical=16
-)
-
-LOAD_AGAINST_COMMITTED = util.symbol("LOAD_AGAINST_COMMITTED",
-"""callables should use committed values as primary/foreign keys during a load
-""", canonical=32
-)
-
-# pre-packaged sets of flags used as inputs
-PASSIVE_OFF = util.symbol("PASSIVE_OFF",
- "Callables can be emitted in all cases.",
- canonical=(RELATED_OBJECT_OK | NON_PERSISTENT_OK |
- INIT_OK | CALLABLES_OK | SQL_OK)
-)
-PASSIVE_RETURN_NEVER_SET = util.symbol("PASSIVE_RETURN_NEVER_SET",
- """PASSIVE_OFF ^ INIT_OK""",
- canonical=PASSIVE_OFF ^ INIT_OK
-)
-PASSIVE_NO_INITIALIZE = util.symbol("PASSIVE_NO_INITIALIZE",
- "PASSIVE_RETURN_NEVER_SET ^ CALLABLES_OK",
- canonical=PASSIVE_RETURN_NEVER_SET ^ CALLABLES_OK
-)
-PASSIVE_NO_FETCH = util.symbol("PASSIVE_NO_FETCH",
- "PASSIVE_OFF ^ SQL_OK",
- canonical=PASSIVE_OFF ^ SQL_OK
-)
-PASSIVE_NO_FETCH_RELATED = util.symbol("PASSIVE_NO_FETCH_RELATED",
- "PASSIVE_OFF ^ RELATED_OBJECT_OK",
- canonical=PASSIVE_OFF ^ RELATED_OBJECT_OK
-)
-PASSIVE_ONLY_PERSISTENT = util.symbol("PASSIVE_ONLY_PERSISTENT",
- "PASSIVE_OFF ^ NON_PERSISTENT_OK",
- canonical=PASSIVE_OFF ^ NON_PERSISTENT_OK
-)
+from .base import instance_state, instance_dict, manager_of_class
+from .base import PASSIVE_NO_RESULT, ATTR_WAS_SET, ATTR_EMPTY, NO_VALUE,\
+ NEVER_SET, NO_CHANGE, CALLABLES_OK, SQL_OK, RELATED_OBJECT_OK,\
+ INIT_OK, NON_PERSISTENT_OK, LOAD_AGAINST_COMMITTED, PASSIVE_OFF,\
+ PASSIVE_RETURN_NEVER_SET, PASSIVE_NO_INITIALIZE, PASSIVE_NO_FETCH,\
+ PASSIVE_NO_FETCH_RELATED, PASSIVE_ONLY_PERSISTENT
+from .base import state_str, instance_str
@inspection._self_inspects
class QueryableAttribute(interfaces._MappedAttribute,
@@ -160,9 +69,6 @@ class QueryableAttribute(interfaces._MappedAttribute,
if key in base:
self.dispatch._update(base[key].dispatch)
- dispatch = event.dispatcher(events.AttributeEvents)
- dispatch.dispatch_cls._active_history = False
-
@util.memoized_property
def _supports_population(self):
return self.impl.supports_population
@@ -586,8 +492,8 @@ class AttributeImpl(object):
"but the parent record "
"has gone stale, can't be sure this "
"is the most recent parent." %
- (orm_util.state_str(state),
- orm_util.state_str(parent_state),
+ (state_str(state),
+ state_str(parent_state),
self.key))
return
@@ -839,8 +745,8 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl):
else:
raise ValueError(
"Object %s not associated with %s on attribute '%s'" % (
- orm_util.instance_str(check_old),
- orm_util.state_str(state),
+ instance_str(check_old),
+ state_str(state),
self.key
))
value = self.fire_replace_event(state, dict_, value, old, initiator)
@@ -1131,7 +1037,7 @@ def backref_listeners(attribute, key, uselist):
'Passing object %s to attribute "%s" '
'triggers a modify event on attribute "%s" '
'via the backref "%s".' % (
- orm_util.state_str(child_state),
+ state_str(child_state),
initiator.parent_token,
child_impl.parent_token,
attribute.impl.parent_token
diff --git a/lib/sqlalchemy/orm/base.py b/lib/sqlalchemy/orm/base.py
new file mode 100644
index 000000000..f7d9dd4fe
--- /dev/null
+++ b/lib/sqlalchemy/orm/base.py
@@ -0,0 +1,419 @@
+# orm/base.py
+# Copyright (C) 2005-2013 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
+"""Constants and rudimental functions used throughout the ORM.
+
+"""
+
+from .. import util, inspection, exc as sa_exc
+from ..sql import expression
+from . import exc
+import operator
+
+PASSIVE_NO_RESULT = util.symbol('PASSIVE_NO_RESULT',
+"""Symbol returned by a loader callable or other attribute/history
+retrieval operation when a value could not be determined, based
+on loader callable flags.
+"""
+)
+
+ATTR_WAS_SET = util.symbol('ATTR_WAS_SET',
+"""Symbol returned by a loader callable to indicate the
+retrieved value, or values, were assigned to their attributes
+on the target object.
+""")
+
+ATTR_EMPTY = util.symbol('ATTR_EMPTY',
+"""Symbol used internally to indicate an attribute had no callable.
+""")
+
+NO_VALUE = util.symbol('NO_VALUE',
+"""Symbol which may be placed as the 'previous' value of an attribute,
+indicating no value was loaded for an attribute when it was modified,
+and flags indicated we were not to load it.
+"""
+)
+
+NEVER_SET = util.symbol('NEVER_SET',
+"""Symbol which may be placed as the 'previous' value of an attribute
+indicating that the attribute had not been assigned to previously.
+"""
+)
+
+NO_CHANGE = util.symbol("NO_CHANGE",
+"""No callables or SQL should be emitted on attribute access
+and no state should change""", canonical=0
+)
+
+CALLABLES_OK = util.symbol("CALLABLES_OK",
+"""Loader callables can be fired off if a value
+is not present.""", canonical=1
+)
+
+SQL_OK = util.symbol("SQL_OK",
+"""Loader callables can emit SQL at least on scalar value
+attributes.""", canonical=2)
+
+RELATED_OBJECT_OK = util.symbol("RELATED_OBJECT_OK",
+"""callables can use SQL to load related objects as well
+as scalar value attributes.
+""", canonical=4
+)
+
+INIT_OK = util.symbol("INIT_OK",
+"""Attributes should be initialized with a blank
+value (None or an empty collection) upon get, if no other
+value can be obtained.
+""", canonical=8
+)
+
+NON_PERSISTENT_OK = util.symbol("NON_PERSISTENT_OK",
+"""callables can be emitted if the parent is not persistent.""",
+canonical=16
+)
+
+LOAD_AGAINST_COMMITTED = util.symbol("LOAD_AGAINST_COMMITTED",
+"""callables should use committed values as primary/foreign keys during a load
+""", canonical=32
+)
+
+# pre-packaged sets of flags used as inputs
+PASSIVE_OFF = util.symbol("PASSIVE_OFF",
+ "Callables can be emitted in all cases.",
+ canonical=(RELATED_OBJECT_OK | NON_PERSISTENT_OK |
+ INIT_OK | CALLABLES_OK | SQL_OK)
+)
+PASSIVE_RETURN_NEVER_SET = util.symbol("PASSIVE_RETURN_NEVER_SET",
+ """PASSIVE_OFF ^ INIT_OK""",
+ canonical=PASSIVE_OFF ^ INIT_OK
+)
+PASSIVE_NO_INITIALIZE = util.symbol("PASSIVE_NO_INITIALIZE",
+ "PASSIVE_RETURN_NEVER_SET ^ CALLABLES_OK",
+ canonical=PASSIVE_RETURN_NEVER_SET ^ CALLABLES_OK
+)
+PASSIVE_NO_FETCH = util.symbol("PASSIVE_NO_FETCH",
+ "PASSIVE_OFF ^ SQL_OK",
+ canonical=PASSIVE_OFF ^ SQL_OK
+)
+PASSIVE_NO_FETCH_RELATED = util.symbol("PASSIVE_NO_FETCH_RELATED",
+ "PASSIVE_OFF ^ RELATED_OBJECT_OK",
+ canonical=PASSIVE_OFF ^ RELATED_OBJECT_OK
+)
+PASSIVE_ONLY_PERSISTENT = util.symbol("PASSIVE_ONLY_PERSISTENT",
+ "PASSIVE_OFF ^ NON_PERSISTENT_OK",
+ canonical=PASSIVE_OFF ^ NON_PERSISTENT_OK
+)
+
+DEFAULT_MANAGER_ATTR = '_sa_class_manager'
+DEFAULT_STATE_ATTR = '_sa_instance_state'
+_INSTRUMENTOR = ('mapper', 'instrumentor')
+
+EXT_CONTINUE = util.symbol('EXT_CONTINUE')
+EXT_STOP = util.symbol('EXT_STOP')
+
+ONETOMANY = util.symbol('ONETOMANY')
+MANYTOONE = util.symbol('MANYTOONE')
+MANYTOMANY = util.symbol('MANYTOMANY')
+
+NOT_EXTENSION = util.symbol('NOT_EXTENSION')
+"""Symbol indicating an :class:`_InspectionAttr` that's
+ not part of sqlalchemy.ext.
+
+ Is assigned to the :attr:`._InspectionAttr.extension_type`
+ attibute.
+
+"""
+
+_none_set = frozenset([None])
+
+
+# these can be replaced by sqlalchemy.ext.instrumentation
+# if augmented class instrumentation is enabled.
+def manager_of_class(cls):
+ return cls.__dict__.get(DEFAULT_MANAGER_ATTR, None)
+
+instance_state = operator.attrgetter(DEFAULT_STATE_ATTR)
+
+instance_dict = operator.attrgetter('__dict__')
+
+def instance_str(instance):
+ """Return a string describing an instance."""
+
+ return state_str(instance_state(instance))
+
+def state_str(state):
+ """Return a string describing an instance via its InstanceState."""
+
+ if state is None:
+ return "None"
+ else:
+ return '<%s at 0x%x>' % (state.class_.__name__, id(state.obj()))
+
+def state_class_str(state):
+ """Return a string describing an instance's class via its InstanceState."""
+
+ if state is None:
+ return "None"
+ else:
+ return '<%s>' % (state.class_.__name__, )
+
+
+def attribute_str(instance, attribute):
+ return instance_str(instance) + "." + attribute
+
+
+def state_attribute_str(state, attribute):
+ return state_str(state) + "." + attribute
+
+def object_mapper(instance):
+ """Given an object, return the primary Mapper associated with the object
+ instance.
+
+ Raises :class:`sqlalchemy.orm.exc.UnmappedInstanceError`
+ if no mapping is configured.
+
+ This function is available via the inspection system as::
+
+ inspect(instance).mapper
+
+ Using the inspection system will raise
+ :class:`sqlalchemy.exc.NoInspectionAvailable` if the instance is
+ not part of a mapping.
+
+ """
+ return object_state(instance).mapper
+
+
+def object_state(instance):
+ """Given an object, return the :class:`.InstanceState`
+ associated with the object.
+
+ Raises :class:`sqlalchemy.orm.exc.UnmappedInstanceError`
+ if no mapping is configured.
+
+ Equivalent functionality is available via the :func:`.inspect`
+ function as::
+
+ inspect(instance)
+
+ Using the inspection system will raise
+ :class:`sqlalchemy.exc.NoInspectionAvailable` if the instance is
+ not part of a mapping.
+
+ """
+ state = _inspect_mapped_object(instance)
+ if state is None:
+ raise exc.UnmappedInstanceError(instance)
+ else:
+ return state
+
+
+@inspection._inspects(object)
+def _inspect_mapped_object(instance):
+ try:
+ return instance_state(instance)
+ # TODO: whats the py-2/3 syntax to catch two
+ # different kinds of exceptions at once ?
+ except exc.UnmappedClassError:
+ return None
+ except exc.NO_STATE:
+ return None
+
+
+
+def _class_to_mapper(class_or_mapper):
+ insp = inspection.inspect(class_or_mapper, False)
+ if insp is not None:
+ return insp.mapper
+ else:
+ raise exc.UnmappedClassError(class_or_mapper)
+
+
+def _mapper_or_none(entity):
+ """Return the :class:`.Mapper` for the given class or None if the
+ class is not mapped."""
+
+ insp = inspection.inspect(entity, False)
+ if insp is not None:
+ return insp.mapper
+ else:
+ return None
+
+
+def _is_mapped_class(entity):
+ """Return True if the given object is a mapped class,
+ :class:`.Mapper`, or :class:`.AliasedClass`."""
+
+ insp = inspection.inspect(entity, False)
+ return insp is not None and \
+ hasattr(insp, "mapper") and \
+ (
+ insp.is_mapper
+ or insp.is_aliased_class
+ )
+
+def _attr_as_key(attr):
+ if hasattr(attr, 'key'):
+ return attr.key
+ else:
+ return expression._column_as_key(attr)
+
+
+
+def _orm_columns(entity):
+ insp = inspection.inspect(entity, False)
+ if hasattr(insp, 'selectable'):
+ return [c for c in insp.selectable.c]
+ else:
+ return [entity]
+
+
+
+def _is_aliased_class(entity):
+ insp = inspection.inspect(entity, False)
+ return insp is not None and \
+ getattr(insp, "is_aliased_class", False)
+
+
+def _entity_descriptor(entity, key):
+ """Return a class attribute given an entity and string name.
+
+ May return :class:`.InstrumentedAttribute` or user-defined
+ attribute.
+
+ """
+ insp = inspection.inspect(entity)
+ if insp.is_selectable:
+ description = entity
+ entity = insp.c
+ elif insp.is_aliased_class:
+ entity = insp.entity
+ description = entity
+ elif hasattr(insp, "mapper"):
+ description = entity = insp.mapper.class_
+ else:
+ description = entity
+
+ try:
+ return getattr(entity, key)
+ except AttributeError:
+ raise sa_exc.InvalidRequestError(
+ "Entity '%s' has no property '%s'" %
+ (description, key)
+ )
+
+_state_mapper = util.dottedgetter('manager.mapper')
+
+@inspection._inspects(type)
+def _inspect_mapped_class(class_, configure=False):
+ try:
+ class_manager = manager_of_class(class_)
+ if not class_manager.is_mapped:
+ return None
+ mapper = class_manager.mapper
+ if configure and mapper._new_mappers:
+ mapper._configure_all()
+ return mapper
+
+ except exc.NO_STATE:
+ return None
+
+def class_mapper(class_, configure=True):
+ """Given a class, return the primary :class:`.Mapper` associated
+ with the key.
+
+ Raises :class:`.UnmappedClassError` if no mapping is configured
+ on the given class, or :class:`.ArgumentError` if a non-class
+ object is passed.
+
+ Equivalent functionality is available via the :func:`.inspect`
+ function as::
+
+ inspect(some_mapped_class)
+
+ Using the inspection system will raise
+ :class:`sqlalchemy.exc.NoInspectionAvailable` if the class is not mapped.
+
+ """
+ mapper = _inspect_mapped_class(class_, configure=configure)
+ if mapper is None:
+ if not isinstance(class_, type):
+ raise sa_exc.ArgumentError(
+ "Class object expected, got '%r'." % class_)
+ raise exc.UnmappedClassError(class_)
+ else:
+ return mapper
+
+
+class _InspectionAttr(object):
+ """A base class applied to all ORM objects that can be returned
+ by the :func:`.inspect` function.
+
+ The attributes defined here allow the usage of simple boolean
+ checks to test basic facts about the object returned.
+
+ While the boolean checks here are basically the same as using
+ the Python isinstance() function, the flags here can be used without
+ the need to import all of these classes, and also such that
+ the SQLAlchemy class system can change while leaving the flags
+ here intact for forwards-compatibility.
+
+ """
+
+ is_selectable = False
+ """Return True if this object is an instance of :class:`.Selectable`."""
+
+ is_aliased_class = False
+ """True if this object is an instance of :class:`.AliasedClass`."""
+
+ is_instance = False
+ """True if this object is an instance of :class:`.InstanceState`."""
+
+ is_mapper = False
+ """True if this object is an instance of :class:`.Mapper`."""
+
+ is_property = False
+ """True if this object is an instance of :class:`.MapperProperty`."""
+
+ is_attribute = False
+ """True if this object is a Python :term:`descriptor`.
+
+ This can refer to one of many types. Usually a
+ :class:`.QueryableAttribute` which handles attributes events on behalf
+ of a :class:`.MapperProperty`. But can also be an extension type
+ such as :class:`.AssociationProxy` or :class:`.hybrid_property`.
+ The :attr:`._InspectionAttr.extension_type` will refer to a constant
+ identifying the specific subtype.
+
+ .. seealso::
+
+ :attr:`.Mapper.all_orm_descriptors`
+
+ """
+
+ is_clause_element = False
+ """True if this object is an instance of :class:`.ClauseElement`."""
+
+ extension_type = NOT_EXTENSION
+ """The extension type, if any.
+ Defaults to :data:`.interfaces.NOT_EXTENSION`
+
+ .. versionadded:: 0.8.0
+
+ .. seealso::
+
+ :data:`.HYBRID_METHOD`
+
+ :data:`.HYBRID_PROPERTY`
+
+ :data:`.ASSOCIATION_PROXY`
+
+ """
+
+class _MappedAttribute(object):
+ """Mixin for attributes which should be replaced by mapper-assigned
+ attributes.
+
+ """
diff --git a/lib/sqlalchemy/orm/collections.py b/lib/sqlalchemy/orm/collections.py
index 2ac94f826..f8f4b9583 100644
--- a/lib/sqlalchemy/orm/collections.py
+++ b/lib/sqlalchemy/orm/collections.py
@@ -108,8 +108,7 @@ import weakref
from ..sql import expression
from .. import util, exc as sa_exc
-orm_util = util.importlater("sqlalchemy.orm", "util")
-attributes = util.importlater("sqlalchemy.orm", "attributes")
+from . import base
__all__ = ['collection', 'collection_adapter',
@@ -139,8 +138,8 @@ class _PlainColumnGetter(object):
return self.cols
def __call__(self, value):
- state = attributes.instance_state(value)
- m = orm_util._state_mapper(state)
+ state = base.instance_state(value)
+ m = base._state_mapper(state)
key = [
m._get_state_attr_by_column(state, state.dict, col)
@@ -167,8 +166,8 @@ class _SerializableColumnGetter(object):
return _SerializableColumnGetter, (self.colkeys,)
def __call__(self, value):
- state = attributes.instance_state(value)
- m = orm_util._state_mapper(state)
+ state = base.instance_state(value)
+ m = base._state_mapper(state)
key = [m._get_state_attr_by_column(
state, state.dict,
m.mapped_table.columns[k])
diff --git a/lib/sqlalchemy/orm/deprecated_interfaces.py b/lib/sqlalchemy/orm/deprecated_interfaces.py
index e50967253..34a976c91 100644
--- a/lib/sqlalchemy/orm/deprecated_interfaces.py
+++ b/lib/sqlalchemy/orm/deprecated_interfaces.py
@@ -7,7 +7,7 @@
from .. import event, util
from .interfaces import EXT_CONTINUE
-
+@util.langhelpers.dependency_for("sqlalchemy.orm.interfaces")
class MapperExtension(object):
"""Base implementation for :class:`.Mapper` event hooks.
@@ -374,6 +374,7 @@ class MapperExtension(object):
return EXT_CONTINUE
+@util.langhelpers.dependency_for("sqlalchemy.orm.interfaces")
class SessionExtension(object):
"""Base implementation for :class:`.Session` event hooks.
@@ -494,6 +495,7 @@ class SessionExtension(object):
"""
+@util.langhelpers.dependency_for("sqlalchemy.orm.interfaces")
class AttributeExtension(object):
"""Base implementation for :class:`.AttributeImpl` event hooks, events
that fire upon attribute mutations in user code.
diff --git a/lib/sqlalchemy/orm/descriptor_props.py b/lib/sqlalchemy/orm/descriptor_props.py
index c58951339..457b26523 100644
--- a/lib/sqlalchemy/orm/descriptor_props.py
+++ b/lib/sqlalchemy/orm/descriptor_props.py
@@ -12,10 +12,10 @@ as actively in the load/persist ORM loop.
from .interfaces import MapperProperty, PropComparator
from .util import _none_set
-from . import attributes, strategies
+from . import attributes
from .. import util, sql, exc as sa_exc, event, schema
from ..sql import expression
-properties = util.importlater('sqlalchemy.orm', 'properties')
+from . import properties
class DescriptorProperty(MapperProperty):
@@ -75,6 +75,7 @@ class DescriptorProperty(MapperProperty):
mapper.class_manager.instrument_attribute(self.key, proxy_attr)
+@util.langhelpers.dependency_for("sqlalchemy.orm.properties")
class CompositeProperty(DescriptorProperty):
"""Defines a "composite" mapped attribute, representing a collection
of columns as one attribute.
@@ -88,6 +89,58 @@ class CompositeProperty(DescriptorProperty):
"""
def __init__(self, class_, *attrs, **kwargs):
+ """Return a composite column-based property for use with a Mapper.
+
+ See the mapping documentation section :ref:`mapper_composite` for a full
+ usage example.
+
+ The :class:`.MapperProperty` returned by :func:`.composite`
+ is the :class:`.CompositeProperty`.
+
+ :param class\_:
+ The "composite type" class.
+
+ :param \*cols:
+ List of Column objects to be mapped.
+
+ :param active_history=False:
+ When ``True``, indicates that the "previous" value for a
+ scalar attribute should be loaded when replaced, if not
+ already loaded. See the same flag on :func:`.column_property`.
+
+ .. versionchanged:: 0.7
+ This flag specifically becomes meaningful
+ - previously it was a placeholder.
+
+ :param group:
+ A group name for this property when marked as deferred.
+
+ :param deferred:
+ When True, the column property is "deferred", meaning that it does not
+ load immediately, and is instead loaded when the attribute is first
+ accessed on an instance. See also :func:`~sqlalchemy.orm.deferred`.
+
+ :param comparator_factory: a class which extends
+ :class:`.CompositeProperty.Comparator` which provides custom SQL clause
+ generation for comparison operations.
+
+ :param doc:
+ optional string that will be applied as the doc on the
+ class-bound descriptor.
+
+ :param info: Optional data dictionary which will be populated into the
+ :attr:`.MapperProperty.info` attribute of this object.
+
+ .. versionadded:: 0.8
+
+ :param extension:
+ an :class:`.AttributeExtension` instance,
+ or list of extensions, which will be prepended to the list of
+ attribute listeners for the resulting descriptor placed on the class.
+ **Deprecated.** Please see :class:`.AttributeEvents`.
+
+ """
+
self.attrs = attrs
self.composite_class = class_
self.active_history = kwargs.get('active_history', False)
@@ -205,7 +258,8 @@ class CompositeProperty(DescriptorProperty):
prop.active_history = self.active_history
if self.deferred:
prop.deferred = self.deferred
- prop.strategy_class = strategies.DeferredColumnLoader
+ prop.strategy_class = prop._strategy_lookup(
+ deferred=True, instrument=True)
prop.group = self.group
def _setup_event_handlers(self):
@@ -356,6 +410,7 @@ class CompositeProperty(DescriptorProperty):
return str(self.parent.class_.__name__) + "." + self.key
+@util.langhelpers.dependency_for("sqlalchemy.orm.properties")
class ConcreteInheritedProperty(DescriptorProperty):
"""A 'do nothing' :class:`.MapperProperty` that disables
an attribute on a concrete subclass that is only present
@@ -404,11 +459,49 @@ class ConcreteInheritedProperty(DescriptorProperty):
self.descriptor = NoninheritedConcreteProp()
+@util.langhelpers.dependency_for("sqlalchemy.orm.properties")
class SynonymProperty(DescriptorProperty):
def __init__(self, name, map_column=None,
descriptor=None, comparator_factory=None,
doc=None):
+ """Denote an attribute name as a synonym to a mapped property.
+
+ .. versionchanged:: 0.7
+ :func:`.synonym` is superseded by the :mod:`~sqlalchemy.ext.hybrid`
+ extension. See the documentation for hybrids
+ at :ref:`hybrids_toplevel`.
+
+ Used with the ``properties`` dictionary sent to
+ :func:`~sqlalchemy.orm.mapper`::
+
+ class MyClass(object):
+ def _get_status(self):
+ return self._status
+ def _set_status(self, value):
+ self._status = value
+ status = property(_get_status, _set_status)
+
+ mapper(MyClass, sometable, properties={
+ "status":synonym("_status", map_column=True)
+ })
+
+ Above, the ``status`` attribute of MyClass will produce
+ expression behavior against the table column named ``status``,
+ using the Python attribute ``_status`` on the mapped class
+ to represent the underlying value.
+
+ :param name: the name of the existing mapped property, which can be
+ any other ``MapperProperty`` including column-based properties and
+ relationships.
+
+ :param map_column: if ``True``, an additional ``ColumnProperty`` is created
+ on the mapper automatically, using the synonym's name as the keyname of
+ the property, and the keyname of this ``synonym()`` as the name of the
+ column to map.
+
+ """
+
self.name = name
self.map_column = map_column
self.descriptor = descriptor
@@ -462,10 +555,72 @@ class SynonymProperty(DescriptorProperty):
self.parent = parent
+@util.langhelpers.dependency_for("sqlalchemy.orm.properties")
class ComparableProperty(DescriptorProperty):
"""Instruments a Python property for use in query expressions."""
def __init__(self, comparator_factory, descriptor=None, doc=None):
+ """Provides a method of applying a :class:`.PropComparator`
+ to any Python descriptor attribute.
+
+ .. versionchanged:: 0.7
+ :func:`.comparable_property` is superseded by
+ the :mod:`~sqlalchemy.ext.hybrid` extension. See the example
+ at :ref:`hybrid_custom_comparators`.
+
+ Allows any Python descriptor to behave like a SQL-enabled
+ attribute when used at the class level in queries, allowing
+ redefinition of expression operator behavior.
+
+ In the example below we redefine :meth:`.PropComparator.operate`
+ to wrap both sides of an expression in ``func.lower()`` to produce
+ case-insensitive comparison::
+
+ from sqlalchemy.orm import comparable_property
+ from sqlalchemy.orm.interfaces import PropComparator
+ from sqlalchemy.sql import func
+ from sqlalchemy import Integer, String, Column
+ from sqlalchemy.ext.declarative import declarative_base
+
+ class CaseInsensitiveComparator(PropComparator):
+ def __clause_element__(self):
+ return self.prop
+
+ def operate(self, op, other):
+ return op(
+ func.lower(self.__clause_element__()),
+ func.lower(other)
+ )
+
+ Base = declarative_base()
+
+ class SearchWord(Base):
+ __tablename__ = 'search_word'
+ id = Column(Integer, primary_key=True)
+ word = Column(String)
+ word_insensitive = comparable_property(lambda prop, mapper:
+ CaseInsensitiveComparator(mapper.c.word, mapper)
+ )
+
+
+ A mapping like the above allows the ``word_insensitive`` attribute
+ to render an expression like::
+
+ >>> print SearchWord.word_insensitive == "Trucks"
+ lower(search_word.word) = lower(:lower_1)
+
+ :param comparator_factory:
+ A PropComparator subclass or factory that defines operator behavior
+ for this property.
+
+ :param descriptor:
+ Optional when used in a ``properties={}`` declaration. The Python
+ descriptor or property to layer comparison behavior on top of.
+
+ The like-named descriptor will be automatically retrieved from the
+ mapped class if left blank in a ``properties`` declaration.
+
+ """
self.descriptor = descriptor
self.comparator_factory = comparator_factory
self.doc = doc or (descriptor and descriptor.__doc__) or None
@@ -473,3 +628,5 @@ class ComparableProperty(DescriptorProperty):
def _comparator_factory(self, mapper):
return self.comparator_factory(self, mapper)
+
+
diff --git a/lib/sqlalchemy/orm/dynamic.py b/lib/sqlalchemy/orm/dynamic.py
index 4ad204357..4631e806f 100644
--- a/lib/sqlalchemy/orm/dynamic.py
+++ b/lib/sqlalchemy/orm/dynamic.py
@@ -15,11 +15,12 @@ from .. import log, util, exc
from ..sql import operators
from . import (
attributes, object_session, util as orm_util, strategies,
- object_mapper, exc as orm_exc
+ object_mapper, exc as orm_exc, properties
)
from .query import Query
@log.class_logger
+@properties.RelationshipProperty._strategy_for(dict(lazy="dynamic"))
class DynaLoader(strategies.AbstractRelationshipLoader):
def init_class_attribute(self, mapper):
self.is_class_level = True
diff --git a/lib/sqlalchemy/orm/events.py b/lib/sqlalchemy/orm/events.py
index b38b64f24..e2ca39137 100644
--- a/lib/sqlalchemy/orm/events.py
+++ b/lib/sqlalchemy/orm/events.py
@@ -8,10 +8,15 @@
"""
from .. import event, exc, util
-orm = util.importlater("sqlalchemy", "orm")
+from .base import _mapper_or_none
import inspect
import weakref
-
+from . import interfaces
+from . import mapperlib, instrumentation
+from .session import Session, sessionmaker
+from .scoping import scoped_session
+from .attributes import QueryableAttribute
+from . import mapper as mapperfunc
class InstrumentationEvents(event.Events):
"""Events related to class instrumentation events.
@@ -43,6 +48,8 @@ class InstrumentationEvents(event.Events):
"""
_target_class_doc = "SomeBaseClass"
+ _dispatch_target = instrumentation.InstrumentationFactory
+
@classmethod
def _accept_with(cls, target):
@@ -65,20 +72,20 @@ class InstrumentationEvents(event.Events):
def remove(ref):
key = event.registry._EventKey(None, identifier, listen,
- orm.instrumentation._instrumentation_factory)
- getattr(orm.instrumentation._instrumentation_factory.dispatch,
+ instrumentation._instrumentation_factory)
+ getattr(instrumentation._instrumentation_factory.dispatch,
identifier).remove(key)
target = weakref.ref(target.class_, remove)
event_key.\
- with_dispatch_target(orm.instrumentation._instrumentation_factory).\
+ with_dispatch_target(instrumentation._instrumentation_factory).\
with_wrapper(listen).base_listen()
@classmethod
def _clear(cls):
super(InstrumentationEvents, cls)._clear()
- orm.instrumentation._instrumentation_factory.dispatch._clear()
+ instrumentation._instrumentation_factory.dispatch._clear()
def class_instrument(self, cls):
"""Called after the given class is instrumented.
@@ -100,6 +107,7 @@ class InstrumentationEvents(event.Events):
"""Called when an attribute is instrumented."""
+
class _InstrumentationEventsHold(object):
"""temporary marker object used to transfer from _accept_with() to
_listen() on the InstrumentationEvents class.
@@ -110,7 +118,6 @@ class _InstrumentationEventsHold(object):
dispatch = event.dispatcher(InstrumentationEvents)
-
class InstanceEvents(event.Events):
"""Define events specific to object lifecycle.
@@ -154,19 +161,26 @@ class InstanceEvents(event.Events):
_target_class_doc = "SomeClass"
+ _dispatch_target = instrumentation.ClassManager
+
@classmethod
- def _accept_with(cls, target):
- if isinstance(target, orm.instrumentation.ClassManager):
+ def _new_classmanager_instance(cls, class_, classmanager):
+ _InstanceEventsHold.populate(class_, classmanager)
+
+ @classmethod
+ @util.dependencies("sqlalchemy.orm")
+ def _accept_with(cls, orm, target):
+ if isinstance(target, instrumentation.ClassManager):
return target
- elif isinstance(target, orm.Mapper):
+ elif isinstance(target, mapperlib.Mapper):
return target.class_manager
elif target is orm.mapper:
- return orm.instrumentation.ClassManager
+ return instrumentation.ClassManager
elif isinstance(target, type):
- if issubclass(target, orm.Mapper):
- return orm.instrumentation.ClassManager
+ if issubclass(target, mapperlib.Mapper):
+ return instrumentation.ClassManager
else:
- manager = orm.instrumentation.manager_of_class(target)
+ manager = instrumentation.manager_of_class(target)
if manager:
return manager
else:
@@ -334,6 +348,8 @@ class _EventsHold(event.RefCollection):
cls.all_holds.clear()
class HoldEvents(object):
+ _dispatch_target = None
+
@classmethod
def _listen(cls, event_key, raw=False, propagate=False):
target, identifier, fn = \
@@ -384,7 +400,7 @@ class _InstanceEventsHold(_EventsHold):
all_holds = weakref.WeakKeyDictionary()
def resolve(self, class_):
- return orm.instrumentation.manager_of_class(class_)
+ return instrumentation.manager_of_class(class_)
class HoldInstanceEvents(_EventsHold.HoldEvents, InstanceEvents):
pass
@@ -464,16 +480,22 @@ class MapperEvents(event.Events):
"""
_target_class_doc = "SomeClass"
+ _dispatch_target = mapperlib.Mapper
@classmethod
- def _accept_with(cls, target):
+ def _new_mapper_instance(cls, class_, mapper):
+ _MapperEventsHold.populate(class_, mapper)
+
+ @classmethod
+ @util.dependencies("sqlalchemy.orm")
+ def _accept_with(cls, orm, target):
if target is orm.mapper:
- return orm.Mapper
+ return mapperlib.Mapper
elif isinstance(target, type):
- if issubclass(target, orm.Mapper):
+ if issubclass(target, mapperlib.Mapper):
return target
else:
- mapper = orm.util._mapper_or_none(target)
+ mapper = _mapper_or_none(target)
if mapper is not None:
return mapper
else:
@@ -504,7 +526,7 @@ class MapperEvents(event.Events):
arg[target_index] = arg[target_index].obj()
if not retval:
wrapped_fn(*arg, **kw)
- return orm.interfaces.EXT_CONTINUE
+ return interfaces.EXT_CONTINUE
else:
return wrapped_fn(*arg, **kw)
fn = wrap
@@ -1066,12 +1088,11 @@ class MapperEvents(event.Events):
"""
-
class _MapperEventsHold(_EventsHold):
all_holds = weakref.WeakKeyDictionary()
def resolve(self, class_):
- return orm.util._mapper_or_none(class_)
+ return _mapper_or_none(class_)
class HoldMapperEvents(_EventsHold.HoldEvents, MapperEvents):
pass
@@ -1106,29 +1127,31 @@ class SessionEvents(event.Events):
_target_class_doc = "SomeSessionOrFactory"
+ _dispatch_target = Session
+
@classmethod
def _accept_with(cls, target):
- if isinstance(target, orm.scoped_session):
+ if isinstance(target, scoped_session):
target = target.session_factory
- if not isinstance(target, orm.sessionmaker) and \
+ if not isinstance(target, sessionmaker) and \
(
not isinstance(target, type) or
- not issubclass(target, orm.Session)
+ not issubclass(target, Session)
):
raise exc.ArgumentError(
"Session event listen on a scoped_session "
"requires that its creation callable "
"is associated with the Session class.")
- if isinstance(target, orm.sessionmaker):
+ if isinstance(target, sessionmaker):
return target.class_
elif isinstance(target, type):
- if issubclass(target, orm.scoped_session):
- return orm.Session
- elif issubclass(target, orm.Session):
+ if issubclass(target, scoped_session):
+ return Session
+ elif issubclass(target, Session):
return target
- elif isinstance(target, orm.Session):
+ elif isinstance(target, Session):
return target
else:
return None
@@ -1511,11 +1534,17 @@ class AttributeEvents(event.Events):
"""
_target_class_doc = "SomeClass.some_attribute"
+ _dispatch_target = QueryableAttribute
+
+ @staticmethod
+ def _set_dispatch(cls, dispatch_cls):
+ event.Events._set_dispatch(cls, dispatch_cls)
+ dispatch_cls._active_history = False
@classmethod
def _accept_with(cls, target):
# TODO: coverage
- if isinstance(target, orm.interfaces.MapperProperty):
+ if isinstance(target, interfaces.MapperProperty):
return getattr(target.parent.class_, target.key)
else:
return target
@@ -1548,7 +1577,7 @@ class AttributeEvents(event.Events):
event_key.base_listen(propagate=propagate)
if propagate:
- manager = orm.instrumentation.manager_of_class(target.class_)
+ manager = instrumentation.manager_of_class(target.class_)
for mgr in manager.subclass_managers(True):
event_key.with_dispatch_target(mgr[target.key]).base_listen(propagate=True)
@@ -1625,3 +1654,4 @@ class AttributeEvents(event.Events):
the given value, or a new effective value, should be returned.
"""
+
diff --git a/lib/sqlalchemy/orm/exc.py b/lib/sqlalchemy/orm/exc.py
index 0faa7bd29..0d59da43f 100644
--- a/lib/sqlalchemy/orm/exc.py
+++ b/lib/sqlalchemy/orm/exc.py
@@ -6,8 +6,6 @@
"""SQLAlchemy ORM exceptions."""
from .. import exc as sa_exc, util
-orm_util = util.importlater('sqlalchemy.orm', 'util')
-attributes = util.importlater('sqlalchemy.orm', 'attributes')
NO_STATE = (AttributeError, KeyError)
"""Exception types that may be raised by instrumentation implementations."""
@@ -65,10 +63,11 @@ class DetachedInstanceError(sa_exc.SQLAlchemyError):
class UnmappedInstanceError(UnmappedError):
"""An mapping operation was requested for an unknown instance."""
- def __init__(self, obj, msg=None):
+ @util.dependencies("sqlalchemy.orm.base")
+ def __init__(self, base, obj, msg=None):
if not msg:
try:
- mapper = orm_util.class_mapper(type(obj))
+ base.class_mapper(type(obj))
name = _safe_cls_name(type(obj))
msg = ("Class %r is mapped, but this instance lacks "
"instrumentation. This occurs when the instance"
@@ -117,10 +116,11 @@ class ObjectDeletedError(sa_exc.InvalidRequestError):
object.
"""
- def __init__(self, state, msg=None):
+ @util.dependencies("sqlalchemy.orm.base")
+ def __init__(self, base, state, msg=None):
if not msg:
msg = "Instance '%s' has been deleted, or its "\
- "row is otherwise not present." % orm_util.state_str(state)
+ "row is otherwise not present." % base.state_str(state)
sa_exc.InvalidRequestError.__init__(self, msg)
@@ -149,10 +149,10 @@ def _safe_cls_name(cls):
cls_name = repr(cls)
return cls_name
-
-def _default_unmapped(cls):
+@util.dependencies("sqlalchemy.orm.base")
+def _default_unmapped(base, cls):
try:
- mappers = attributes.manager_of_class(cls).mappers
+ mappers = base.manager_of_class(cls).mappers
except NO_STATE:
mappers = {}
except TypeError:
diff --git a/lib/sqlalchemy/orm/instrumentation.py b/lib/sqlalchemy/orm/instrumentation.py
index 877a72193..a86a6086d 100644
--- a/lib/sqlalchemy/orm/instrumentation.py
+++ b/lib/sqlalchemy/orm/instrumentation.py
@@ -29,17 +29,15 @@ alternate instrumentation forms.
"""
-from . import exc, collections, events, interfaces
-from operator import attrgetter
+from . import exc, collections, interfaces, state
from .. import event, util
-state = util.importlater("sqlalchemy.orm", "state")
-
+from . import base
class ClassManager(dict):
"""tracks state information at the class level."""
- MANAGER_ATTR = '_sa_class_manager'
- STATE_ATTR = '_sa_instance_state'
+ MANAGER_ATTR = base.DEFAULT_MANAGER_ATTR
+ STATE_ATTR = base.DEFAULT_STATE_ATTR
deferred_scalar_loader = None
@@ -63,7 +61,8 @@ class ClassManager(dict):
for base in self._bases:
self.update(base)
- events._InstanceEventsHold.populate(class_, self)
+ self.dispatch._events._new_classmanager_instance(class_, self)
+ #events._InstanceEventsHold.populate(class_, self)
for basecls in class_.__mro__:
mgr = manager_of_class(basecls)
@@ -79,8 +78,6 @@ class ClassManager(dict):
"reference cycles. Please remove this method." %
class_)
- dispatch = event.dispatcher(events.InstanceEvents)
-
def __hash__(self):
return id(self)
@@ -170,9 +167,7 @@ class ClassManager(dict):
@util.hybridmethod
def manager_getter(self):
- def manager_of_class(cls):
- return cls.__dict__.get(ClassManager.MANAGER_ATTR, None)
- return manager_of_class
+ return _default_manager_getter
@util.hybridmethod
def state_getter(self):
@@ -183,11 +178,12 @@ class ClassManager(dict):
instance.
"""
- return attrgetter(self.STATE_ATTR)
+ return _default_state_getter
@util.hybridmethod
def dict_getter(self):
- return attrgetter('__dict__')
+ return _default_dict_getter
+
def instrument_attribute(self, key, inst, propagated=False):
if propagated:
@@ -302,6 +298,9 @@ class ClassManager(dict):
def teardown_instance(self, instance):
delattr(instance, self.STATE_ATTR)
+ def _serialize(self, state, state_dict):
+ return _SerializeManager(state, state_dict)
+
def _new_state_if_none(self, instance):
"""Install a default InstanceState if none is present.
@@ -341,12 +340,41 @@ class ClassManager(dict):
return '<%s of %r at %x>' % (
self.__class__.__name__, self.class_, id(self))
+class _SerializeManager(object):
+ """Provide serialization of a :class:`.ClassManager`.
+
+ The :class:`.InstanceState` uses ``__init__()`` on serialize
+ and ``__call__()`` on deserialize.
+
+ """
+ def __init__(self, state, d):
+ self.class_ = state.class_
+ manager = state.manager
+ manager.dispatch.pickle(state, d)
+
+ def __call__(self, state, inst, state_dict):
+ state.manager = manager = manager_of_class(self.class_)
+ if manager is None:
+ raise exc.UnmappedInstanceError(
+ inst,
+ "Cannot deserialize object of type %r - "
+ "no mapper() has "
+ "been configured for this class within the current "
+ "Python process!" %
+ self.class_)
+ elif manager.is_mapped and not manager.mapper.configured:
+ manager.mapper._configure_all()
+
+ # setup _sa_instance_state ahead of time so that
+ # unpickle events can access the object normally.
+ # see [ticket:2362]
+ if inst is not None:
+ manager.setup_instance(inst, state)
+ manager.dispatch.unpickle(state, state_dict)
class InstrumentationFactory(object):
"""Factory for new ClassManager instances."""
- dispatch = event.dispatcher(events.InstrumentationEvents)
-
def create_manager_for_cls(self, class_):
assert class_ is not None
assert manager_of_class(class_) is None
@@ -386,6 +414,14 @@ class InstrumentationFactory(object):
# when importred.
_instrumentation_factory = InstrumentationFactory()
+# these attributes are replaced by sqlalchemy.ext.instrumentation
+# when a non-standard InstrumentationManager class is first
+# used to instrument a class.
+instance_state = _default_state_getter = base.instance_state
+
+instance_dict = _default_dict_getter = base.instance_dict
+
+manager_of_class = _default_manager_getter = base.manager_of_class
def register_class(class_):
"""Register class instrumentation.
@@ -417,15 +453,6 @@ def is_instrumented(instance, key):
return manager_of_class(instance.__class__).\
is_instrumented(key, search=True)
-# these attributes are replaced by sqlalchemy.ext.instrumentation
-# when a non-standard InstrumentationManager class is first
-# used to instrument a class.
-instance_state = _default_state_getter = ClassManager.state_getter()
-
-instance_dict = _default_dict_getter = ClassManager.dict_getter()
-
-manager_of_class = _default_manager_getter = ClassManager.manager_getter()
-
def _generate_init(class_, class_manager):
"""Build an __init__ decorator that triggers ClassManager events."""
diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py
index 150277be2..00906a262 100644
--- a/lib/sqlalchemy/orm/interfaces.py
+++ b/lib/sqlalchemy/orm/interfaces.py
@@ -21,9 +21,12 @@ from __future__ import absolute_import
from .. import exc as sa_exc, util, inspect
from ..sql import operators
from collections import deque
+from .base import _is_aliased_class, _class_to_mapper
+from .base import ONETOMANY, MANYTOONE, MANYTOMANY, EXT_CONTINUE, EXT_STOP, NOT_EXTENSION
+from .base import _InspectionAttr, _MappedAttribute
+from .path_registry import PathRegistry
+import collections
-orm_util = util.importlater('sqlalchemy.orm', 'util')
-collections = util.importlater('sqlalchemy.orm', 'collections')
__all__ = (
'AttributeExtension',
@@ -42,97 +45,6 @@ __all__ = (
'StrategizedProperty',
)
-EXT_CONTINUE = util.symbol('EXT_CONTINUE')
-EXT_STOP = util.symbol('EXT_STOP')
-
-ONETOMANY = util.symbol('ONETOMANY')
-MANYTOONE = util.symbol('MANYTOONE')
-MANYTOMANY = util.symbol('MANYTOMANY')
-
-from .deprecated_interfaces import AttributeExtension, \
- SessionExtension, \
- MapperExtension
-
-
-NOT_EXTENSION = util.symbol('NOT_EXTENSION')
-"""Symbol indicating an :class:`_InspectionAttr` that's
- not part of sqlalchemy.ext.
-
- Is assigned to the :attr:`._InspectionAttr.extension_type`
- attibute.
-
-"""
-
-class _InspectionAttr(object):
- """A base class applied to all ORM objects that can be returned
- by the :func:`.inspect` function.
-
- The attributes defined here allow the usage of simple boolean
- checks to test basic facts about the object returned.
-
- While the boolean checks here are basically the same as using
- the Python isinstance() function, the flags here can be used without
- the need to import all of these classes, and also such that
- the SQLAlchemy class system can change while leaving the flags
- here intact for forwards-compatibility.
-
- """
-
- is_selectable = False
- """Return True if this object is an instance of :class:`.Selectable`."""
-
- is_aliased_class = False
- """True if this object is an instance of :class:`.AliasedClass`."""
-
- is_instance = False
- """True if this object is an instance of :class:`.InstanceState`."""
-
- is_mapper = False
- """True if this object is an instance of :class:`.Mapper`."""
-
- is_property = False
- """True if this object is an instance of :class:`.MapperProperty`."""
-
- is_attribute = False
- """True if this object is a Python :term:`descriptor`.
-
- This can refer to one of many types. Usually a
- :class:`.QueryableAttribute` which handles attributes events on behalf
- of a :class:`.MapperProperty`. But can also be an extension type
- such as :class:`.AssociationProxy` or :class:`.hybrid_property`.
- The :attr:`._InspectionAttr.extension_type` will refer to a constant
- identifying the specific subtype.
-
- .. seealso::
-
- :attr:`.Mapper.all_orm_descriptors`
-
- """
-
- is_clause_element = False
- """True if this object is an instance of :class:`.ClauseElement`."""
-
- extension_type = NOT_EXTENSION
- """The extension type, if any.
- Defaults to :data:`.interfaces.NOT_EXTENSION`
-
- .. versionadded:: 0.8.0
-
- .. seealso::
-
- :data:`.HYBRID_METHOD`
-
- :data:`.HYBRID_PROPERTY`
-
- :data:`.ASSOCIATION_PROXY`
-
- """
-
-class _MappedAttribute(object):
- """Mixin for attributes which should be replaced by mapper-assigned
- attributes.
-
- """
class MapperProperty(_MappedAttribute, _InspectionAttr):
@@ -542,6 +454,30 @@ class StrategizedProperty(MapperProperty):
self.strategy.init_class_attribute(mapper)
+ _strategies = collections.defaultdict(dict)
+
+ @classmethod
+ def _strategy_for(cls, *keys):
+ def decorate(dec_cls):
+ for key in keys:
+ key = tuple(sorted(key.items()))
+ cls._strategies[cls][key] = dec_cls
+ return dec_cls
+ return decorate
+
+ @classmethod
+ def _strategy_lookup(cls, **kw):
+ key = tuple(sorted(kw.items()))
+ for prop_cls in cls.__mro__:
+ if prop_cls in cls._strategies:
+ strategies = cls._strategies[prop_cls]
+ try:
+ return strategies[key]
+ except KeyError:
+ pass
+ raise Exception("can't locate strategy for %s %s" % (cls, kw))
+
+
class MapperOption(object):
"""Describe a modification to a Query."""
@@ -608,10 +544,10 @@ class PropertyOption(MapperOption):
self.__dict__ = state
def _find_entity_prop_comparator(self, query, token, mapper, raiseerr):
- if orm_util._is_aliased_class(mapper):
+ if _is_aliased_class(mapper):
searchfor = mapper
else:
- searchfor = orm_util._class_to_mapper(mapper)
+ searchfor = _class_to_mapper(mapper)
for ent in query._mapper_entities:
if ent.corresponds_to(searchfor):
return ent
@@ -650,14 +586,15 @@ class PropertyOption(MapperOption):
else:
return None
- def _process_paths(self, query, raiseerr):
+ @util.dependencies("sqlalchemy.orm.util")
+ def _process_paths(self, orm_util, query, raiseerr):
"""reconcile the 'key' for this PropertyOption with
the current path and entities of the query.
Return a list of affected paths.
"""
- path = orm_util.PathRegistry.root
+ path = PathRegistry.root
entity = None
paths = []
no_result = []
diff --git a/lib/sqlalchemy/orm/loading.py b/lib/sqlalchemy/orm/loading.py
index c219ea096..512a07d66 100644
--- a/lib/sqlalchemy/orm/loading.py
+++ b/lib/sqlalchemy/orm/loading.py
@@ -20,9 +20,6 @@ from ..sql import util as sql_util
from .util import _none_set, state_str
from .. import exc as sa_exc
-querylib = util.importlater("sqlalchemy.orm", "query")
-sessionlib = util.importlater("sqlalchemy.orm", "session")
-
_new_runid = util.counter()
@@ -100,7 +97,8 @@ def instances(query, cursor, context):
break
-def merge_result(query, iterator, load=True):
+@util.dependencies("sqlalchemy.orm.query")
+def merge_result(querylib, query, iterator, load=True):
"""Merge a result into this :class:`.Query` object's Session."""
session = query.session
@@ -547,7 +545,7 @@ def load_scalar_attributes(mapper, state, attribute_names):
"""initiate a column-based attribute refresh operation."""
#assert mapper is _state_mapper(state)
- session = sessionlib._state_session(state)
+ session = state.session
if not session:
raise orm_exc.DetachedInstanceError(
"Instance %s is not bound to a Session; "
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)
diff --git a/lib/sqlalchemy/orm/path_registry.py b/lib/sqlalchemy/orm/path_registry.py
new file mode 100644
index 000000000..c9c91f905
--- /dev/null
+++ b/lib/sqlalchemy/orm/path_registry.py
@@ -0,0 +1,220 @@
+# orm/path_registry.py
+# Copyright (C) 2005-2013 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
+"""Path tracking utilities, representing mapper graph traversals.
+
+"""
+
+from .. import inspection
+from .. import util
+from itertools import chain
+from .base import class_mapper
+
+def _unreduce_path(path):
+ return PathRegistry.deserialize(path)
+
+class PathRegistry(object):
+ """Represent query load paths and registry functions.
+
+ Basically represents structures like:
+
+ (<User mapper>, "orders", <Order mapper>, "items", <Item mapper>)
+
+ These structures are generated by things like
+ query options (joinedload(), subqueryload(), etc.) and are
+ used to compose keys stored in the query._attributes dictionary
+ for various options.
+
+ They are then re-composed at query compile/result row time as
+ the query is formed and as rows are fetched, where they again
+ serve to compose keys to look up options in the context.attributes
+ dictionary, which is copied from query._attributes.
+
+ The path structure has a limited amount of caching, where each
+ "root" ultimately pulls from a fixed registry associated with
+ the first mapper, that also contains elements for each of its
+ property keys. However paths longer than two elements, which
+ are the exception rather than the rule, are generated on an
+ as-needed basis.
+
+ """
+
+ def __eq__(self, other):
+ return other is not None and \
+ self.path == other.path
+
+ def set(self, attributes, key, value):
+ attributes[(key, self.path)] = value
+
+ def setdefault(self, attributes, key, value):
+ attributes.setdefault((key, self.path), value)
+
+ def get(self, attributes, key, value=None):
+ key = (key, self.path)
+ if key in attributes:
+ return attributes[key]
+ else:
+ return value
+
+ def __len__(self):
+ return len(self.path)
+
+ @property
+ def length(self):
+ return len(self.path)
+
+ def pairs(self):
+ path = self.path
+ for i in range(0, len(path), 2):
+ yield path[i], path[i + 1]
+
+ def contains_mapper(self, mapper):
+ for path_mapper in [
+ self.path[i] for i in range(0, len(self.path), 2)
+ ]:
+ if path_mapper.is_mapper and \
+ path_mapper.isa(mapper):
+ return True
+ else:
+ return False
+
+ def contains(self, attributes, key):
+ return (key, self.path) in attributes
+
+ def __reduce__(self):
+ return _unreduce_path, (self.serialize(), )
+
+ def serialize(self):
+ path = self.path
+ return list(zip(
+ [m.class_ for m in [path[i] for i in range(0, len(path), 2)]],
+ [path[i].key for i in range(1, len(path), 2)] + [None]
+ ))
+
+ @classmethod
+ def deserialize(cls, path):
+ if path is None:
+ return None
+
+ p = tuple(chain(*[(class_mapper(mcls),
+ class_mapper(mcls).attrs[key]
+ if key is not None else None)
+ for mcls, key in path]))
+ if p and p[-1] is None:
+ p = p[0:-1]
+ return cls.coerce(p)
+
+ @classmethod
+ def per_mapper(cls, mapper):
+ return EntityRegistry(
+ cls.root, mapper
+ )
+
+ @classmethod
+ def coerce(cls, raw):
+ return util.reduce(lambda prev, next: prev[next], raw, cls.root)
+
+ @classmethod
+ def token(cls, token):
+ return TokenRegistry(cls.root, token)
+
+ def __add__(self, other):
+ return util.reduce(
+ lambda prev, next: prev[next],
+ other.path, self)
+
+ def __repr__(self):
+ return "%s(%r)" % (self.__class__.__name__, self.path, )
+
+
+class RootRegistry(PathRegistry):
+ """Root registry, defers to mappers so that
+ paths are maintained per-root-mapper.
+
+ """
+ path = ()
+
+ def __getitem__(self, entity):
+ return entity._path_registry
+PathRegistry.root = RootRegistry()
+
+class TokenRegistry(PathRegistry):
+ def __init__(self, parent, token):
+ self.token = token
+ self.parent = parent
+ self.path = parent.path + (token,)
+
+ def __getitem__(self, entity):
+ raise NotImplementedError()
+
+class PropRegistry(PathRegistry):
+ def __init__(self, parent, prop):
+ # restate this path in terms of the
+ # given MapperProperty's parent.
+ insp = inspection.inspect(parent[-1])
+ if not insp.is_aliased_class or insp._use_mapper_path:
+ parent = parent.parent[prop.parent]
+ elif insp.is_aliased_class and insp.with_polymorphic_mappers:
+ if prop.parent is not insp.mapper and \
+ prop.parent in insp.with_polymorphic_mappers:
+ subclass_entity = parent[-1]._entity_for_mapper(prop.parent)
+ parent = parent.parent[subclass_entity]
+
+ self.prop = prop
+ self.parent = parent
+ self.path = parent.path + (prop,)
+
+ def __getitem__(self, entity):
+ if isinstance(entity, (int, slice)):
+ return self.path[entity]
+ else:
+ return EntityRegistry(
+ self, entity
+ )
+
+
+class EntityRegistry(PathRegistry, dict):
+ is_aliased_class = False
+
+ def __init__(self, parent, entity):
+ self.key = entity
+ self.parent = parent
+ self.is_aliased_class = entity.is_aliased_class
+
+ self.path = parent.path + (entity,)
+
+ def __bool__(self):
+ return True
+ __nonzero__ = __bool__
+
+ def __getitem__(self, entity):
+ if isinstance(entity, (int, slice)):
+ return self.path[entity]
+ else:
+ return dict.__getitem__(self, entity)
+
+ def _inlined_get_for(self, prop, context, key):
+ """an inlined version of:
+
+ cls = path[mapperproperty].get(context, key)
+
+ Skips the isinstance() check in __getitem__
+ and the extra method call for get().
+ Used by StrategizedProperty for its
+ very frequent lookup.
+
+ """
+ path = dict.__getitem__(self, prop)
+ path_key = (key, path.path)
+ if path_key in context.attributes:
+ return context.attributes[path_key]
+ else:
+ return None
+
+ def __missing__(self, key):
+ self[key] = item = PropRegistry(self, key)
+ return item
+
+
diff --git a/lib/sqlalchemy/orm/persistence.py b/lib/sqlalchemy/orm/persistence.py
index 44da88118..14970ef25 100644
--- a/lib/sqlalchemy/orm/persistence.py
+++ b/lib/sqlalchemy/orm/persistence.py
@@ -17,7 +17,7 @@ import operator
from itertools import groupby
from .. import sql, util, exc as sa_exc, schema
from . import attributes, sync, exc as orm_exc, evaluator
-from .util import _state_mapper, state_str, _attr_as_key
+from .base import _state_mapper, state_str, _attr_as_key
from ..sql import expression
from . import loading
diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py
index 551461818..ef71d663c 100644
--- a/lib/sqlalchemy/orm/properties.py
+++ b/lib/sqlalchemy/orm/properties.py
@@ -10,29 +10,18 @@ This is a private module which defines the behavior of invidual ORM-
mapped attributes.
"""
+from __future__ import absolute_import
-from .. import sql, util, log, exc as sa_exc, inspect
-from ..sql import operators, expression
-from . import (
- attributes, mapper,
- strategies, configure_mappers, relationships,
- dependency
- )
-from .util import CascadeOptions, \
- _orm_annotate, _orm_deannotate, _orm_full_deannotate
+from .. import util, log
+from ..sql import expression
+from . import attributes
+from .util import _orm_full_deannotate
-from .interfaces import MANYTOMANY, MANYTOONE, ONETOMANY,\
- PropComparator, StrategizedProperty
-
-dynamic = util.importlater("sqlalchemy.orm", "dynamic")
-mapperlib = util.importlater("sqlalchemy.orm", "mapperlib")
-NoneType = type(None)
-
-from .descriptor_props import CompositeProperty, SynonymProperty, \
- ComparableProperty, ConcreteInheritedProperty
+from .interfaces import PropComparator, StrategizedProperty
__all__ = ['ColumnProperty', 'CompositeProperty', 'SynonymProperty',
- 'ComparableProperty', 'RelationshipProperty', 'RelationProperty']
+ 'ComparableProperty', 'RelationshipProperty']
+
@log.class_logger
class ColumnProperty(StrategizedProperty):
@@ -43,30 +32,78 @@ class ColumnProperty(StrategizedProperty):
"""
def __init__(self, *columns, **kwargs):
- """Construct a ColumnProperty.
+ """Provide a column-level property for use with a Mapper.
- Note the public constructor is the :func:`.orm.column_property`
- function.
+ Column-based properties can normally be applied to the mapper's
+ ``properties`` dictionary using the :class:`.Column` element directly.
+ Use this function when the given column is not directly present within the
+ mapper's selectable; examples include SQL expressions, functions, and
+ scalar SELECT queries.
- :param \*columns: The list of `columns` describes a single
- object property. If there are multiple tables joined
- together for the mapper, this list represents the equivalent
- column as it appears across each table.
+ Columns that aren't present in the mapper's selectable won't be persisted
+ by the mapper and are effectively "read-only" attributes.
- :param group:
+ :param \*cols:
+ list of Column objects to be mapped.
- :param deferred:
+ :param active_history=False:
+ When ``True``, indicates that the "previous" value for a
+ scalar attribute should be loaded when replaced, if not
+ already loaded. Normally, history tracking logic for
+ simple non-primary-key scalar values only needs to be
+ aware of the "new" value in order to perform a flush. This
+ flag is available for applications that make use of
+ :func:`.attributes.get_history` or :meth:`.Session.is_modified`
+ which also need to know
+ the "previous" value of the attribute.
- :param comparator_factory:
+ .. versionadded:: 0.6.6
- :param descriptor:
+ :param comparator_factory: a class which extends
+ :class:`.ColumnProperty.Comparator` which provides custom SQL clause
+ generation for comparison operations.
- :param expire_on_flush:
+ :param group:
+ a group name for this property when marked as deferred.
- :param extension:
+ :param deferred:
+ when True, the column property is "deferred", meaning that
+ it does not load immediately, and is instead loaded when the
+ attribute is first accessed on an instance. See also
+ :func:`~sqlalchemy.orm.deferred`.
+
+ :param doc:
+ optional string that will be applied as the doc on the
+ class-bound descriptor.
+
+ :param expire_on_flush=True:
+ Disable expiry on flush. A column_property() which refers
+ to a SQL expression (and not a single table-bound column)
+ is considered to be a "read only" property; populating it
+ has no effect on the state of data, and it can only return
+ database state. For this reason a column_property()'s value
+ is expired whenever the parent object is involved in a
+ flush, that is, has any kind of "dirty" state within a flush.
+ Setting this parameter to ``False`` will have the effect of
+ leaving any existing value present after the flush proceeds.
+ Note however that the :class:`.Session` with default expiration
+ settings still expires
+ all attributes after a :meth:`.Session.commit` call, however.
+
+ .. versionadded:: 0.7.3
:param info: Optional data dictionary which will be populated into the
- :attr:`.info` attribute of this object.
+ :attr:`.MapperProperty.info` attribute of this object.
+
+ .. versionadded:: 0.8
+
+ :param extension:
+ an
+ :class:`.AttributeExtension`
+ instance, or list of extensions, which will be prepended
+ to the list of attribute listeners for the resulting
+ descriptor placed on the class.
+ **Deprecated.** Please see :class:`.AttributeEvents`.
"""
self._orig_columns = [expression._labeled(c) for c in columns]
@@ -103,12 +140,10 @@ class ColumnProperty(StrategizedProperty):
', '.join(sorted(kwargs.keys()))))
util.set_creation_order(self)
- if not self.instrument:
- self.strategy_class = strategies.UninstrumentedColumnLoader
- elif self.deferred:
- self.strategy_class = strategies.DeferredColumnLoader
- else:
- self.strategy_class = strategies.ColumnLoader
+
+ self.strategy_class = self._strategy_lookup(
+ deferred=self.deferred,
+ instrument=self.instrument)
@property
def expression(self):
@@ -216,1098 +251,6 @@ class ColumnProperty(StrategizedProperty):
col = self.__clause_element__()
return op(col._bind_param(op, other), col, **kwargs)
- # TODO: legacy..do we need this ? (0.5)
- ColumnComparator = Comparator
-
def __str__(self):
return str(self.parent.class_.__name__) + "." + self.key
-
-@log.class_logger
-class RelationshipProperty(StrategizedProperty):
- """Describes an object property that holds a single item or list
- of items that correspond to a related database table.
-
- Public constructor is the :func:`.orm.relationship` function.
-
- See also:
-
- :ref:`relationship_config_toplevel`
-
- """
-
- strategy_wildcard_key = 'relationship:*'
-
- _dependency_processor = None
-
- def __init__(self, argument,
- secondary=None, primaryjoin=None,
- secondaryjoin=None,
- foreign_keys=None,
- uselist=None,
- order_by=False,
- backref=None,
- back_populates=None,
- post_update=False,
- cascade=False, extension=None,
- viewonly=False, lazy=True,
- collection_class=None, passive_deletes=False,
- passive_updates=True, remote_side=None,
- enable_typechecks=True, join_depth=None,
- comparator_factory=None,
- single_parent=False, innerjoin=False,
- doc=None,
- active_history=False,
- cascade_backrefs=True,
- load_on_pending=False,
- strategy_class=None, _local_remote_pairs=None,
- query_class=None,
- info=None):
-
- self.uselist = uselist
- self.argument = argument
- self.secondary = secondary
- self.primaryjoin = primaryjoin
- self.secondaryjoin = secondaryjoin
- self.post_update = post_update
- self.direction = None
- self.viewonly = viewonly
- self.lazy = lazy
- self.single_parent = single_parent
- self._user_defined_foreign_keys = foreign_keys
- self.collection_class = collection_class
- self.passive_deletes = passive_deletes
- self.cascade_backrefs = cascade_backrefs
- self.passive_updates = passive_updates
- self.remote_side = remote_side
- self.enable_typechecks = enable_typechecks
- self.query_class = query_class
- self.innerjoin = innerjoin
- self.doc = doc
- self.active_history = active_history
- self.join_depth = join_depth
- self.local_remote_pairs = _local_remote_pairs
- self.extension = extension
- self.load_on_pending = load_on_pending
- self.comparator_factory = comparator_factory or \
- RelationshipProperty.Comparator
- self.comparator = self.comparator_factory(self, None)
- util.set_creation_order(self)
-
- if info is not None:
- self.info = info
-
- if strategy_class:
- self.strategy_class = strategy_class
- elif self.lazy == 'dynamic':
- self.strategy_class = dynamic.DynaLoader
- else:
- self.strategy_class = strategies.factory(self.lazy)
-
- self._reverse_property = set()
-
- self.cascade = cascade if cascade is not False \
- else "save-update, merge"
-
- self.order_by = order_by
-
- self.back_populates = back_populates
-
- if self.back_populates:
- if backref:
- raise sa_exc.ArgumentError(
- "backref and back_populates keyword arguments "
- "are mutually exclusive")
- self.backref = None
- else:
- self.backref = backref
-
- def instrument_class(self, mapper):
- attributes.register_descriptor(
- mapper.class_,
- self.key,
- comparator=self.comparator_factory(self, mapper),
- parententity=mapper,
- doc=self.doc,
- )
-
- class Comparator(PropComparator):
- """Produce boolean, comparison, and other operators for
- :class:`.RelationshipProperty` attributes.
-
- See the documentation for :class:`.PropComparator` for a brief overview
- of ORM level operator definition.
-
- See also:
-
- :class:`.PropComparator`
-
- :class:`.ColumnProperty.Comparator`
-
- :class:`.ColumnOperators`
-
- :ref:`types_operators`
-
- :attr:`.TypeEngine.comparator_factory`
-
- """
-
- _of_type = None
-
- def __init__(self, prop, parentmapper, adapt_to_entity=None, of_type=None):
- """Construction of :class:`.RelationshipProperty.Comparator`
- is internal to the ORM's attribute mechanics.
-
- """
- self.prop = prop
- self._parentmapper = parentmapper
- self._adapt_to_entity = adapt_to_entity
- if of_type:
- self._of_type = of_type
-
- def adapt_to_entity(self, adapt_to_entity):
- return self.__class__(self.property, self._parentmapper,
- adapt_to_entity=adapt_to_entity,
- of_type=self._of_type)
-
- @util.memoized_property
- def mapper(self):
- """The target :class:`.Mapper` referred to by this
- :class:`.RelationshipProperty.Comparator.
-
- This is the "target" or "remote" side of the
- :func:`.relationship`.
-
- """
- return self.property.mapper
-
- @util.memoized_property
- def _parententity(self):
- return self.property.parent
-
- def _source_selectable(self):
- elem = self.property.parent._with_polymorphic_selectable
- if self.adapter:
- return self.adapter(elem)
- else:
- return elem
-
- def __clause_element__(self):
- adapt_from = self._source_selectable()
- if self._of_type:
- of_type = inspect(self._of_type).mapper
- else:
- of_type = None
-
- pj, sj, source, dest, \
- secondary, target_adapter = self.property._create_joins(
- source_selectable=adapt_from,
- source_polymorphic=True,
- of_type=of_type)
- if sj is not None:
- return pj & sj
- else:
- return pj
-
- def of_type(self, cls):
- """Produce a construct that represents a particular 'subtype' of
- attribute for the parent class.
-
- Currently this is usable in conjunction with :meth:`.Query.join`
- and :meth:`.Query.outerjoin`.
-
- """
- return RelationshipProperty.Comparator(
- self.property,
- self._parentmapper,
- adapt_to_entity=self._adapt_to_entity,
- of_type=cls)
-
- def in_(self, other):
- """Produce an IN clause - this is not implemented
- for :func:`~.orm.relationship`-based attributes at this time.
-
- """
- raise NotImplementedError('in_() not yet supported for '
- 'relationships. For a simple many-to-one, use '
- 'in_() against the set of foreign key values.')
-
- __hash__ = None
-
- def __eq__(self, other):
- """Implement the ``==`` operator.
-
- In a many-to-one context, such as::
-
- MyClass.some_prop == <some object>
-
- this will typically produce a
- clause such as::
-
- mytable.related_id == <some id>
-
- Where ``<some id>`` is the primary key of the given
- object.
-
- The ``==`` operator provides partial functionality for non-
- many-to-one comparisons:
-
- * Comparisons against collections are not supported.
- Use :meth:`~.RelationshipProperty.Comparator.contains`.
- * Compared to a scalar one-to-many, will produce a
- clause that compares the target columns in the parent to
- the given target.
- * Compared to a scalar many-to-many, an alias
- of the association table will be rendered as
- well, forming a natural join that is part of the
- main body of the query. This will not work for
- queries that go beyond simple AND conjunctions of
- comparisons, such as those which use OR. Use
- explicit joins, outerjoins, or
- :meth:`~.RelationshipProperty.Comparator.has` for
- more comprehensive non-many-to-one scalar
- membership tests.
- * Comparisons against ``None`` given in a one-to-many
- or many-to-many context produce a NOT EXISTS clause.
-
- """
- if isinstance(other, (NoneType, expression.Null)):
- if self.property.direction in [ONETOMANY, MANYTOMANY]:
- return ~self._criterion_exists()
- else:
- return _orm_annotate(self.property._optimized_compare(
- None, adapt_source=self.adapter))
- elif self.property.uselist:
- raise sa_exc.InvalidRequestError("Can't compare a colle"
- "ction to an object or collection; use "
- "contains() to test for membership.")
- else:
- return _orm_annotate(self.property._optimized_compare(other,
- adapt_source=self.adapter))
-
- def _criterion_exists(self, criterion=None, **kwargs):
- if getattr(self, '_of_type', None):
- info = inspect(self._of_type)
- target_mapper, to_selectable, is_aliased_class = \
- info.mapper, info.selectable, info.is_aliased_class
- if self.property._is_self_referential and not is_aliased_class:
- to_selectable = to_selectable.alias()
-
- single_crit = target_mapper._single_table_criterion
- if single_crit is not None:
- if criterion is not None:
- criterion = single_crit & criterion
- else:
- criterion = single_crit
- else:
- is_aliased_class = False
- to_selectable = None
-
- if self.adapter:
- source_selectable = self._source_selectable()
- else:
- source_selectable = None
-
- pj, sj, source, dest, secondary, target_adapter = \
- self.property._create_joins(dest_polymorphic=True,
- dest_selectable=to_selectable,
- source_selectable=source_selectable)
-
- for k in kwargs:
- crit = getattr(self.property.mapper.class_, k) == kwargs[k]
- if criterion is None:
- criterion = crit
- else:
- criterion = criterion & crit
-
- # annotate the *local* side of the join condition, in the case
- # of pj + sj this is the full primaryjoin, in the case of just
- # pj its the local side of the primaryjoin.
- if sj is not None:
- j = _orm_annotate(pj) & sj
- else:
- j = _orm_annotate(pj, exclude=self.property.remote_side)
-
- if criterion is not None and target_adapter and not is_aliased_class:
- # limit this adapter to annotated only?
- criterion = target_adapter.traverse(criterion)
-
- # only have the "joined left side" of what we
- # return be subject to Query adaption. The right
- # side of it is used for an exists() subquery and
- # should not correlate or otherwise reach out
- # to anything in the enclosing query.
- if criterion is not None:
- criterion = criterion._annotate(
- {'no_replacement_traverse': True})
-
- crit = j & criterion
-
- ex = sql.exists([1], crit, from_obj=dest).correlate_except(dest)
- if secondary is not None:
- ex = ex.correlate_except(secondary)
- return ex
-
- def any(self, criterion=None, **kwargs):
- """Produce an expression that tests a collection against
- particular criterion, using EXISTS.
-
- An expression like::
-
- session.query(MyClass).filter(
- MyClass.somereference.any(SomeRelated.x==2)
- )
-
-
- Will produce a query like::
-
- SELECT * FROM my_table WHERE
- EXISTS (SELECT 1 FROM related WHERE related.my_id=my_table.id
- AND related.x=2)
-
- Because :meth:`~.RelationshipProperty.Comparator.any` uses
- a correlated subquery, its performance is not nearly as
- good when compared against large target tables as that of
- using a join.
-
- :meth:`~.RelationshipProperty.Comparator.any` is particularly
- useful for testing for empty collections::
-
- session.query(MyClass).filter(
- ~MyClass.somereference.any()
- )
-
- will produce::
-
- SELECT * FROM my_table WHERE
- NOT EXISTS (SELECT 1 FROM related WHERE
- related.my_id=my_table.id)
-
- :meth:`~.RelationshipProperty.Comparator.any` is only
- valid for collections, i.e. a :func:`.relationship`
- that has ``uselist=True``. For scalar references,
- use :meth:`~.RelationshipProperty.Comparator.has`.
-
- """
- if not self.property.uselist:
- raise sa_exc.InvalidRequestError(
- "'any()' not implemented for scalar "
- "attributes. Use has()."
- )
-
- return self._criterion_exists(criterion, **kwargs)
-
- def has(self, criterion=None, **kwargs):
- """Produce an expression that tests a scalar reference against
- particular criterion, using EXISTS.
-
- An expression like::
-
- session.query(MyClass).filter(
- MyClass.somereference.has(SomeRelated.x==2)
- )
-
-
- Will produce a query like::
-
- SELECT * FROM my_table WHERE
- EXISTS (SELECT 1 FROM related WHERE
- related.id==my_table.related_id AND related.x=2)
-
- Because :meth:`~.RelationshipProperty.Comparator.has` uses
- a correlated subquery, its performance is not nearly as
- good when compared against large target tables as that of
- using a join.
-
- :meth:`~.RelationshipProperty.Comparator.has` is only
- valid for scalar references, i.e. a :func:`.relationship`
- that has ``uselist=False``. For collection references,
- use :meth:`~.RelationshipProperty.Comparator.any`.
-
- """
- if self.property.uselist:
- raise sa_exc.InvalidRequestError(
- "'has()' not implemented for collections. "
- "Use any().")
- return self._criterion_exists(criterion, **kwargs)
-
- def contains(self, other, **kwargs):
- """Return a simple expression that tests a collection for
- containment of a particular item.
-
- :meth:`~.RelationshipProperty.Comparator.contains` is
- only valid for a collection, i.e. a
- :func:`~.orm.relationship` that implements
- one-to-many or many-to-many with ``uselist=True``.
-
- When used in a simple one-to-many context, an
- expression like::
-
- MyClass.contains(other)
-
- Produces a clause like::
-
- mytable.id == <some id>
-
- Where ``<some id>`` is the value of the foreign key
- attribute on ``other`` which refers to the primary
- key of its parent object. From this it follows that
- :meth:`~.RelationshipProperty.Comparator.contains` is
- very useful when used with simple one-to-many
- operations.
-
- For many-to-many operations, the behavior of
- :meth:`~.RelationshipProperty.Comparator.contains`
- has more caveats. The association table will be
- rendered in the statement, producing an "implicit"
- join, that is, includes multiple tables in the FROM
- clause which are equated in the WHERE clause::
-
- query(MyClass).filter(MyClass.contains(other))
-
- Produces a query like::
-
- SELECT * FROM my_table, my_association_table AS
- my_association_table_1 WHERE
- my_table.id = my_association_table_1.parent_id
- AND my_association_table_1.child_id = <some id>
-
- Where ``<some id>`` would be the primary key of
- ``other``. From the above, it is clear that
- :meth:`~.RelationshipProperty.Comparator.contains`
- will **not** work with many-to-many collections when
- used in queries that move beyond simple AND
- conjunctions, such as multiple
- :meth:`~.RelationshipProperty.Comparator.contains`
- expressions joined by OR. In such cases subqueries or
- explicit "outer joins" will need to be used instead.
- See :meth:`~.RelationshipProperty.Comparator.any` for
- a less-performant alternative using EXISTS, or refer
- to :meth:`.Query.outerjoin` as well as :ref:`ormtutorial_joins`
- for more details on constructing outer joins.
-
- """
- if not self.property.uselist:
- raise sa_exc.InvalidRequestError(
- "'contains' not implemented for scalar "
- "attributes. Use ==")
- clause = self.property._optimized_compare(other,
- adapt_source=self.adapter)
-
- if self.property.secondaryjoin is not None:
- clause.negation_clause = \
- self.__negated_contains_or_equals(other)
-
- return clause
-
- def __negated_contains_or_equals(self, other):
- if self.property.direction == MANYTOONE:
- state = attributes.instance_state(other)
-
- def state_bindparam(x, state, col):
- o = state.obj() # strong ref
- return sql.bindparam(x, unique=True, callable_=lambda: \
- self.property.mapper._get_committed_attr_by_column(o, col))
-
- def adapt(col):
- if self.adapter:
- return self.adapter(col)
- else:
- return col
-
- if self.property._use_get:
- return sql.and_(*[
- sql.or_(
- adapt(x) != state_bindparam(adapt(x), state, y),
- adapt(x) == None)
- for (x, y) in self.property.local_remote_pairs])
-
- criterion = sql.and_(*[x == y for (x, y) in
- zip(
- self.property.mapper.primary_key,
- self.property.\
- mapper.\
- primary_key_from_instance(other))
- ])
- return ~self._criterion_exists(criterion)
-
- def __ne__(self, other):
- """Implement the ``!=`` operator.
-
- In a many-to-one context, such as::
-
- MyClass.some_prop != <some object>
-
- This will typically produce a clause such as::
-
- mytable.related_id != <some id>
-
- Where ``<some id>`` is the primary key of the
- given object.
-
- The ``!=`` operator provides partial functionality for non-
- many-to-one comparisons:
-
- * Comparisons against collections are not supported.
- Use
- :meth:`~.RelationshipProperty.Comparator.contains`
- in conjunction with :func:`~.expression.not_`.
- * Compared to a scalar one-to-many, will produce a
- clause that compares the target columns in the parent to
- the given target.
- * Compared to a scalar many-to-many, an alias
- of the association table will be rendered as
- well, forming a natural join that is part of the
- main body of the query. This will not work for
- queries that go beyond simple AND conjunctions of
- comparisons, such as those which use OR. Use
- explicit joins, outerjoins, or
- :meth:`~.RelationshipProperty.Comparator.has` in
- conjunction with :func:`~.expression.not_` for
- more comprehensive non-many-to-one scalar
- membership tests.
- * Comparisons against ``None`` given in a one-to-many
- or many-to-many context produce an EXISTS clause.
-
- """
- if isinstance(other, (NoneType, expression.Null)):
- if self.property.direction == MANYTOONE:
- return sql.or_(*[x != None for x in
- self.property._calculated_foreign_keys])
- else:
- return self._criterion_exists()
- elif self.property.uselist:
- raise sa_exc.InvalidRequestError("Can't compare a collection"
- " to an object or collection; use "
- "contains() to test for membership.")
- else:
- return self.__negated_contains_or_equals(other)
-
- @util.memoized_property
- def property(self):
- if mapperlib.module._new_mappers:
- configure_mappers()
- return self.prop
-
- def compare(self, op, value,
- value_is_parent=False,
- alias_secondary=True):
- if op == operators.eq:
- if value is None:
- if self.uselist:
- return ~sql.exists([1], self.primaryjoin)
- else:
- return self._optimized_compare(None,
- value_is_parent=value_is_parent,
- alias_secondary=alias_secondary)
- else:
- return self._optimized_compare(value,
- value_is_parent=value_is_parent,
- alias_secondary=alias_secondary)
- else:
- return op(self.comparator, value)
-
- def _optimized_compare(self, value, value_is_parent=False,
- adapt_source=None,
- alias_secondary=True):
- if value is not None:
- value = attributes.instance_state(value)
- return self._get_strategy(strategies.LazyLoader).lazy_clause(value,
- reverse_direction=not value_is_parent,
- alias_secondary=alias_secondary,
- adapt_source=adapt_source)
-
- def __str__(self):
- return str(self.parent.class_.__name__) + "." + self.key
-
- def merge(self,
- session,
- source_state,
- source_dict,
- dest_state,
- dest_dict,
- load, _recursive):
-
- if load:
- for r in self._reverse_property:
- if (source_state, r) in _recursive:
- return
-
- if not "merge" in self._cascade:
- return
-
- if self.key not in source_dict:
- return
-
- if self.uselist:
- instances = source_state.get_impl(self.key).\
- get(source_state, source_dict)
- if hasattr(instances, '_sa_adapter'):
- # convert collections to adapters to get a true iterator
- instances = instances._sa_adapter
-
- if load:
- # for a full merge, pre-load the destination collection,
- # so that individual _merge of each item pulls from identity
- # map for those already present.
- # also assumes CollectionAttrbiuteImpl behavior of loading
- # "old" list in any case
- dest_state.get_impl(self.key).get(dest_state, dest_dict)
-
- dest_list = []
- for current in instances:
- current_state = attributes.instance_state(current)
- current_dict = attributes.instance_dict(current)
- _recursive[(current_state, self)] = True
- obj = session._merge(current_state, current_dict,
- load=load, _recursive=_recursive)
- if obj is not None:
- dest_list.append(obj)
-
- if not load:
- coll = attributes.init_state_collection(dest_state,
- dest_dict, self.key)
- for c in dest_list:
- coll.append_without_event(c)
- else:
- dest_state.get_impl(self.key)._set_iterable(dest_state,
- dest_dict, dest_list)
- else:
- current = source_dict[self.key]
- if current is not None:
- current_state = attributes.instance_state(current)
- current_dict = attributes.instance_dict(current)
- _recursive[(current_state, self)] = True
- obj = session._merge(current_state, current_dict,
- load=load, _recursive=_recursive)
- else:
- obj = None
-
- if not load:
- dest_dict[self.key] = obj
- else:
- dest_state.get_impl(self.key).set(dest_state,
- dest_dict, obj, None)
-
- def _value_as_iterable(self, state, dict_, key,
- passive=attributes.PASSIVE_OFF):
- """Return a list of tuples (state, obj) for the given
- key.
-
- returns an empty list if the value is None/empty/PASSIVE_NO_RESULT
- """
-
- impl = state.manager[key].impl
- x = impl.get(state, dict_, passive=passive)
- if x is attributes.PASSIVE_NO_RESULT or x is None:
- return []
- elif hasattr(impl, 'get_collection'):
- return [
- (attributes.instance_state(o), o) for o in
- impl.get_collection(state, dict_, x, passive=passive)
- ]
- else:
- return [(attributes.instance_state(x), x)]
-
- def cascade_iterator(self, type_, state, dict_,
- visited_states, halt_on=None):
- #assert type_ in self._cascade
-
- # only actively lazy load on the 'delete' cascade
- if type_ != 'delete' or self.passive_deletes:
- passive = attributes.PASSIVE_NO_INITIALIZE
- else:
- passive = attributes.PASSIVE_OFF
-
- if type_ == 'save-update':
- tuples = state.manager[self.key].impl.\
- get_all_pending(state, dict_)
-
- else:
- tuples = self._value_as_iterable(state, dict_, self.key,
- passive=passive)
-
- skip_pending = type_ == 'refresh-expire' and 'delete-orphan' \
- not in self._cascade
-
- for instance_state, c in tuples:
- if instance_state in visited_states:
- continue
-
- if c is None:
- # would like to emit a warning here, but
- # would not be consistent with collection.append(None)
- # current behavior of silently skipping.
- # see [ticket:2229]
- continue
-
- instance_dict = attributes.instance_dict(c)
-
- if halt_on and halt_on(instance_state):
- continue
-
- if skip_pending and not instance_state.key:
- continue
-
- instance_mapper = instance_state.manager.mapper
-
- if not instance_mapper.isa(self.mapper.class_manager.mapper):
- raise AssertionError("Attribute '%s' on class '%s' "
- "doesn't handle objects "
- "of type '%s'" % (
- self.key,
- self.parent.class_,
- c.__class__
- ))
-
- visited_states.add(instance_state)
-
- yield c, instance_mapper, instance_state, instance_dict
-
- def _add_reverse_property(self, key):
- other = self.mapper.get_property(key, _configure_mappers=False)
- self._reverse_property.add(other)
- other._reverse_property.add(self)
-
- if not other.mapper.common_parent(self.parent):
- raise sa_exc.ArgumentError('reverse_property %r on '
- 'relationship %s references relationship %s, which '
- 'does not reference mapper %s' % (key, self, other,
- self.parent))
- if self.direction in (ONETOMANY, MANYTOONE) and self.direction \
- == other.direction:
- raise sa_exc.ArgumentError('%s and back-reference %s are '
- 'both of the same direction %r. Did you mean to '
- 'set remote_side on the many-to-one side ?'
- % (other, self, self.direction))
-
- @util.memoized_property
- def mapper(self):
- """Return the targeted :class:`.Mapper` for this
- :class:`.RelationshipProperty`.
-
- This is a lazy-initializing static attribute.
-
- """
- if isinstance(self.argument, type):
- mapper_ = mapper.class_mapper(self.argument,
- configure=False)
- elif isinstance(self.argument, mapper.Mapper):
- mapper_ = self.argument
- elif util.callable(self.argument):
-
- # accept a callable to suit various deferred-
- # configurational schemes
-
- mapper_ = mapper.class_mapper(self.argument(),
- configure=False)
- else:
- raise sa_exc.ArgumentError("relationship '%s' expects "
- "a class or a mapper argument (received: %s)"
- % (self.key, type(self.argument)))
- assert isinstance(mapper_, mapper.Mapper), mapper_
- return mapper_
-
- @util.memoized_property
- @util.deprecated("0.7", "Use .target")
- def table(self):
- """Return the selectable linked to this
- :class:`.RelationshipProperty` object's target
- :class:`.Mapper`."""
- return self.target
-
- def do_init(self):
- self._check_conflicts()
- self._process_dependent_arguments()
- self._setup_join_conditions()
- self._check_cascade_settings(self._cascade)
- self._post_init()
- self._generate_backref()
- super(RelationshipProperty, self).do_init()
-
- def _process_dependent_arguments(self):
- """Convert incoming configuration arguments to their
- proper form.
-
- Callables are resolved, ORM annotations removed.
-
- """
- # accept callables for other attributes which may require
- # deferred initialization. This technique is used
- # by declarative "string configs" and some recipes.
- for attr in (
- 'order_by', 'primaryjoin', 'secondaryjoin',
- 'secondary', '_user_defined_foreign_keys', 'remote_side',
- ):
- attr_value = getattr(self, attr)
- if util.callable(attr_value):
- setattr(self, attr, attr_value())
-
- # remove "annotations" which are present if mapped class
- # descriptors are used to create the join expression.
- for attr in 'primaryjoin', 'secondaryjoin':
- val = getattr(self, attr)
- if val is not None:
- setattr(self, attr, _orm_deannotate(
- expression._only_column_elements(val, attr))
- )
-
- # ensure expressions in self.order_by, foreign_keys,
- # remote_side are all columns, not strings.
- if self.order_by is not False and self.order_by is not None:
- self.order_by = [
- expression._only_column_elements(x, "order_by")
- for x in
- util.to_list(self.order_by)]
-
- self._user_defined_foreign_keys = \
- util.column_set(
- expression._only_column_elements(x, "foreign_keys")
- for x in util.to_column_set(
- self._user_defined_foreign_keys
- ))
-
- self.remote_side = \
- util.column_set(
- expression._only_column_elements(x, "remote_side")
- for x in
- util.to_column_set(self.remote_side))
-
- self.target = self.mapper.mapped_table
-
-
- def _setup_join_conditions(self):
- self._join_condition = jc = relationships.JoinCondition(
- parent_selectable=self.parent.mapped_table,
- child_selectable=self.mapper.mapped_table,
- parent_local_selectable=self.parent.local_table,
- child_local_selectable=self.mapper.local_table,
- primaryjoin=self.primaryjoin,
- secondary=self.secondary,
- secondaryjoin=self.secondaryjoin,
- parent_equivalents=self.parent._equivalent_columns,
- child_equivalents=self.mapper._equivalent_columns,
- consider_as_foreign_keys=self._user_defined_foreign_keys,
- local_remote_pairs=self.local_remote_pairs,
- remote_side=self.remote_side,
- self_referential=self._is_self_referential,
- prop=self,
- support_sync=not self.viewonly,
- can_be_synced_fn=self._columns_are_mapped
- )
- self.primaryjoin = jc.deannotated_primaryjoin
- self.secondaryjoin = jc.deannotated_secondaryjoin
- self.direction = jc.direction
- self.local_remote_pairs = jc.local_remote_pairs
- self.remote_side = jc.remote_columns
- self.local_columns = jc.local_columns
- self.synchronize_pairs = jc.synchronize_pairs
- self._calculated_foreign_keys = jc.foreign_key_columns
- self.secondary_synchronize_pairs = jc.secondary_synchronize_pairs
-
- def _check_conflicts(self):
- """Test that this relationship is legal, warn about
- inheritance conflicts."""
-
- if not self.is_primary() \
- and not mapper.class_mapper(
- self.parent.class_,
- configure=False).has_property(self.key):
- raise sa_exc.ArgumentError("Attempting to assign a new "
- "relationship '%s' to a non-primary mapper on "
- "class '%s'. New relationships can only be added "
- "to the primary mapper, i.e. the very first mapper "
- "created for class '%s' " % (self.key,
- self.parent.class_.__name__,
- self.parent.class_.__name__))
-
- # check for conflicting relationship() on superclass
- if not self.parent.concrete:
- for inheriting in self.parent.iterate_to_root():
- if inheriting is not self.parent \
- and inheriting.has_property(self.key):
- util.warn("Warning: relationship '%s' on mapper "
- "'%s' supersedes the same relationship "
- "on inherited mapper '%s'; this can "
- "cause dependency issues during flush"
- % (self.key, self.parent, inheriting))
-
- def _get_cascade(self):
- """Return the current cascade setting for this
- :class:`.RelationshipProperty`.
- """
- return self._cascade
-
- def _set_cascade(self, cascade):
- cascade = CascadeOptions(cascade)
- if 'mapper' in self.__dict__:
- self._check_cascade_settings(cascade)
- self._cascade = cascade
-
- if self._dependency_processor:
- self._dependency_processor.cascade = cascade
-
- cascade = property(_get_cascade, _set_cascade)
-
- def _check_cascade_settings(self, cascade):
- if cascade.delete_orphan and not self.single_parent \
- and (self.direction is MANYTOMANY or self.direction
- is MANYTOONE):
- raise sa_exc.ArgumentError(
- 'On %s, delete-orphan cascade is not supported '
- 'on a many-to-many or many-to-one relationship '
- 'when single_parent is not set. Set '
- 'single_parent=True on the relationship().'
- % self)
- if self.direction is MANYTOONE and self.passive_deletes:
- util.warn("On %s, 'passive_deletes' is normally configured "
- "on one-to-many, one-to-one, many-to-many "
- "relationships only."
- % self)
-
- if self.passive_deletes == 'all' and \
- ("delete" in cascade or
- "delete-orphan" in cascade):
- raise sa_exc.ArgumentError(
- "On %s, can't set passive_deletes='all' in conjunction "
- "with 'delete' or 'delete-orphan' cascade" % self)
-
- if cascade.delete_orphan:
- self.mapper.primary_mapper()._delete_orphans.append(
- (self.key, self.parent.class_)
- )
-
- def _columns_are_mapped(self, *cols):
- """Return True if all columns in the given collection are
- mapped by the tables referenced by this :class:`.Relationship`.
-
- """
- for c in cols:
- if self.secondary is not None \
- and self.secondary.c.contains_column(c):
- continue
- if not self.parent.mapped_table.c.contains_column(c) and \
- not self.target.c.contains_column(c):
- return False
- return True
-
- def _generate_backref(self):
- """Interpret the 'backref' instruction to create a
- :func:`.relationship` complementary to this one."""
-
- if not self.is_primary():
- return
- if self.backref is not None and not self.back_populates:
- if isinstance(self.backref, str):
- backref_key, kwargs = self.backref, {}
- else:
- backref_key, kwargs = self.backref
- mapper = self.mapper.primary_mapper()
-
- check = set(mapper.iterate_to_root()).\
- union(mapper.self_and_descendants)
- for m in check:
- if m.has_property(backref_key):
- raise sa_exc.ArgumentError("Error creating backref "
- "'%s' on relationship '%s': property of that "
- "name exists on mapper '%s'" % (backref_key,
- self, m))
-
- # determine primaryjoin/secondaryjoin for the
- # backref. Use the one we had, so that
- # a custom join doesn't have to be specified in
- # both directions.
- if self.secondary is not None:
- # for many to many, just switch primaryjoin/
- # secondaryjoin. use the annotated
- # pj/sj on the _join_condition.
- pj = kwargs.pop('primaryjoin',
- self._join_condition.secondaryjoin_minus_local)
- sj = kwargs.pop('secondaryjoin',
- self._join_condition.primaryjoin_minus_local)
- else:
- pj = kwargs.pop('primaryjoin',
- self._join_condition.primaryjoin_reverse_remote)
- sj = kwargs.pop('secondaryjoin', None)
- if sj:
- raise sa_exc.InvalidRequestError(
- "Can't assign 'secondaryjoin' on a backref "
- "against a non-secondary relationship."
- )
-
- foreign_keys = kwargs.pop('foreign_keys',
- self._user_defined_foreign_keys)
- parent = self.parent.primary_mapper()
- kwargs.setdefault('viewonly', self.viewonly)
- kwargs.setdefault('post_update', self.post_update)
- kwargs.setdefault('passive_updates', self.passive_updates)
- self.back_populates = backref_key
- relationship = RelationshipProperty(
- parent, self.secondary,
- pj, sj,
- foreign_keys=foreign_keys,
- back_populates=self.key,
- **kwargs)
- mapper._configure_property(backref_key, relationship)
-
- if self.back_populates:
- self._add_reverse_property(self.back_populates)
-
- def _post_init(self):
- if self.uselist is None:
- self.uselist = self.direction is not MANYTOONE
- if not self.viewonly:
- self._dependency_processor = \
- dependency.DependencyProcessor.from_relationship(self)
-
- @util.memoized_property
- def _use_get(self):
- """memoize the 'use_get' attribute of this RelationshipLoader's
- lazyloader."""
-
- strategy = self._get_strategy(strategies.LazyLoader)
- return strategy.use_get
-
- @util.memoized_property
- def _is_self_referential(self):
- return self.mapper.common_parent(self.parent)
-
- def _create_joins(self, source_polymorphic=False,
- source_selectable=None, dest_polymorphic=False,
- dest_selectable=None, of_type=None):
- if source_selectable is None:
- if source_polymorphic and self.parent.with_polymorphic:
- source_selectable = self.parent._with_polymorphic_selectable
-
- aliased = False
- if dest_selectable is None:
- if dest_polymorphic and self.mapper.with_polymorphic:
- dest_selectable = self.mapper._with_polymorphic_selectable
- aliased = True
- else:
- dest_selectable = self.mapper.mapped_table
-
- if self._is_self_referential and source_selectable is None:
- dest_selectable = dest_selectable.alias()
- aliased = True
- else:
- aliased = True
-
- dest_mapper = of_type or self.mapper
-
- single_crit = dest_mapper._single_table_criterion
- aliased = aliased or (source_selectable is not None)
-
- primaryjoin, secondaryjoin, secondary, target_adapter, dest_selectable = \
- self._join_condition.join_targets(
- source_selectable, dest_selectable, aliased, single_crit
- )
- if source_selectable is None:
- source_selectable = self.parent.local_table
- if dest_selectable is None:
- dest_selectable = self.mapper.local_table
- return (primaryjoin, secondaryjoin, source_selectable,
- dest_selectable, secondary, target_adapter)
-
-
-PropertyLoader = RelationProperty = RelationshipProperty
diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py
index ad8183b0d..1c322d5b9 100644
--- a/lib/sqlalchemy/orm/query.py
+++ b/lib/sqlalchemy/orm/query.py
@@ -24,19 +24,18 @@ from . import (
attributes, interfaces, object_mapper, persistence,
exc as orm_exc, loading
)
+from .base import _entity_descriptor, _is_aliased_class, _is_mapped_class, _orm_columns
+from .path_registry import PathRegistry
from .util import (
- AliasedClass, ORMAdapter, _entity_descriptor, PathRegistry,
- _is_aliased_class, _is_mapped_class, _orm_columns,
- join as orm_join, with_parent, aliased
+ AliasedClass, ORMAdapter, join as orm_join, with_parent, aliased
)
-from .. import sql, util, log, exc as sa_exc, inspect, inspection, \
- types as sqltypes
+from .. import sql, util, log, exc as sa_exc, inspect, inspection
from ..sql.expression import _interpret_as_from
from ..sql import (
util as sql_util,
expression, visitors
)
-properties = util.importlater("sqlalchemy.orm", "properties")
+from . import properties
__all__ = ['Query', 'QueryContext', 'aliased']
@@ -908,7 +907,7 @@ class Query(object):
mapper = object_mapper(instance)
for prop in mapper.iterate_properties:
- if isinstance(prop, properties.PropertyLoader) and \
+ if isinstance(prop, properties.RelationshipProperty) and \
prop.mapper is self._mapper_zero():
property = prop
break
@@ -3227,6 +3226,37 @@ class QueryContext(object):
class AliasOption(interfaces.MapperOption):
def __init__(self, alias):
+ """Return a :class:`.MapperOption` that will indicate to the query that
+ the main table has been aliased.
+
+ This is used in the very rare case that :func:`.contains_eager`
+ is being used in conjunction with a user-defined SELECT
+ statement that aliases the parent table. E.g.::
+
+ # define an aliased UNION called 'ulist'
+ statement = users.select(users.c.user_id==7).\\
+ union(users.select(users.c.user_id>7)).\\
+ alias('ulist')
+
+ # add on an eager load of "addresses"
+ statement = statement.outerjoin(addresses).\\
+ select().apply_labels()
+
+ # create query, indicating "ulist" will be an
+ # alias for the main table, "addresses"
+ # property should be eager loaded
+ query = session.query(User).options(
+ contains_alias('ulist'),
+ contains_eager('addresses'))
+
+ # then get results via the statement
+ results = query.from_statement(statement).all()
+
+ :param alias: is the string name of an alias, or a
+ :class:`~.sql.expression.Alias` object representing
+ the alias.
+
+ """
self.alias = alias
def process_query(self, query):
diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py
index 33377d3ec..f37bb8a4d 100644
--- a/lib/sqlalchemy/orm/relationships.py
+++ b/lib/sqlalchemy/orm/relationships.py
@@ -13,15 +13,20 @@ and `secondaryjoin` aspects of :func:`.relationship`.
"""
-from .. import sql, util, exc as sa_exc, schema
+from .. import sql, util, exc as sa_exc, schema, log
+
+from .util import CascadeOptions, _orm_annotate, _orm_deannotate
+from . import dependency
+from . import attributes
from ..sql.util import (
ClauseAdapter,
join_condition, _shallow_annotate, visit_binary_product,
- _deep_deannotate, find_tables, selectables_overlap
+ _deep_deannotate, selectables_overlap
)
from ..sql import operators, expression, visitors
-from .interfaces import MANYTOMANY, MANYTOONE, ONETOMANY
-
+from .interfaces import MANYTOMANY, MANYTOONE, ONETOMANY, StrategizedProperty, PropComparator
+from ..inspection import inspect
+from . import mapper as mapperlib
def remote(expr):
"""Annotate a portion of a primaryjoin expression
@@ -64,6 +69,1583 @@ def foreign(expr):
{"foreign": True})
+@log.class_logger
+@util.langhelpers.dependency_for("sqlalchemy.orm.properties")
+class RelationshipProperty(StrategizedProperty):
+ """Describes an object property that holds a single item or list
+ of items that correspond to a related database table.
+
+ Public constructor is the :func:`.orm.relationship` function.
+
+ See also:
+
+ :ref:`relationship_config_toplevel`
+
+ """
+
+ strategy_wildcard_key = 'relationship:*'
+
+ _dependency_processor = None
+
+ def __init__(self, argument,
+ secondary=None, primaryjoin=None,
+ secondaryjoin=None,
+ foreign_keys=None,
+ uselist=None,
+ order_by=False,
+ backref=None,
+ back_populates=None,
+ post_update=False,
+ cascade=False, extension=None,
+ viewonly=False, lazy=True,
+ collection_class=None, passive_deletes=False,
+ passive_updates=True, remote_side=None,
+ enable_typechecks=True, join_depth=None,
+ comparator_factory=None,
+ single_parent=False, innerjoin=False,
+ doc=None,
+ active_history=False,
+ cascade_backrefs=True,
+ load_on_pending=False,
+ strategy_class=None, _local_remote_pairs=None,
+ query_class=None,
+ info=None):
+ """Provide a relationship of a primary Mapper to a secondary Mapper.
+
+ This corresponds to a parent-child or associative table relationship. The
+ constructed class is an instance of :class:`.RelationshipProperty`.
+
+ A typical :func:`.relationship`, used in a classical mapping::
+
+ mapper(Parent, properties={
+ 'children': relationship(Child)
+ })
+
+ Some arguments accepted by :func:`.relationship` optionally accept a
+ callable function, which when called produces the desired value.
+ The callable is invoked by the parent :class:`.Mapper` at "mapper
+ initialization" time, which happens only when mappers are first used, and
+ is assumed to be after all mappings have been constructed. This can be
+ used to resolve order-of-declaration and other dependency issues, such as
+ if ``Child`` is declared below ``Parent`` in the same file::
+
+ mapper(Parent, properties={
+ "children":relationship(lambda: Child,
+ order_by=lambda: Child.id)
+ })
+
+ When using the :ref:`declarative_toplevel` extension, the Declarative
+ initializer allows string arguments to be passed to :func:`.relationship`.
+ These string arguments are converted into callables that evaluate
+ the string as Python code, using the Declarative
+ class-registry as a namespace. This allows the lookup of related
+ classes to be automatic via their string name, and removes the need to
+ import related classes at all into the local module space::
+
+ from sqlalchemy.ext.declarative import declarative_base
+
+ Base = declarative_base()
+
+ class Parent(Base):
+ __tablename__ = 'parent'
+ id = Column(Integer, primary_key=True)
+ children = relationship("Child", order_by="Child.id")
+
+ A full array of examples and reference documentation regarding
+ :func:`.relationship` is at :ref:`relationship_config_toplevel`.
+
+ :param argument:
+ a mapped class, or actual :class:`.Mapper` instance, representing the
+ target of the relationship.
+
+ ``argument`` may also be passed as a callable function
+ which is evaluated at mapper initialization time, and may be passed as a
+ Python-evaluable string when using Declarative.
+
+ :param secondary:
+ for a many-to-many relationship, specifies the intermediary
+ table, and is an instance of :class:`.Table`. The ``secondary`` keyword
+ argument should generally only
+ be used for a table that is not otherwise expressed in any class
+ mapping, unless this relationship is declared as view only, otherwise
+ conflicting persistence operations can occur.
+
+ ``secondary`` may
+ also be passed as a callable function which is evaluated at
+ mapper initialization time.
+
+ :param active_history=False:
+ When ``True``, indicates that the "previous" value for a
+ many-to-one reference should be loaded when replaced, if
+ not already loaded. Normally, history tracking logic for
+ simple many-to-ones only needs to be aware of the "new"
+ value in order to perform a flush. This flag is available
+ for applications that make use of
+ :func:`.attributes.get_history` which also need to know
+ the "previous" value of the attribute.
+
+ :param backref:
+ indicates the string name of a property to be placed on the related
+ mapper's class that will handle this relationship in the other
+ direction. The other property will be created automatically
+ when the mappers are configured. Can also be passed as a
+ :func:`backref` object to control the configuration of the
+ new relationship.
+
+ :param back_populates:
+ Takes a string name and has the same meaning as ``backref``,
+ except the complementing property is **not** created automatically,
+ and instead must be configured explicitly on the other mapper. The
+ complementing property should also indicate ``back_populates``
+ to this relationship to ensure proper functioning.
+
+ :param cascade:
+ a comma-separated list of cascade rules which determines how
+ Session operations should be "cascaded" from parent to child.
+ This defaults to ``False``, which means the default cascade
+ should be used. The default value is ``"save-update, merge"``.
+
+ Available cascades are:
+
+ * ``save-update`` - cascade the :meth:`.Session.add`
+ operation. This cascade applies both to future and
+ past calls to :meth:`~sqlalchemy.orm.session.Session.add`,
+ meaning new items added to a collection or scalar relationship
+ get placed into the same session as that of the parent, and
+ also applies to items which have been removed from this
+ relationship but are still part of unflushed history.
+
+ * ``merge`` - cascade the :meth:`~sqlalchemy.orm.session.Session.merge`
+ operation
+
+ * ``expunge`` - cascade the :meth:`.Session.expunge`
+ operation
+
+ * ``delete`` - cascade the :meth:`.Session.delete`
+ operation
+
+ * ``delete-orphan`` - if an item of the child's type is
+ detached from its parent, mark it for deletion.
+
+ .. versionchanged:: 0.7
+ This option does not prevent
+ a new instance of the child object from being persisted
+ without a parent to start with; to constrain against
+ that case, ensure the child's foreign key column(s)
+ is configured as NOT NULL
+
+ * ``refresh-expire`` - cascade the :meth:`.Session.expire`
+ and :meth:`~sqlalchemy.orm.session.Session.refresh` operations
+
+ * ``all`` - shorthand for "save-update,merge, refresh-expire,
+ expunge, delete"
+
+ See the section :ref:`unitofwork_cascades` for more background
+ on configuring cascades.
+
+ :param cascade_backrefs=True:
+ a boolean value indicating if the ``save-update`` cascade should
+ operate along an assignment event intercepted by a backref.
+ When set to ``False``,
+ the attribute managed by this relationship will not cascade
+ an incoming transient object into the session of a
+ persistent parent, if the event is received via backref.
+
+ That is::
+
+ mapper(A, a_table, properties={
+ 'bs':relationship(B, backref="a", cascade_backrefs=False)
+ })
+
+ If an ``A()`` is present in the session, assigning it to
+ the "a" attribute on a transient ``B()`` will not place
+ the ``B()`` into the session. To set the flag in the other
+ direction, i.e. so that ``A().bs.append(B())`` won't add
+ a transient ``A()`` into the session for a persistent ``B()``::
+
+ mapper(A, a_table, properties={
+ 'bs':relationship(B,
+ backref=backref("a", cascade_backrefs=False)
+ )
+ })
+
+ See the section :ref:`unitofwork_cascades` for more background
+ on configuring cascades.
+
+ :param collection_class:
+ a class or callable that returns a new list-holding object. will
+ be used in place of a plain list for storing elements.
+ Behavior of this attribute is described in detail at
+ :ref:`custom_collections`.
+
+ :param comparator_factory:
+ a class which extends :class:`.RelationshipProperty.Comparator` which
+ provides custom SQL clause generation for comparison operations.
+
+ :param doc:
+ docstring which will be applied to the resulting descriptor.
+
+ :param extension:
+ an :class:`.AttributeExtension` instance, or list of extensions,
+ which will be prepended to the list of attribute listeners for
+ the resulting descriptor placed on the class.
+ **Deprecated.** Please see :class:`.AttributeEvents`.
+
+ :param foreign_keys:
+ a list of columns which are to be used as "foreign key" columns,
+ or columns which refer to the value in a remote column, within the
+ context of this :func:`.relationship` object's ``primaryjoin``
+ condition. That is, if the ``primaryjoin`` condition of this
+ :func:`.relationship` is ``a.id == b.a_id``, and the values in ``b.a_id``
+ are required to be present in ``a.id``, then the "foreign key" column
+ of this :func:`.relationship` is ``b.a_id``.
+
+ In normal cases, the ``foreign_keys`` parameter is **not required.**
+ :func:`.relationship` will **automatically** determine which columns
+ in the ``primaryjoin`` conditition are to be considered "foreign key"
+ columns based on those :class:`.Column` objects that specify
+ :class:`.ForeignKey`, or are otherwise listed as referencing columns
+ in a :class:`.ForeignKeyConstraint` construct. ``foreign_keys`` is only
+ needed when:
+
+ 1. There is more than one way to construct a join from the local
+ table to the remote table, as there are multiple foreign key
+ references present. Setting ``foreign_keys`` will limit the
+ :func:`.relationship` to consider just those columns specified
+ here as "foreign".
+
+ .. versionchanged:: 0.8
+ A multiple-foreign key join ambiguity can be resolved by
+ setting the ``foreign_keys`` parameter alone, without the
+ need to explicitly set ``primaryjoin`` as well.
+
+ 2. The :class:`.Table` being mapped does not actually have
+ :class:`.ForeignKey` or :class:`.ForeignKeyConstraint`
+ constructs present, often because the table
+ was reflected from a database that does not support foreign key
+ reflection (MySQL MyISAM).
+
+ 3. The ``primaryjoin`` argument is used to construct a non-standard
+ join condition, which makes use of columns or expressions that do
+ not normally refer to their "parent" column, such as a join condition
+ expressed by a complex comparison using a SQL function.
+
+ The :func:`.relationship` construct will raise informative error messages
+ that suggest the use of the ``foreign_keys`` parameter when presented
+ with an ambiguous condition. In typical cases, if :func:`.relationship`
+ doesn't raise any exceptions, the ``foreign_keys`` parameter is usually
+ not needed.
+
+ ``foreign_keys`` may also be passed as a callable function
+ which is evaluated at mapper initialization time, and may be passed as a
+ Python-evaluable string when using Declarative.
+
+ .. seealso::
+
+ :ref:`relationship_foreign_keys`
+
+ :ref:`relationship_custom_foreign`
+
+ :func:`.foreign` - allows direct annotation of the "foreign" columns
+ within a ``primaryjoin`` condition.
+
+ .. versionadded:: 0.8
+ The :func:`.foreign` annotation can also be applied
+ directly to the ``primaryjoin`` expression, which is an alternate,
+ more specific system of describing which columns in a particular
+ ``primaryjoin`` should be considered "foreign".
+
+ :param info: Optional data dictionary which will be populated into the
+ :attr:`.MapperProperty.info` attribute of this object.
+
+ .. versionadded:: 0.8
+
+ :param innerjoin=False:
+ when ``True``, joined eager loads will use an inner join to join
+ against related tables instead of an outer join. The purpose
+ of this option is generally one of performance, as inner joins
+ generally perform better than outer joins. Another reason can be
+ the use of ``with_lockmode``, which does not support outer joins.
+
+ This flag can be set to ``True`` when the relationship references an
+ object via many-to-one using local foreign keys that are not nullable,
+ or when the reference is one-to-one or a collection that is guaranteed
+ to have one or at least one entry.
+
+ :param join_depth:
+ when non-``None``, an integer value indicating how many levels
+ deep "eager" loaders should join on a self-referring or cyclical
+ relationship. The number counts how many times the same Mapper
+ shall be present in the loading condition along a particular join
+ branch. When left at its default of ``None``, eager loaders
+ will stop chaining when they encounter a the same target mapper
+ which is already higher up in the chain. This option applies
+ both to joined- and subquery- eager loaders.
+
+ :param lazy='select': specifies
+ how the related items should be loaded. Default value is
+ ``select``. Values include:
+
+ * ``select`` - items should be loaded lazily when the property is first
+ accessed, using a separate SELECT statement, or identity map
+ fetch for simple many-to-one references.
+
+ * ``immediate`` - items should be loaded as the parents are loaded,
+ using a separate SELECT statement, or identity map fetch for
+ simple many-to-one references.
+
+ .. versionadded:: 0.6.5
+
+ * ``joined`` - items should be loaded "eagerly" in the same query as
+ that of the parent, using a JOIN or LEFT OUTER JOIN. Whether
+ the join is "outer" or not is determined by the ``innerjoin``
+ parameter.
+
+ * ``subquery`` - items should be loaded "eagerly" as the parents are
+ loaded, using one additional SQL statement, which issues a JOIN to a
+ subquery of the original statement, for each collection requested.
+
+ * ``noload`` - no loading should occur at any time. This is to
+ support "write-only" attributes, or attributes which are
+ populated in some manner specific to the application.
+
+ * ``dynamic`` - the attribute will return a pre-configured
+ :class:`~sqlalchemy.orm.query.Query` object for all read
+ operations, onto which further filtering operations can be
+ applied before iterating the results. See
+ the section :ref:`dynamic_relationship` for more details.
+
+ * True - a synonym for 'select'
+
+ * False - a synonym for 'joined'
+
+ * None - a synonym for 'noload'
+
+ Detailed discussion of loader strategies is at :doc:`/orm/loading`.
+
+ :param load_on_pending=False:
+ Indicates loading behavior for transient or pending parent objects.
+
+ .. versionchanged:: 0.8
+ load_on_pending is superseded by
+ :meth:`.Session.enable_relationship_loading`.
+
+ When set to ``True``, causes the lazy-loader to
+ issue a query for a parent object that is not persistent, meaning it has
+ never been flushed. This may take effect for a pending object when
+ autoflush is disabled, or for a transient object that has been
+ "attached" to a :class:`.Session` but is not part of its pending
+ collection.
+
+ The load_on_pending flag does not improve behavior
+ when the ORM is used normally - object references should be constructed
+ at the object level, not at the foreign key level, so that they
+ are present in an ordinary way before flush() proceeds. This flag
+ is not not intended for general use.
+
+ .. versionadded:: 0.6.5
+
+ :param order_by:
+ indicates the ordering that should be applied when loading these
+ items. ``order_by`` is expected to refer to one of the :class:`.Column`
+ objects to which the target class is mapped, or
+ the attribute itself bound to the target class which refers
+ to the column.
+
+ ``order_by`` may also be passed as a callable function
+ which is evaluated at mapper initialization time, and may be passed as a
+ Python-evaluable string when using Declarative.
+
+ :param passive_deletes=False:
+ Indicates loading behavior during delete operations.
+
+ A value of True indicates that unloaded child items should not
+ be loaded during a delete operation on the parent. Normally,
+ when a parent item is deleted, all child items are loaded so
+ that they can either be marked as deleted, or have their
+ foreign key to the parent set to NULL. Marking this flag as
+ True usually implies an ON DELETE <CASCADE|SET NULL> rule is in
+ place which will handle updating/deleting child rows on the
+ database side.
+
+ Additionally, setting the flag to the string value 'all' will
+ disable the "nulling out" of the child foreign keys, when there
+ is no delete or delete-orphan cascade enabled. This is
+ typically used when a triggering or error raise scenario is in
+ place on the database side. Note that the foreign key
+ attributes on in-session child objects will not be changed
+ after a flush occurs so this is a very special use-case
+ setting.
+
+ :param passive_updates=True:
+ Indicates loading and INSERT/UPDATE/DELETE behavior when the
+ source of a foreign key value changes (i.e. an "on update"
+ cascade), which are typically the primary key columns of the
+ source row.
+
+ 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 rows. Note that with databases which enforce
+ referential integrity (i.e. PostgreSQL, MySQL with InnoDB tables),
+ ON UPDATE CASCADE is required for this operation. The
+ relationship() will update the value of the attribute on related
+ items which are locally present in the session during a flush.
+
+ 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 relationship() will issue the
+ appropriate UPDATE statements to the database in response to the
+ change of a referenced key, and items locally present in the
+ session during a flush will also be refreshed.
+
+ This flag should probably be set to False if primary key changes
+ are expected and the database in use doesn't support CASCADE
+ (i.e. SQLite, MySQL MyISAM tables).
+
+ Also see the passive_updates flag on ``mapper()``.
+
+ A future SQLAlchemy release will provide a "detect" feature for
+ this flag.
+
+ :param post_update:
+ this indicates that the relationship should be handled by a
+ second UPDATE statement after an INSERT or before a
+ DELETE. Currently, it also will issue an UPDATE after the
+ instance was UPDATEd as well, although this technically should
+ be improved. This flag is used to handle saving bi-directional
+ dependencies between two individual rows (i.e. each row
+ references the other), where it would otherwise be impossible to
+ INSERT or DELETE both rows fully since one row exists before the
+ other. Use this flag when a particular mapping arrangement will
+ incur two rows that are dependent on each other, such as a table
+ that has a one-to-many relationship to a set of child rows, and
+ also has a column that references a single child row within that
+ list (i.e. both tables contain a foreign key to each other). If
+ a ``flush()`` operation returns an error that a "cyclical
+ dependency" was detected, this is a cue that you might want to
+ use ``post_update`` to "break" the cycle.
+
+ :param primaryjoin:
+ a SQL expression that will be used as the primary
+ join of this child object against the parent object, or in a
+ many-to-many relationship the join of the primary object to the
+ association table. By default, this value is computed based on the
+ foreign key relationships of the parent and child tables (or association
+ table).
+
+ ``primaryjoin`` may also be passed as a callable function
+ which is evaluated at mapper initialization time, and may be passed as a
+ Python-evaluable string when using Declarative.
+
+ :param remote_side:
+ used for self-referential relationships, indicates the column or
+ list of columns that form the "remote side" of the relationship.
+
+ ``remote_side`` may also be passed as a callable function
+ which is evaluated at mapper initialization time, and may be passed as a
+ Python-evaluable string when using Declarative.
+
+ .. versionchanged:: 0.8
+ The :func:`.remote` annotation can also be applied
+ directly to the ``primaryjoin`` expression, which is an alternate,
+ more specific system of describing which columns in a particular
+ ``primaryjoin`` should be considered "remote".
+
+ :param query_class:
+ a :class:`.Query` subclass that will be used as the base of the
+ "appender query" returned by a "dynamic" relationship, that
+ is, a relationship that specifies ``lazy="dynamic"`` or was
+ otherwise constructed using the :func:`.orm.dynamic_loader`
+ function.
+
+ :param secondaryjoin:
+ a SQL expression that will be used as the join of
+ an association table to the child object. By default, this value is
+ computed based on the foreign key relationships of the association and
+ child tables.
+
+ ``secondaryjoin`` may also be passed as a callable function
+ which is evaluated at mapper initialization time, and may be passed as a
+ Python-evaluable string when using Declarative.
+
+ :param single_parent=(True|False):
+ when True, installs a validator which will prevent objects
+ from being associated with more than one parent at a time.
+ This is used for many-to-one or many-to-many relationships that
+ should be treated either as one-to-one or one-to-many. Its
+ usage is optional unless delete-orphan cascade is also
+ set on this relationship(), in which case its required.
+
+ :param uselist=(True|False):
+ a boolean that indicates if this property should be loaded as a
+ list or a scalar. In most cases, this value is determined
+ automatically by ``relationship()``, based on the type and direction
+ of the relationship - one to many forms a list, many to one
+ forms a scalar, many to many is a list. If a scalar is desired
+ where normally a list would be present, such as a bi-directional
+ one-to-one relationship, set uselist to False.
+
+ :param viewonly=False:
+ when set to True, the relationship is used only for loading objects
+ within the relationship, and has no effect on the unit-of-work
+ flush process. Relationships with viewonly can specify any kind of
+ join conditions to provide additional views of related objects
+ onto a parent object. Note that the functionality of a viewonly
+ relationship has its limits - complicated join conditions may
+ not compile into eager or lazy loaders properly. If this is the
+ case, use an alternative method.
+
+ .. versionchanged:: 0.6
+ :func:`relationship` was renamed from its previous name
+ :func:`relation`.
+
+ """
+
+ self.uselist = uselist
+ self.argument = argument
+ self.secondary = secondary
+ self.primaryjoin = primaryjoin
+ self.secondaryjoin = secondaryjoin
+ self.post_update = post_update
+ self.direction = None
+ self.viewonly = viewonly
+ self.lazy = lazy
+ self.single_parent = single_parent
+ self._user_defined_foreign_keys = foreign_keys
+ self.collection_class = collection_class
+ self.passive_deletes = passive_deletes
+ self.cascade_backrefs = cascade_backrefs
+ self.passive_updates = passive_updates
+ self.remote_side = remote_side
+ self.enable_typechecks = enable_typechecks
+ self.query_class = query_class
+ self.innerjoin = innerjoin
+ self.doc = doc
+ self.active_history = active_history
+ self.join_depth = join_depth
+ self.local_remote_pairs = _local_remote_pairs
+ self.extension = extension
+ self.load_on_pending = load_on_pending
+ self.comparator_factory = comparator_factory or \
+ RelationshipProperty.Comparator
+ self.comparator = self.comparator_factory(self, None)
+ util.set_creation_order(self)
+
+ if info is not None:
+ self.info = info
+
+ if strategy_class:
+ self.strategy_class = strategy_class
+ else:
+ self.strategy_class = self._strategy_lookup(lazy=self.lazy)
+ self._lazy_strategy = self._strategy_lookup(lazy="select")
+
+ self._reverse_property = set()
+
+ self.cascade = cascade if cascade is not False \
+ else "save-update, merge"
+
+ self.order_by = order_by
+
+ self.back_populates = back_populates
+
+ if self.back_populates:
+ if backref:
+ raise sa_exc.ArgumentError(
+ "backref and back_populates keyword arguments "
+ "are mutually exclusive")
+ self.backref = None
+ else:
+ self.backref = backref
+
+ def instrument_class(self, mapper):
+ attributes.register_descriptor(
+ mapper.class_,
+ self.key,
+ comparator=self.comparator_factory(self, mapper),
+ parententity=mapper,
+ doc=self.doc,
+ )
+
+ class Comparator(PropComparator):
+ """Produce boolean, comparison, and other operators for
+ :class:`.RelationshipProperty` attributes.
+
+ See the documentation for :class:`.PropComparator` for a brief overview
+ of ORM level operator definition.
+
+ See also:
+
+ :class:`.PropComparator`
+
+ :class:`.ColumnProperty.Comparator`
+
+ :class:`.ColumnOperators`
+
+ :ref:`types_operators`
+
+ :attr:`.TypeEngine.comparator_factory`
+
+ """
+
+ _of_type = None
+
+ def __init__(self, prop, parentmapper, adapt_to_entity=None, of_type=None):
+ """Construction of :class:`.RelationshipProperty.Comparator`
+ is internal to the ORM's attribute mechanics.
+
+ """
+ self.prop = prop
+ self._parentmapper = parentmapper
+ self._adapt_to_entity = adapt_to_entity
+ if of_type:
+ self._of_type = of_type
+
+ def adapt_to_entity(self, adapt_to_entity):
+ return self.__class__(self.property, self._parentmapper,
+ adapt_to_entity=adapt_to_entity,
+ of_type=self._of_type)
+
+ @util.memoized_property
+ def mapper(self):
+ """The target :class:`.Mapper` referred to by this
+ :class:`.RelationshipProperty.Comparator.
+
+ This is the "target" or "remote" side of the
+ :func:`.relationship`.
+
+ """
+ return self.property.mapper
+
+ @util.memoized_property
+ def _parententity(self):
+ return self.property.parent
+
+ def _source_selectable(self):
+ elem = self.property.parent._with_polymorphic_selectable
+ if self.adapter:
+ return self.adapter(elem)
+ else:
+ return elem
+
+ def __clause_element__(self):
+ adapt_from = self._source_selectable()
+ if self._of_type:
+ of_type = inspect(self._of_type).mapper
+ else:
+ of_type = None
+
+ pj, sj, source, dest, \
+ secondary, target_adapter = self.property._create_joins(
+ source_selectable=adapt_from,
+ source_polymorphic=True,
+ of_type=of_type)
+ if sj is not None:
+ return pj & sj
+ else:
+ return pj
+
+ def of_type(self, cls):
+ """Produce a construct that represents a particular 'subtype' of
+ attribute for the parent class.
+
+ Currently this is usable in conjunction with :meth:`.Query.join`
+ and :meth:`.Query.outerjoin`.
+
+ """
+ return RelationshipProperty.Comparator(
+ self.property,
+ self._parentmapper,
+ adapt_to_entity=self._adapt_to_entity,
+ of_type=cls)
+
+ def in_(self, other):
+ """Produce an IN clause - this is not implemented
+ for :func:`~.orm.relationship`-based attributes at this time.
+
+ """
+ raise NotImplementedError('in_() not yet supported for '
+ 'relationships. For a simple many-to-one, use '
+ 'in_() against the set of foreign key values.')
+
+ __hash__ = None
+
+ def __eq__(self, other):
+ """Implement the ``==`` operator.
+
+ In a many-to-one context, such as::
+
+ MyClass.some_prop == <some object>
+
+ this will typically produce a
+ clause such as::
+
+ mytable.related_id == <some id>
+
+ Where ``<some id>`` is the primary key of the given
+ object.
+
+ The ``==`` operator provides partial functionality for non-
+ many-to-one comparisons:
+
+ * Comparisons against collections are not supported.
+ Use :meth:`~.RelationshipProperty.Comparator.contains`.
+ * Compared to a scalar one-to-many, will produce a
+ clause that compares the target columns in the parent to
+ the given target.
+ * Compared to a scalar many-to-many, an alias
+ of the association table will be rendered as
+ well, forming a natural join that is part of the
+ main body of the query. This will not work for
+ queries that go beyond simple AND conjunctions of
+ comparisons, such as those which use OR. Use
+ explicit joins, outerjoins, or
+ :meth:`~.RelationshipProperty.Comparator.has` for
+ more comprehensive non-many-to-one scalar
+ membership tests.
+ * Comparisons against ``None`` given in a one-to-many
+ or many-to-many context produce a NOT EXISTS clause.
+
+ """
+ if isinstance(other, (util.NoneType, expression.Null)):
+ if self.property.direction in [ONETOMANY, MANYTOMANY]:
+ return ~self._criterion_exists()
+ else:
+ return _orm_annotate(self.property._optimized_compare(
+ None, adapt_source=self.adapter))
+ elif self.property.uselist:
+ raise sa_exc.InvalidRequestError("Can't compare a colle"
+ "ction to an object or collection; use "
+ "contains() to test for membership.")
+ else:
+ return _orm_annotate(self.property._optimized_compare(other,
+ adapt_source=self.adapter))
+
+ def _criterion_exists(self, criterion=None, **kwargs):
+ if getattr(self, '_of_type', None):
+ info = inspect(self._of_type)
+ target_mapper, to_selectable, is_aliased_class = \
+ info.mapper, info.selectable, info.is_aliased_class
+ if self.property._is_self_referential and not is_aliased_class:
+ to_selectable = to_selectable.alias()
+
+ single_crit = target_mapper._single_table_criterion
+ if single_crit is not None:
+ if criterion is not None:
+ criterion = single_crit & criterion
+ else:
+ criterion = single_crit
+ else:
+ is_aliased_class = False
+ to_selectable = None
+
+ if self.adapter:
+ source_selectable = self._source_selectable()
+ else:
+ source_selectable = None
+
+ pj, sj, source, dest, secondary, target_adapter = \
+ self.property._create_joins(dest_polymorphic=True,
+ dest_selectable=to_selectable,
+ source_selectable=source_selectable)
+
+ for k in kwargs:
+ crit = getattr(self.property.mapper.class_, k) == kwargs[k]
+ if criterion is None:
+ criterion = crit
+ else:
+ criterion = criterion & crit
+
+ # annotate the *local* side of the join condition, in the case
+ # of pj + sj this is the full primaryjoin, in the case of just
+ # pj its the local side of the primaryjoin.
+ if sj is not None:
+ j = _orm_annotate(pj) & sj
+ else:
+ j = _orm_annotate(pj, exclude=self.property.remote_side)
+
+ if criterion is not None and target_adapter and not is_aliased_class:
+ # limit this adapter to annotated only?
+ criterion = target_adapter.traverse(criterion)
+
+ # only have the "joined left side" of what we
+ # return be subject to Query adaption. The right
+ # side of it is used for an exists() subquery and
+ # should not correlate or otherwise reach out
+ # to anything in the enclosing query.
+ if criterion is not None:
+ criterion = criterion._annotate(
+ {'no_replacement_traverse': True})
+
+ crit = j & criterion
+
+ ex = sql.exists([1], crit, from_obj=dest).correlate_except(dest)
+ if secondary is not None:
+ ex = ex.correlate_except(secondary)
+ return ex
+
+ def any(self, criterion=None, **kwargs):
+ """Produce an expression that tests a collection against
+ particular criterion, using EXISTS.
+
+ An expression like::
+
+ session.query(MyClass).filter(
+ MyClass.somereference.any(SomeRelated.x==2)
+ )
+
+
+ Will produce a query like::
+
+ SELECT * FROM my_table WHERE
+ EXISTS (SELECT 1 FROM related WHERE related.my_id=my_table.id
+ AND related.x=2)
+
+ Because :meth:`~.RelationshipProperty.Comparator.any` uses
+ a correlated subquery, its performance is not nearly as
+ good when compared against large target tables as that of
+ using a join.
+
+ :meth:`~.RelationshipProperty.Comparator.any` is particularly
+ useful for testing for empty collections::
+
+ session.query(MyClass).filter(
+ ~MyClass.somereference.any()
+ )
+
+ will produce::
+
+ SELECT * FROM my_table WHERE
+ NOT EXISTS (SELECT 1 FROM related WHERE
+ related.my_id=my_table.id)
+
+ :meth:`~.RelationshipProperty.Comparator.any` is only
+ valid for collections, i.e. a :func:`.relationship`
+ that has ``uselist=True``. For scalar references,
+ use :meth:`~.RelationshipProperty.Comparator.has`.
+
+ """
+ if not self.property.uselist:
+ raise sa_exc.InvalidRequestError(
+ "'any()' not implemented for scalar "
+ "attributes. Use has()."
+ )
+
+ return self._criterion_exists(criterion, **kwargs)
+
+ def has(self, criterion=None, **kwargs):
+ """Produce an expression that tests a scalar reference against
+ particular criterion, using EXISTS.
+
+ An expression like::
+
+ session.query(MyClass).filter(
+ MyClass.somereference.has(SomeRelated.x==2)
+ )
+
+
+ Will produce a query like::
+
+ SELECT * FROM my_table WHERE
+ EXISTS (SELECT 1 FROM related WHERE
+ related.id==my_table.related_id AND related.x=2)
+
+ Because :meth:`~.RelationshipProperty.Comparator.has` uses
+ a correlated subquery, its performance is not nearly as
+ good when compared against large target tables as that of
+ using a join.
+
+ :meth:`~.RelationshipProperty.Comparator.has` is only
+ valid for scalar references, i.e. a :func:`.relationship`
+ that has ``uselist=False``. For collection references,
+ use :meth:`~.RelationshipProperty.Comparator.any`.
+
+ """
+ if self.property.uselist:
+ raise sa_exc.InvalidRequestError(
+ "'has()' not implemented for collections. "
+ "Use any().")
+ return self._criterion_exists(criterion, **kwargs)
+
+ def contains(self, other, **kwargs):
+ """Return a simple expression that tests a collection for
+ containment of a particular item.
+
+ :meth:`~.RelationshipProperty.Comparator.contains` is
+ only valid for a collection, i.e. a
+ :func:`~.orm.relationship` that implements
+ one-to-many or many-to-many with ``uselist=True``.
+
+ When used in a simple one-to-many context, an
+ expression like::
+
+ MyClass.contains(other)
+
+ Produces a clause like::
+
+ mytable.id == <some id>
+
+ Where ``<some id>`` is the value of the foreign key
+ attribute on ``other`` which refers to the primary
+ key of its parent object. From this it follows that
+ :meth:`~.RelationshipProperty.Comparator.contains` is
+ very useful when used with simple one-to-many
+ operations.
+
+ For many-to-many operations, the behavior of
+ :meth:`~.RelationshipProperty.Comparator.contains`
+ has more caveats. The association table will be
+ rendered in the statement, producing an "implicit"
+ join, that is, includes multiple tables in the FROM
+ clause which are equated in the WHERE clause::
+
+ query(MyClass).filter(MyClass.contains(other))
+
+ Produces a query like::
+
+ SELECT * FROM my_table, my_association_table AS
+ my_association_table_1 WHERE
+ my_table.id = my_association_table_1.parent_id
+ AND my_association_table_1.child_id = <some id>
+
+ Where ``<some id>`` would be the primary key of
+ ``other``. From the above, it is clear that
+ :meth:`~.RelationshipProperty.Comparator.contains`
+ will **not** work with many-to-many collections when
+ used in queries that move beyond simple AND
+ conjunctions, such as multiple
+ :meth:`~.RelationshipProperty.Comparator.contains`
+ expressions joined by OR. In such cases subqueries or
+ explicit "outer joins" will need to be used instead.
+ See :meth:`~.RelationshipProperty.Comparator.any` for
+ a less-performant alternative using EXISTS, or refer
+ to :meth:`.Query.outerjoin` as well as :ref:`ormtutorial_joins`
+ for more details on constructing outer joins.
+
+ """
+ if not self.property.uselist:
+ raise sa_exc.InvalidRequestError(
+ "'contains' not implemented for scalar "
+ "attributes. Use ==")
+ clause = self.property._optimized_compare(other,
+ adapt_source=self.adapter)
+
+ if self.property.secondaryjoin is not None:
+ clause.negation_clause = \
+ self.__negated_contains_or_equals(other)
+
+ return clause
+
+ def __negated_contains_or_equals(self, other):
+ if self.property.direction == MANYTOONE:
+ state = attributes.instance_state(other)
+
+ def state_bindparam(x, state, col):
+ o = state.obj() # strong ref
+ return sql.bindparam(x, unique=True, callable_=lambda: \
+ self.property.mapper._get_committed_attr_by_column(o, col))
+
+ def adapt(col):
+ if self.adapter:
+ return self.adapter(col)
+ else:
+ return col
+
+ if self.property._use_get:
+ return sql.and_(*[
+ sql.or_(
+ adapt(x) != state_bindparam(adapt(x), state, y),
+ adapt(x) == None)
+ for (x, y) in self.property.local_remote_pairs])
+
+ criterion = sql.and_(*[x == y for (x, y) in
+ zip(
+ self.property.mapper.primary_key,
+ self.property.\
+ mapper.\
+ primary_key_from_instance(other))
+ ])
+ return ~self._criterion_exists(criterion)
+
+ def __ne__(self, other):
+ """Implement the ``!=`` operator.
+
+ In a many-to-one context, such as::
+
+ MyClass.some_prop != <some object>
+
+ This will typically produce a clause such as::
+
+ mytable.related_id != <some id>
+
+ Where ``<some id>`` is the primary key of the
+ given object.
+
+ The ``!=`` operator provides partial functionality for non-
+ many-to-one comparisons:
+
+ * Comparisons against collections are not supported.
+ Use
+ :meth:`~.RelationshipProperty.Comparator.contains`
+ in conjunction with :func:`~.expression.not_`.
+ * Compared to a scalar one-to-many, will produce a
+ clause that compares the target columns in the parent to
+ the given target.
+ * Compared to a scalar many-to-many, an alias
+ of the association table will be rendered as
+ well, forming a natural join that is part of the
+ main body of the query. This will not work for
+ queries that go beyond simple AND conjunctions of
+ comparisons, such as those which use OR. Use
+ explicit joins, outerjoins, or
+ :meth:`~.RelationshipProperty.Comparator.has` in
+ conjunction with :func:`~.expression.not_` for
+ more comprehensive non-many-to-one scalar
+ membership tests.
+ * Comparisons against ``None`` given in a one-to-many
+ or many-to-many context produce an EXISTS clause.
+
+ """
+ if isinstance(other, (util.NoneType, expression.Null)):
+ if self.property.direction == MANYTOONE:
+ return sql.or_(*[x != None for x in
+ self.property._calculated_foreign_keys])
+ else:
+ return self._criterion_exists()
+ elif self.property.uselist:
+ raise sa_exc.InvalidRequestError("Can't compare a collection"
+ " to an object or collection; use "
+ "contains() to test for membership.")
+ else:
+ return self.__negated_contains_or_equals(other)
+
+ @util.memoized_property
+ def property(self):
+ if mapperlib.Mapper._new_mappers:
+ mapperlib.Mapper._configure_all()
+ return self.prop
+
+ def compare(self, op, value,
+ value_is_parent=False,
+ alias_secondary=True):
+ if op == operators.eq:
+ if value is None:
+ if self.uselist:
+ return ~sql.exists([1], self.primaryjoin)
+ else:
+ return self._optimized_compare(None,
+ value_is_parent=value_is_parent,
+ alias_secondary=alias_secondary)
+ else:
+ return self._optimized_compare(value,
+ value_is_parent=value_is_parent,
+ alias_secondary=alias_secondary)
+ else:
+ return op(self.comparator, value)
+
+ def _optimized_compare(self, value, value_is_parent=False,
+ adapt_source=None,
+ alias_secondary=True):
+ if value is not None:
+ value = attributes.instance_state(value)
+ return self._get_strategy(self._lazy_strategy).lazy_clause(value,
+ reverse_direction=not value_is_parent,
+ alias_secondary=alias_secondary,
+ adapt_source=adapt_source)
+
+ def __str__(self):
+ return str(self.parent.class_.__name__) + "." + self.key
+
+ def merge(self,
+ session,
+ source_state,
+ source_dict,
+ dest_state,
+ dest_dict,
+ load, _recursive):
+
+ if load:
+ for r in self._reverse_property:
+ if (source_state, r) in _recursive:
+ return
+
+ if not "merge" in self._cascade:
+ return
+
+ if self.key not in source_dict:
+ return
+
+ if self.uselist:
+ instances = source_state.get_impl(self.key).\
+ get(source_state, source_dict)
+ if hasattr(instances, '_sa_adapter'):
+ # convert collections to adapters to get a true iterator
+ instances = instances._sa_adapter
+
+ if load:
+ # for a full merge, pre-load the destination collection,
+ # so that individual _merge of each item pulls from identity
+ # map for those already present.
+ # also assumes CollectionAttrbiuteImpl behavior of loading
+ # "old" list in any case
+ dest_state.get_impl(self.key).get(dest_state, dest_dict)
+
+ dest_list = []
+ for current in instances:
+ current_state = attributes.instance_state(current)
+ current_dict = attributes.instance_dict(current)
+ _recursive[(current_state, self)] = True
+ obj = session._merge(current_state, current_dict,
+ load=load, _recursive=_recursive)
+ if obj is not None:
+ dest_list.append(obj)
+
+ if not load:
+ coll = attributes.init_state_collection(dest_state,
+ dest_dict, self.key)
+ for c in dest_list:
+ coll.append_without_event(c)
+ else:
+ dest_state.get_impl(self.key)._set_iterable(dest_state,
+ dest_dict, dest_list)
+ else:
+ current = source_dict[self.key]
+ if current is not None:
+ current_state = attributes.instance_state(current)
+ current_dict = attributes.instance_dict(current)
+ _recursive[(current_state, self)] = True
+ obj = session._merge(current_state, current_dict,
+ load=load, _recursive=_recursive)
+ else:
+ obj = None
+
+ if not load:
+ dest_dict[self.key] = obj
+ else:
+ dest_state.get_impl(self.key).set(dest_state,
+ dest_dict, obj, None)
+
+ def _value_as_iterable(self, state, dict_, key,
+ passive=attributes.PASSIVE_OFF):
+ """Return a list of tuples (state, obj) for the given
+ key.
+
+ returns an empty list if the value is None/empty/PASSIVE_NO_RESULT
+ """
+
+ impl = state.manager[key].impl
+ x = impl.get(state, dict_, passive=passive)
+ if x is attributes.PASSIVE_NO_RESULT or x is None:
+ return []
+ elif hasattr(impl, 'get_collection'):
+ return [
+ (attributes.instance_state(o), o) for o in
+ impl.get_collection(state, dict_, x, passive=passive)
+ ]
+ else:
+ return [(attributes.instance_state(x), x)]
+
+ def cascade_iterator(self, type_, state, dict_,
+ visited_states, halt_on=None):
+ #assert type_ in self._cascade
+
+ # only actively lazy load on the 'delete' cascade
+ if type_ != 'delete' or self.passive_deletes:
+ passive = attributes.PASSIVE_NO_INITIALIZE
+ else:
+ passive = attributes.PASSIVE_OFF
+
+ if type_ == 'save-update':
+ tuples = state.manager[self.key].impl.\
+ get_all_pending(state, dict_)
+
+ else:
+ tuples = self._value_as_iterable(state, dict_, self.key,
+ passive=passive)
+
+ skip_pending = type_ == 'refresh-expire' and 'delete-orphan' \
+ not in self._cascade
+
+ for instance_state, c in tuples:
+ if instance_state in visited_states:
+ continue
+
+ if c is None:
+ # would like to emit a warning here, but
+ # would not be consistent with collection.append(None)
+ # current behavior of silently skipping.
+ # see [ticket:2229]
+ continue
+
+ instance_dict = attributes.instance_dict(c)
+
+ if halt_on and halt_on(instance_state):
+ continue
+
+ if skip_pending and not instance_state.key:
+ continue
+
+ instance_mapper = instance_state.manager.mapper
+
+ if not instance_mapper.isa(self.mapper.class_manager.mapper):
+ raise AssertionError("Attribute '%s' on class '%s' "
+ "doesn't handle objects "
+ "of type '%s'" % (
+ self.key,
+ self.parent.class_,
+ c.__class__
+ ))
+
+ visited_states.add(instance_state)
+
+ yield c, instance_mapper, instance_state, instance_dict
+
+ def _add_reverse_property(self, key):
+ other = self.mapper.get_property(key, _configure_mappers=False)
+ self._reverse_property.add(other)
+ other._reverse_property.add(self)
+
+ if not other.mapper.common_parent(self.parent):
+ raise sa_exc.ArgumentError('reverse_property %r on '
+ 'relationship %s references relationship %s, which '
+ 'does not reference mapper %s' % (key, self, other,
+ self.parent))
+ if self.direction in (ONETOMANY, MANYTOONE) and self.direction \
+ == other.direction:
+ raise sa_exc.ArgumentError('%s and back-reference %s are '
+ 'both of the same direction %r. Did you mean to '
+ 'set remote_side on the many-to-one side ?'
+ % (other, self, self.direction))
+
+ @util.memoized_property
+ def mapper(self):
+ """Return the targeted :class:`.Mapper` for this
+ :class:`.RelationshipProperty`.
+
+ This is a lazy-initializing static attribute.
+
+ """
+ if isinstance(self.argument, type):
+ mapper_ = mapperlib.class_mapper(self.argument,
+ configure=False)
+ elif isinstance(self.argument, mapperlib.Mapper):
+ mapper_ = self.argument
+ elif util.callable(self.argument):
+
+ # accept a callable to suit various deferred-
+ # configurational schemes
+
+ mapper_ = mapperlib.class_mapper(self.argument(),
+ configure=False)
+ else:
+ raise sa_exc.ArgumentError("relationship '%s' expects "
+ "a class or a mapper argument (received: %s)"
+ % (self.key, type(self.argument)))
+ assert isinstance(mapper_, mapperlib.Mapper), mapper_
+ return mapper_
+
+ @util.memoized_property
+ @util.deprecated("0.7", "Use .target")
+ def table(self):
+ """Return the selectable linked to this
+ :class:`.RelationshipProperty` object's target
+ :class:`.Mapper`."""
+ return self.target
+
+ def do_init(self):
+ self._check_conflicts()
+ self._process_dependent_arguments()
+ self._setup_join_conditions()
+ self._check_cascade_settings(self._cascade)
+ self._post_init()
+ self._generate_backref()
+ super(RelationshipProperty, self).do_init()
+
+ def _process_dependent_arguments(self):
+ """Convert incoming configuration arguments to their
+ proper form.
+
+ Callables are resolved, ORM annotations removed.
+
+ """
+ # accept callables for other attributes which may require
+ # deferred initialization. This technique is used
+ # by declarative "string configs" and some recipes.
+ for attr in (
+ 'order_by', 'primaryjoin', 'secondaryjoin',
+ 'secondary', '_user_defined_foreign_keys', 'remote_side',
+ ):
+ attr_value = getattr(self, attr)
+ if util.callable(attr_value):
+ setattr(self, attr, attr_value())
+
+ # remove "annotations" which are present if mapped class
+ # descriptors are used to create the join expression.
+ for attr in 'primaryjoin', 'secondaryjoin':
+ val = getattr(self, attr)
+ if val is not None:
+ setattr(self, attr, _orm_deannotate(
+ expression._only_column_elements(val, attr))
+ )
+
+ # ensure expressions in self.order_by, foreign_keys,
+ # remote_side are all columns, not strings.
+ if self.order_by is not False and self.order_by is not None:
+ self.order_by = [
+ expression._only_column_elements(x, "order_by")
+ for x in
+ util.to_list(self.order_by)]
+
+ self._user_defined_foreign_keys = \
+ util.column_set(
+ expression._only_column_elements(x, "foreign_keys")
+ for x in util.to_column_set(
+ self._user_defined_foreign_keys
+ ))
+
+ self.remote_side = \
+ util.column_set(
+ expression._only_column_elements(x, "remote_side")
+ for x in
+ util.to_column_set(self.remote_side))
+
+ self.target = self.mapper.mapped_table
+
+
+ def _setup_join_conditions(self):
+ self._join_condition = jc = JoinCondition(
+ parent_selectable=self.parent.mapped_table,
+ child_selectable=self.mapper.mapped_table,
+ parent_local_selectable=self.parent.local_table,
+ child_local_selectable=self.mapper.local_table,
+ primaryjoin=self.primaryjoin,
+ secondary=self.secondary,
+ secondaryjoin=self.secondaryjoin,
+ parent_equivalents=self.parent._equivalent_columns,
+ child_equivalents=self.mapper._equivalent_columns,
+ consider_as_foreign_keys=self._user_defined_foreign_keys,
+ local_remote_pairs=self.local_remote_pairs,
+ remote_side=self.remote_side,
+ self_referential=self._is_self_referential,
+ prop=self,
+ support_sync=not self.viewonly,
+ can_be_synced_fn=self._columns_are_mapped
+ )
+ self.primaryjoin = jc.deannotated_primaryjoin
+ self.secondaryjoin = jc.deannotated_secondaryjoin
+ self.direction = jc.direction
+ self.local_remote_pairs = jc.local_remote_pairs
+ self.remote_side = jc.remote_columns
+ self.local_columns = jc.local_columns
+ self.synchronize_pairs = jc.synchronize_pairs
+ self._calculated_foreign_keys = jc.foreign_key_columns
+ self.secondary_synchronize_pairs = jc.secondary_synchronize_pairs
+
+ def _check_conflicts(self):
+ """Test that this relationship is legal, warn about
+ inheritance conflicts."""
+
+ if not self.is_primary() \
+ and not mapperlib.class_mapper(
+ self.parent.class_,
+ configure=False).has_property(self.key):
+ raise sa_exc.ArgumentError("Attempting to assign a new "
+ "relationship '%s' to a non-primary mapper on "
+ "class '%s'. New relationships can only be added "
+ "to the primary mapper, i.e. the very first mapper "
+ "created for class '%s' " % (self.key,
+ self.parent.class_.__name__,
+ self.parent.class_.__name__))
+
+ # check for conflicting relationship() on superclass
+ if not self.parent.concrete:
+ for inheriting in self.parent.iterate_to_root():
+ if inheriting is not self.parent \
+ and inheriting.has_property(self.key):
+ util.warn("Warning: relationship '%s' on mapper "
+ "'%s' supersedes the same relationship "
+ "on inherited mapper '%s'; this can "
+ "cause dependency issues during flush"
+ % (self.key, self.parent, inheriting))
+
+ def _get_cascade(self):
+ """Return the current cascade setting for this
+ :class:`.RelationshipProperty`.
+ """
+ return self._cascade
+
+ def _set_cascade(self, cascade):
+ cascade = CascadeOptions(cascade)
+ if 'mapper' in self.__dict__:
+ self._check_cascade_settings(cascade)
+ self._cascade = cascade
+
+ if self._dependency_processor:
+ self._dependency_processor.cascade = cascade
+
+ cascade = property(_get_cascade, _set_cascade)
+
+ def _check_cascade_settings(self, cascade):
+ if cascade.delete_orphan and not self.single_parent \
+ and (self.direction is MANYTOMANY or self.direction
+ is MANYTOONE):
+ raise sa_exc.ArgumentError(
+ 'On %s, delete-orphan cascade is not supported '
+ 'on a many-to-many or many-to-one relationship '
+ 'when single_parent is not set. Set '
+ 'single_parent=True on the relationship().'
+ % self)
+ if self.direction is MANYTOONE and self.passive_deletes:
+ util.warn("On %s, 'passive_deletes' is normally configured "
+ "on one-to-many, one-to-one, many-to-many "
+ "relationships only."
+ % self)
+
+ if self.passive_deletes == 'all' and \
+ ("delete" in cascade or
+ "delete-orphan" in cascade):
+ raise sa_exc.ArgumentError(
+ "On %s, can't set passive_deletes='all' in conjunction "
+ "with 'delete' or 'delete-orphan' cascade" % self)
+
+ if cascade.delete_orphan:
+ self.mapper.primary_mapper()._delete_orphans.append(
+ (self.key, self.parent.class_)
+ )
+
+ def _columns_are_mapped(self, *cols):
+ """Return True if all columns in the given collection are
+ mapped by the tables referenced by this :class:`.Relationship`.
+
+ """
+ for c in cols:
+ if self.secondary is not None \
+ and self.secondary.c.contains_column(c):
+ continue
+ if not self.parent.mapped_table.c.contains_column(c) and \
+ not self.target.c.contains_column(c):
+ return False
+ return True
+
+ def _generate_backref(self):
+ """Interpret the 'backref' instruction to create a
+ :func:`.relationship` complementary to this one."""
+
+ if not self.is_primary():
+ return
+ if self.backref is not None and not self.back_populates:
+ if isinstance(self.backref, str):
+ backref_key, kwargs = self.backref, {}
+ else:
+ backref_key, kwargs = self.backref
+ mapper = self.mapper.primary_mapper()
+
+ check = set(mapper.iterate_to_root()).\
+ union(mapper.self_and_descendants)
+ for m in check:
+ if m.has_property(backref_key):
+ raise sa_exc.ArgumentError("Error creating backref "
+ "'%s' on relationship '%s': property of that "
+ "name exists on mapper '%s'" % (backref_key,
+ self, m))
+
+ # determine primaryjoin/secondaryjoin for the
+ # backref. Use the one we had, so that
+ # a custom join doesn't have to be specified in
+ # both directions.
+ if self.secondary is not None:
+ # for many to many, just switch primaryjoin/
+ # secondaryjoin. use the annotated
+ # pj/sj on the _join_condition.
+ pj = kwargs.pop('primaryjoin',
+ self._join_condition.secondaryjoin_minus_local)
+ sj = kwargs.pop('secondaryjoin',
+ self._join_condition.primaryjoin_minus_local)
+ else:
+ pj = kwargs.pop('primaryjoin',
+ self._join_condition.primaryjoin_reverse_remote)
+ sj = kwargs.pop('secondaryjoin', None)
+ if sj:
+ raise sa_exc.InvalidRequestError(
+ "Can't assign 'secondaryjoin' on a backref "
+ "against a non-secondary relationship."
+ )
+
+ foreign_keys = kwargs.pop('foreign_keys',
+ self._user_defined_foreign_keys)
+ parent = self.parent.primary_mapper()
+ kwargs.setdefault('viewonly', self.viewonly)
+ kwargs.setdefault('post_update', self.post_update)
+ kwargs.setdefault('passive_updates', self.passive_updates)
+ self.back_populates = backref_key
+ relationship = RelationshipProperty(
+ parent, self.secondary,
+ pj, sj,
+ foreign_keys=foreign_keys,
+ back_populates=self.key,
+ **kwargs)
+ mapper._configure_property(backref_key, relationship)
+
+ if self.back_populates:
+ self._add_reverse_property(self.back_populates)
+
+ def _post_init(self):
+ if self.uselist is None:
+ self.uselist = self.direction is not MANYTOONE
+ if not self.viewonly:
+ self._dependency_processor = \
+ dependency.DependencyProcessor.from_relationship(self)
+
+ @util.memoized_property
+ def _use_get(self):
+ """memoize the 'use_get' attribute of this RelationshipLoader's
+ lazyloader."""
+
+ strategy = self._get_strategy(self._lazy_strategy)
+ return strategy.use_get
+
+ @util.memoized_property
+ def _is_self_referential(self):
+ return self.mapper.common_parent(self.parent)
+
+ def _create_joins(self, source_polymorphic=False,
+ source_selectable=None, dest_polymorphic=False,
+ dest_selectable=None, of_type=None):
+ if source_selectable is None:
+ if source_polymorphic and self.parent.with_polymorphic:
+ source_selectable = self.parent._with_polymorphic_selectable
+
+ aliased = False
+ if dest_selectable is None:
+ if dest_polymorphic and self.mapper.with_polymorphic:
+ dest_selectable = self.mapper._with_polymorphic_selectable
+ aliased = True
+ else:
+ dest_selectable = self.mapper.mapped_table
+
+ if self._is_self_referential and source_selectable is None:
+ dest_selectable = dest_selectable.alias()
+ aliased = True
+ else:
+ aliased = True
+
+ dest_mapper = of_type or self.mapper
+
+ single_crit = dest_mapper._single_table_criterion
+ aliased = aliased or (source_selectable is not None)
+
+ primaryjoin, secondaryjoin, secondary, target_adapter, dest_selectable = \
+ self._join_condition.join_targets(
+ source_selectable, dest_selectable, aliased, single_crit
+ )
+ if source_selectable is None:
+ source_selectable = self.parent.local_table
+ if dest_selectable is None:
+ dest_selectable = self.mapper.local_table
+ return (primaryjoin, secondaryjoin, source_selectable,
+ dest_selectable, secondary, target_adapter)
+
def _annotate_columns(element, annotations):
def clone(elem):
if isinstance(elem, expression.ColumnClause):
@@ -901,4 +2483,4 @@ class _ColInAnnotations(object):
self.name = name
def __call__(self, c):
- return self.name in c._annotations \ No newline at end of file
+ return self.name in c._annotations
diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py
index e5742f018..857ef0666 100644
--- a/lib/sqlalchemy/orm/session.py
+++ b/lib/sqlalchemy/orm/session.py
@@ -11,22 +11,38 @@ import weakref
from .. import util, sql, engine, exc as sa_exc, event
from ..sql import util as sql_util, expression
from . import (
- SessionExtension, attributes, exc, query, util as orm_util,
+ SessionExtension, attributes, exc, query,
loading, identity
)
-from .util import (
+from ..inspection import inspect
+from .base import (
object_mapper, class_mapper,
_class_to_mapper, _state_mapper, object_state,
- _none_set
+ _none_set, state_str, instance_str
)
from .unitofwork import UOWTransaction
-from .mapper import Mapper
-from .events import SessionEvents
-statelib = util.importlater("sqlalchemy.orm", "state")
+#from .events import SessionEvents
+from . import state as statelib
import sys
__all__ = ['Session', 'SessionTransaction', 'SessionExtension', 'sessionmaker']
+_sessions = weakref.WeakValueDictionary()
+"""Weak-referencing dictionary of :class:`.Session` objects.
+"""
+
+def _state_session(state):
+ """Given an :class:`.InstanceState`, return the :class:`.Session`
+ associated, if any.
+ """
+ if state.session_id:
+ try:
+ return _sessions[state.session_id]
+ except KeyError:
+ pass
+ return None
+
+
class _SessionClassMethods(object):
"""Class-level methods for :class:`.Session`, :class:`.sessionmaker`."""
@@ -39,7 +55,8 @@ class _SessionClassMethods(object):
sess.close()
@classmethod
- def identity_key(cls, *args, **kwargs):
+ @util.dependencies("sqlalchemy.orm.util")
+ def identity_key(cls, orm_util, *args, **kwargs):
"""Return an identity key.
This is an alias of :func:`.util.identity_key`.
@@ -617,17 +634,19 @@ class Session(_SessionClassMethods):
if binds is not None:
for mapperortable, bind in binds.items():
- if isinstance(mapperortable, (type, Mapper)):
+ insp = inspect(mapperortable)
+ if insp.is_selectable:
+ self.bind_table(mapperortable, bind)
+ elif insp.is_mapper:
self.bind_mapper(mapperortable, bind)
else:
- self.bind_table(mapperortable, bind)
+ assert False
+
if not self.autocommit:
self.begin()
_sessions[self.hash_key] = self
- dispatch = event.dispatcher(SessionEvents)
-
connection_callable = None
transaction = None
@@ -1206,7 +1225,7 @@ class Session(_SessionClassMethods):
only_load_props=attribute_names) is None:
raise sa_exc.InvalidRequestError(
"Could not refresh instance '%s'" %
- orm_util.instance_str(instance))
+ instance_str(instance))
def expire_all(self):
"""Expires all persistent instances within this Session.
@@ -1317,7 +1336,7 @@ class Session(_SessionClassMethods):
if state.session_id is not self.hash_key:
raise sa_exc.InvalidRequestError(
"Instance %s is not present in this Session" %
- orm_util.state_str(state))
+ state_str(state))
cascaded = list(state.manager.mapper.cascade_iterator(
'expunge', state))
@@ -1357,7 +1376,7 @@ class Session(_SessionClassMethods):
"expect these generated values. Ensure also that "
"this flush() is not occurring at an inappropriate "
"time, such aswithin a load() event."
- % orm_util.state_str(state)
+ % state_str(state)
)
if state.key is None:
@@ -1460,7 +1479,7 @@ class Session(_SessionClassMethods):
if state.key is None:
raise sa_exc.InvalidRequestError(
"Instance '%s' is not persisted" %
- orm_util.state_str(state))
+ state_str(state))
if state in self._deleted:
return
@@ -1624,7 +1643,7 @@ class Session(_SessionClassMethods):
"merging to update the most recent version."
% (
existing_version,
- orm_util.state_str(merged_state),
+ state_str(merged_state),
merged_version
))
@@ -1648,13 +1667,13 @@ class Session(_SessionClassMethods):
if not self.identity_map.contains_state(state):
raise sa_exc.InvalidRequestError(
"Instance '%s' is not persistent within this Session" %
- orm_util.state_str(state))
+ state_str(state))
def _save_impl(self, state):
if state.key is not None:
raise sa_exc.InvalidRequestError(
"Object '%s' already has an identity - it can't be registered "
- "as pending" % orm_util.state_str(state))
+ "as pending" % state_str(state))
self._before_attach(state)
if state not in self._new:
@@ -1670,13 +1689,13 @@ class Session(_SessionClassMethods):
if state.key is None:
raise sa_exc.InvalidRequestError(
"Instance '%s' is not persisted" %
- orm_util.state_str(state))
+ state_str(state))
if state.deleted:
raise sa_exc.InvalidRequestError(
"Instance '%s' has been deleted. Use the make_transient() "
"function to send this object back to the transient state." %
- orm_util.state_str(state)
+ state_str(state)
)
self._before_attach(state)
self._deleted.pop(state, None)
@@ -1764,14 +1783,14 @@ class Session(_SessionClassMethods):
raise sa_exc.InvalidRequestError("Can't attach instance "
"%s; another instance with key %s is already "
"present in this session."
- % (orm_util.state_str(state), state.key))
+ % (state_str(state), state.key))
if state.session_id and \
state.session_id is not self.hash_key and \
state.session_id in _sessions:
raise sa_exc.InvalidRequestError(
"Object '%s' is already attached to session '%s' "
- "(this is '%s')" % (orm_util.state_str(state),
+ "(this is '%s')" % (state_str(state),
state.session_id, self.hash_key))
if state.session_id != self.hash_key:
@@ -2299,7 +2318,6 @@ class sessionmaker(_SessionClassMethods):
", ".join("%s=%r" % (k, v) for k, v in self.kw.items())
)
-_sessions = weakref.WeakValueDictionary()
def make_transient(instance):
@@ -2344,12 +2362,4 @@ def object_session(instance):
raise exc.UnmappedInstanceError(instance)
-def _state_session(state):
- if state.session_id:
- try:
- return _sessions[state.session_id]
- except KeyError:
- pass
- return None
-
_new_sessionid = util.counter()
diff --git a/lib/sqlalchemy/orm/state.py b/lib/sqlalchemy/orm/state.py
index c479d880d..7805a47e4 100644
--- a/lib/sqlalchemy/orm/state.py
+++ b/lib/sqlalchemy/orm/state.py
@@ -13,16 +13,11 @@ defines a large part of the ORM's interactivity.
import weakref
from .. import util
-from . import exc as orm_exc, attributes, util as orm_util, interfaces
-from .attributes import (
- PASSIVE_NO_RESULT,
- SQL_OK, NEVER_SET, ATTR_WAS_SET, NO_VALUE,\
- PASSIVE_NO_INITIALIZE
- )
-sessionlib = util.importlater("sqlalchemy.orm", "session")
-instrumentation = util.importlater("sqlalchemy.orm", "instrumentation")
-mapperlib = util.importlater("sqlalchemy.orm", "mapperlib")
-
+from . import exc as orm_exc, interfaces
+from .path_registry import PathRegistry
+from .base import PASSIVE_NO_RESULT, SQL_OK, NEVER_SET, ATTR_WAS_SET, \
+ NO_VALUE, PASSIVE_NO_INITIALIZE
+from . import base
class InstanceState(interfaces._InspectionAttr):
"""tracks state information at the instance level."""
@@ -89,15 +84,16 @@ class InstanceState(interfaces._InspectionAttr):
not self._attached
@property
- def _attached(self):
+ @util.dependencies("sqlalchemy.orm.session")
+ def _attached(self, sessionlib):
return self.session_id is not None and \
self.session_id in sessionlib._sessions
@property
- def session(self):
+ @util.dependencies("sqlalchemy.orm.session")
+ def session(self, sessionlib):
"""Return the owning :class:`.Session` for this instance,
or ``None`` if none available."""
-
return sessionlib._state_session(self)
@property
@@ -186,7 +182,7 @@ class InstanceState(interfaces._InspectionAttr):
def dict(self):
o = self.obj()
if o is not None:
- return attributes.instance_dict(o)
+ return base.instance_dict(o)
else:
return {}
@@ -214,8 +210,8 @@ class InstanceState(interfaces._InspectionAttr):
return self._pending_mutations[key]
def __getstate__(self):
- d = {'instance': self.obj()}
- d.update(
+ state_dict = {'instance': self.obj()}
+ state_dict.update(
(k, self.__dict__[k]) for k in (
'committed_state', '_pending_mutations', 'modified', 'expired',
'callables', 'key', 'parents', 'load_options',
@@ -223,14 +219,14 @@ class InstanceState(interfaces._InspectionAttr):
) if k in self.__dict__
)
if self.load_path:
- d['load_path'] = self.load_path.serialize()
+ state_dict['load_path'] = self.load_path.serialize()
- self.manager.dispatch.pickle(self, d)
+ state_dict['manager'] = self.manager._serialize(self, state_dict)
- return d
+ return state_dict
- def __setstate__(self, state):
- inst = state['instance']
+ def __setstate__(self, state_dict):
+ inst = state_dict['instance']
if inst is not None:
self.obj = weakref.ref(inst, self._cleanup)
self.class_ = inst.__class__
@@ -239,42 +235,26 @@ class InstanceState(interfaces._InspectionAttr):
# due to storage of state in "parents". "class_"
# also new.
self.obj = None
- self.class_ = state['class_']
- self.manager = manager = instrumentation.manager_of_class(self.class_)
- if manager is None:
- raise orm_exc.UnmappedInstanceError(
- inst,
- "Cannot deserialize object of type %r - "
- "no mapper() has "
- "been configured for this class within the current "
- "Python process!" %
- self.class_)
- elif manager.is_mapped and not manager.mapper.configured:
- mapperlib.configure_mappers()
-
- self.committed_state = state.get('committed_state', {})
- self._pending_mutations = state.get('_pending_mutations', {})
- self.parents = state.get('parents', {})
- self.modified = state.get('modified', False)
- self.expired = state.get('expired', False)
- self.callables = state.get('callables', {})
+ self.class_ = state_dict['class_']
+
+ self.committed_state = state_dict.get('committed_state', {})
+ self._pending_mutations = state_dict.get('_pending_mutations', {})
+ self.parents = state_dict.get('parents', {})
+ self.modified = state_dict.get('modified', False)
+ self.expired = state_dict.get('expired', False)
+ self.callables = state_dict.get('callables', {})
self.__dict__.update([
- (k, state[k]) for k in (
+ (k, state_dict[k]) for k in (
'key', 'load_options',
- ) if k in state
+ ) if k in state_dict
])
- if 'load_path' in state:
- self.load_path = orm_util.PathRegistry.\
- deserialize(state['load_path'])
+ if 'load_path' in state_dict:
+ self.load_path = PathRegistry.\
+ deserialize(state_dict['load_path'])
- # setup _sa_instance_state ahead of time so that
- # unpickle events can access the object normally.
- # see [ticket:2362]
- if inst is not None:
- manager.setup_instance(inst, self)
- manager.dispatch.unpickle(self, state)
+ state_dict['manager'](self, inst, state_dict)
def _initialize(self, key):
"""Set this attribute to an empty value or collection,
@@ -461,7 +441,7 @@ class InstanceState(interfaces._InspectionAttr):
"collected."
% (
self.manager[attr.key],
- orm_util.state_class_str(self)
+ base.state_class_str(self)
))
self.modified = True
diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py
index 39ddaa7b8..fb63de807 100644
--- a/lib/sqlalchemy/orm/strategies.py
+++ b/lib/sqlalchemy/orm/strategies.py
@@ -16,6 +16,7 @@ from . import (
)
from .state import InstanceState
from .util import _none_set
+from . import properties
from .interfaces import (
LoaderStrategy, StrategizedOption, MapperOption, PropertyOption,
StrategizedProperty
@@ -23,7 +24,6 @@ from .interfaces import (
from .session import _state_session
import itertools
-
def _register_attribute(strategy, mapper, useobject,
compare_function=None,
typecallable=None,
@@ -88,7 +88,7 @@ def _register_attribute(strategy, mapper, useobject,
for hook in listen_hooks:
hook(desc, prop)
-
+@properties.ColumnProperty._strategy_for(dict(instrument=False, deferred=False))
class UninstrumentedColumnLoader(LoaderStrategy):
"""Represent the a non-instrumented MapperProperty.
@@ -112,6 +112,7 @@ class UninstrumentedColumnLoader(LoaderStrategy):
@log.class_logger
+@properties.ColumnProperty._strategy_for(dict(instrument=True, deferred=False))
class ColumnLoader(LoaderStrategy):
"""Provide loading behavior for a :class:`.ColumnProperty`."""
@@ -159,6 +160,7 @@ class ColumnLoader(LoaderStrategy):
@log.class_logger
+@properties.ColumnProperty._strategy_for(dict(deferred=True, instrument=True))
class DeferredColumnLoader(LoaderStrategy):
"""Provide loading behavior for a deferred :class:`.ColumnProperty`."""
@@ -303,6 +305,7 @@ class AbstractRelationshipLoader(LoaderStrategy):
@log.class_logger
+@properties.RelationshipProperty._strategy_for(dict(lazy=None), dict(lazy="noload"))
class NoLoader(AbstractRelationshipLoader):
"""Provide loading behavior for a :class:`.RelationshipProperty`
with "lazy=None".
@@ -326,6 +329,7 @@ class NoLoader(AbstractRelationshipLoader):
@log.class_logger
+@properties.RelationshipProperty._strategy_for(dict(lazy=True), dict(lazy="select"))
class LazyLoader(AbstractRelationshipLoader):
"""Provide loading behavior for a :class:`.RelationshipProperty`
with "lazy=True", that is loads when first accessed.
@@ -643,6 +647,7 @@ class LoadLazyAttribute(object):
return strategy._load_for_state(state, passive)
+@properties.RelationshipProperty._strategy_for(dict(lazy="immediate"))
class ImmediateLoader(AbstractRelationshipLoader):
def init_class_attribute(self, mapper):
self.parent_property.\
@@ -663,6 +668,7 @@ class ImmediateLoader(AbstractRelationshipLoader):
@log.class_logger
+@properties.RelationshipProperty._strategy_for(dict(lazy="subquery"))
class SubqueryLoader(AbstractRelationshipLoader):
def __init__(self, parent):
super(SubqueryLoader, self).__init__(parent)
@@ -982,6 +988,7 @@ class SubqueryLoader(AbstractRelationshipLoader):
@log.class_logger
+@properties.RelationshipProperty._strategy_for(dict(lazy=False), dict(lazy="joined"))
class JoinedLoader(AbstractRelationshipLoader):
"""Provide loading behavior for a :class:`.RelationshipProperty`
using joined eager loading.
@@ -1346,6 +1353,7 @@ class EagerLazyOption(StrategizedOption):
self.lazy = lazy
self.chained = chained
self.propagate_to_loaders = propagate_to_loaders
+ #self.strategy_cls = properties.RelationshipProperty._strategy_lookup(lazy=lazy)
self.strategy_cls = factory(lazy)
def get_strategy_class(self):
diff --git a/lib/sqlalchemy/orm/unitofwork.py b/lib/sqlalchemy/orm/unitofwork.py
index aa5f7836c..2fc1c3b22 100644
--- a/lib/sqlalchemy/orm/unitofwork.py
+++ b/lib/sqlalchemy/orm/unitofwork.py
@@ -16,8 +16,6 @@ from .. import util, event
from ..util import topological
from . import attributes, persistence, util as orm_util
-sessionlib = util.importlater("sqlalchemy.orm", "session")
-
def track_cascade_events(descriptor, prop):
"""Establish event listeners on object attributes which handle
@@ -33,7 +31,7 @@ def track_cascade_events(descriptor, prop):
if item is None:
return
- sess = sessionlib._state_session(state)
+ sess = state.session
if sess:
if sess._warn_on_events:
sess._flush_warning("collection append")
@@ -50,7 +48,7 @@ def track_cascade_events(descriptor, prop):
if item is None:
return
- sess = sessionlib._state_session(state)
+ sess = state.session
if sess:
prop = state.manager.mapper._props[key]
@@ -74,7 +72,7 @@ def track_cascade_events(descriptor, prop):
if oldvalue is newvalue:
return newvalue
- sess = sessionlib._state_session(state)
+ sess = state.session
if sess:
if sess._warn_on_events:
diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py
index ae1ca2013..d10a78dd7 100644
--- a/lib/sqlalchemy/orm/util.py
+++ b/lib/sqlalchemy/orm/util.py
@@ -7,21 +7,19 @@
from .. import sql, util, event, exc as sa_exc, inspection
from ..sql import expression, util as sql_util, operators
-from .interfaces import PropComparator, MapperProperty, _InspectionAttr
-from itertools import chain
-from . import attributes, exc
+from .interfaces import PropComparator, MapperProperty
+from . import attributes
import re
-mapperlib = util.importlater("sqlalchemy.orm", "mapperlib")
+from .base import instance_str, state_str, state_class_str, attribute_str, \
+ state_attribute_str, object_mapper, object_state, _none_set
+from .base import class_mapper, _class_to_mapper
+from .base import _InspectionAttr
all_cascades = frozenset(("delete", "delete-orphan", "all", "merge",
"expunge", "save-update", "refresh-expire",
"none"))
-_INSTRUMENTOR = ('mapper', 'instrumentor')
-
-_none_set = frozenset([None])
-
class CascadeOptions(frozenset):
"""Keeps track of the options sent to relationship().cascade"""
@@ -245,211 +243,7 @@ class ORMAdapter(sql_util.ColumnAdapter):
else:
return None
-def _unreduce_path(path):
- return PathRegistry.deserialize(path)
-
-class PathRegistry(object):
- """Represent query load paths and registry functions.
-
- Basically represents structures like:
-
- (<User mapper>, "orders", <Order mapper>, "items", <Item mapper>)
-
- These structures are generated by things like
- query options (joinedload(), subqueryload(), etc.) and are
- used to compose keys stored in the query._attributes dictionary
- for various options.
-
- They are then re-composed at query compile/result row time as
- the query is formed and as rows are fetched, where they again
- serve to compose keys to look up options in the context.attributes
- dictionary, which is copied from query._attributes.
-
- The path structure has a limited amount of caching, where each
- "root" ultimately pulls from a fixed registry associated with
- the first mapper, that also contains elements for each of its
- property keys. However paths longer than two elements, which
- are the exception rather than the rule, are generated on an
- as-needed basis.
-
- """
-
- def __eq__(self, other):
- return other is not None and \
- self.path == other.path
-
- def set(self, attributes, key, value):
- attributes[(key, self.path)] = value
-
- def setdefault(self, attributes, key, value):
- attributes.setdefault((key, self.path), value)
-
- def get(self, attributes, key, value=None):
- key = (key, self.path)
- if key in attributes:
- return attributes[key]
- else:
- return value
-
- def __len__(self):
- return len(self.path)
-
- @property
- def length(self):
- return len(self.path)
-
- def pairs(self):
- path = self.path
- for i in range(0, len(path), 2):
- yield path[i], path[i + 1]
-
- def contains_mapper(self, mapper):
- for path_mapper in [
- self.path[i] for i in range(0, len(self.path), 2)
- ]:
- if isinstance(path_mapper, mapperlib.Mapper) and \
- path_mapper.isa(mapper):
- return True
- else:
- return False
-
- def contains(self, attributes, key):
- return (key, self.path) in attributes
-
- def __reduce__(self):
- return _unreduce_path, (self.serialize(), )
-
- def serialize(self):
- path = self.path
- return list(zip(
- [m.class_ for m in [path[i] for i in range(0, len(path), 2)]],
- [path[i].key for i in range(1, len(path), 2)] + [None]
- ))
-
- @classmethod
- def deserialize(cls, path):
- if path is None:
- return None
-
- p = tuple(chain(*[(class_mapper(mcls),
- class_mapper(mcls).attrs[key]
- if key is not None else None)
- for mcls, key in path]))
- if p and p[-1] is None:
- p = p[0:-1]
- return cls.coerce(p)
-
- @classmethod
- def per_mapper(cls, mapper):
- return EntityRegistry(
- cls.root, mapper
- )
-
- @classmethod
- def coerce(cls, raw):
- return util.reduce(lambda prev, next: prev[next], raw, cls.root)
-
- @classmethod
- def token(cls, token):
- return TokenRegistry(cls.root, token)
-
- def __add__(self, other):
- return util.reduce(
- lambda prev, next: prev[next],
- other.path, self)
-
- def __repr__(self):
- return "%s(%r)" % (self.__class__.__name__, self.path, )
-
-
-class RootRegistry(PathRegistry):
- """Root registry, defers to mappers so that
- paths are maintained per-root-mapper.
-
- """
- path = ()
-
- def __getitem__(self, entity):
- return entity._path_registry
-PathRegistry.root = RootRegistry()
-
-class TokenRegistry(PathRegistry):
- def __init__(self, parent, token):
- self.token = token
- self.parent = parent
- self.path = parent.path + (token,)
-
- def __getitem__(self, entity):
- raise NotImplementedError()
-
-class PropRegistry(PathRegistry):
- def __init__(self, parent, prop):
- # restate this path in terms of the
- # given MapperProperty's parent.
- insp = inspection.inspect(parent[-1])
- if not insp.is_aliased_class or insp._use_mapper_path:
- parent = parent.parent[prop.parent]
- elif insp.is_aliased_class and insp.with_polymorphic_mappers:
- if prop.parent is not insp.mapper and \
- prop.parent in insp.with_polymorphic_mappers:
- subclass_entity = parent[-1]._entity_for_mapper(prop.parent)
- parent = parent.parent[subclass_entity]
-
- self.prop = prop
- self.parent = parent
- self.path = parent.path + (prop,)
-
- def __getitem__(self, entity):
- if isinstance(entity, (int, slice)):
- return self.path[entity]
- else:
- return EntityRegistry(
- self, entity
- )
-
-
-class EntityRegistry(PathRegistry, dict):
- is_aliased_class = False
-
- def __init__(self, parent, entity):
- self.key = entity
- self.parent = parent
- self.is_aliased_class = entity.is_aliased_class
-
- self.path = parent.path + (entity,)
-
- def __bool__(self):
- return True
- __nonzero__ = __bool__
-
- def __getitem__(self, entity):
- if isinstance(entity, (int, slice)):
- return self.path[entity]
- else:
- return dict.__getitem__(self, entity)
-
- def _inlined_get_for(self, prop, context, key):
- """an inlined version of:
-
- cls = path[mapperproperty].get(context, key)
-
- Skips the isinstance() check in __getitem__
- and the extra method call for get().
- Used by StrategizedProperty for its
- very frequent lookup.
-
- """
- path = dict.__getitem__(self, prop)
- path_key = (key, path.path)
- if path_key in context.attributes:
- return context.attributes[path_key]
- else:
- return None
-
- def __missing__(self, key):
- self[key] = item = PropRegistry(self, key)
- return item
-
+from .path_registry import _unreduce_path, PathRegistry, RootRegistry, TokenRegistry, PropRegistry, EntityRegistry
class AliasedClass(object):
"""Represents an "aliased" form of a mapped class for usage with Query.
@@ -1053,186 +847,6 @@ def with_parent(instance, prop):
value_is_parent=True)
-def _attr_as_key(attr):
- if hasattr(attr, 'key'):
- return attr.key
- else:
- return expression._column_as_key(attr)
-
-
-_state_mapper = util.dottedgetter('manager.mapper')
-
-
-@inspection._inspects(object)
-def _inspect_mapped_object(instance):
- try:
- return attributes.instance_state(instance)
- # TODO: whats the py-2/3 syntax to catch two
- # different kinds of exceptions at once ?
- except exc.UnmappedClassError:
- return None
- except exc.NO_STATE:
- return None
-
-
-@inspection._inspects(type)
-def _inspect_mapped_class(class_, configure=False):
- try:
- class_manager = attributes.manager_of_class(class_)
- if not class_manager.is_mapped:
- return None
- mapper = class_manager.mapper
- if configure and mapperlib.module._new_mappers:
- mapperlib.configure_mappers()
- return mapper
-
- except exc.NO_STATE:
- return None
-
-
-def object_mapper(instance):
- """Given an object, return the primary Mapper associated with the object
- instance.
-
- Raises :class:`sqlalchemy.orm.exc.UnmappedInstanceError`
- if no mapping is configured.
-
- This function is available via the inspection system as::
-
- inspect(instance).mapper
-
- Using the inspection system will raise
- :class:`sqlalchemy.exc.NoInspectionAvailable` if the instance is
- not part of a mapping.
-
- """
- return object_state(instance).mapper
-
-
-def object_state(instance):
- """Given an object, return the :class:`.InstanceState`
- associated with the object.
-
- Raises :class:`sqlalchemy.orm.exc.UnmappedInstanceError`
- if no mapping is configured.
-
- Equivalent functionality is available via the :func:`.inspect`
- function as::
-
- inspect(instance)
-
- Using the inspection system will raise
- :class:`sqlalchemy.exc.NoInspectionAvailable` if the instance is
- not part of a mapping.
-
- """
- state = _inspect_mapped_object(instance)
- if state is None:
- raise exc.UnmappedInstanceError(instance)
- else:
- return state
-
-
-def class_mapper(class_, configure=True):
- """Given a class, return the primary :class:`.Mapper` associated
- with the key.
-
- Raises :class:`.UnmappedClassError` if no mapping is configured
- on the given class, or :class:`.ArgumentError` if a non-class
- object is passed.
-
- Equivalent functionality is available via the :func:`.inspect`
- function as::
-
- inspect(some_mapped_class)
-
- Using the inspection system will raise
- :class:`sqlalchemy.exc.NoInspectionAvailable` if the class is not mapped.
-
- """
- mapper = _inspect_mapped_class(class_, configure=configure)
- if mapper is None:
- if not isinstance(class_, type):
- raise sa_exc.ArgumentError(
- "Class object expected, got '%r'." % class_)
- raise exc.UnmappedClassError(class_)
- else:
- return mapper
-
-
-def _class_to_mapper(class_or_mapper):
- insp = inspection.inspect(class_or_mapper, False)
- if insp is not None:
- return insp.mapper
- else:
- raise exc.UnmappedClassError(class_or_mapper)
-
-
-def _mapper_or_none(entity):
- """Return the :class:`.Mapper` for the given class or None if the
- class is not mapped."""
-
- insp = inspection.inspect(entity, False)
- if insp is not None:
- return insp.mapper
- else:
- return None
-
-
-def _is_mapped_class(entity):
- """Return True if the given object is a mapped class,
- :class:`.Mapper`, or :class:`.AliasedClass`."""
-
- insp = inspection.inspect(entity, False)
- return insp is not None and \
- hasattr(insp, "mapper") and \
- (
- insp.is_mapper
- or insp.is_aliased_class
- )
-
-
-def _is_aliased_class(entity):
- insp = inspection.inspect(entity, False)
- return insp is not None and \
- getattr(insp, "is_aliased_class", False)
-
-
-def _entity_descriptor(entity, key):
- """Return a class attribute given an entity and string name.
-
- May return :class:`.InstrumentedAttribute` or user-defined
- attribute.
-
- """
- insp = inspection.inspect(entity)
- if insp.is_selectable:
- description = entity
- entity = insp.c
- elif insp.is_aliased_class:
- entity = insp.entity
- description = entity
- elif hasattr(insp, "mapper"):
- description = entity = insp.mapper.class_
- else:
- description = entity
-
- try:
- return getattr(entity, key)
- except AttributeError:
- raise sa_exc.InvalidRequestError(
- "Entity '%s' has no property '%s'" %
- (description, key)
- )
-
-
-def _orm_columns(entity):
- insp = inspection.inspect(entity, False)
- if hasattr(insp, 'selectable'):
- return [c for c in insp.selectable.c]
- else:
- return [entity]
-
def has_identity(object):
"""Return True if the given object has a database
@@ -1260,36 +874,7 @@ def was_deleted(object):
state = attributes.instance_state(object)
return state.deleted
-def instance_str(instance):
- """Return a string describing an instance."""
-
- return state_str(attributes.instance_state(instance))
-
-
-def state_str(state):
- """Return a string describing an instance via its InstanceState."""
-
- if state is None:
- return "None"
- else:
- return '<%s at 0x%x>' % (state.class_.__name__, id(state.obj()))
-
-
-def state_class_str(state):
- """Return a string describing an instance's class via its InstanceState."""
-
- if state is None:
- return "None"
- else:
- return '<%s>' % (state.class_.__name__, )
-
-
-def attribute_str(instance, attribute):
- return instance_str(instance) + "." + attribute
-
-def state_attribute_str(state, attribute):
- return state_str(state) + "." + attribute
def randomize_unitofwork():
diff --git a/lib/sqlalchemy/pool.py b/lib/sqlalchemy/pool.py
index f2d6ca6ea..88d17a997 100644
--- a/lib/sqlalchemy/pool.py
+++ b/lib/sqlalchemy/pool.py
@@ -20,7 +20,7 @@ import time
import traceback
import weakref
-from . import exc, log, event, events, interfaces, util
+from . import exc, log, event, interfaces, util
from .util import queue as sqla_queue
from .util import threading, memoized_property, \
chop_traceback
@@ -185,8 +185,6 @@ class Pool(log.Identified):
for l in listeners:
self.add_listener(l)
- dispatch = event.dispatcher(events.PoolEvents)
-
def _close_connection(self, connection):
self.logger.debug("Closing connection %r", connection)
try:
diff --git a/lib/sqlalchemy/sql/__init__.py b/lib/sqlalchemy/sql/__init__.py
index 3503f8c97..e1497e9fa 100644
--- a/lib/sqlalchemy/sql/__init__.py
+++ b/lib/sqlalchemy/sql/__init__.py
@@ -64,10 +64,16 @@ from .expression import (
from .visitors import ClauseVisitor
-__tmp = list(locals().keys())
-__all__ = sorted([i for i in __tmp if not i.startswith('__')])
-def __go():
+def __go(lcls):
+ global __all__
+ from .. import util as _sa_util
+
+ import inspect as _inspect
+
+ __all__ = sorted(name for name, obj in lcls.items()
+ if not (name.startswith('_') or _inspect.ismodule(obj)))
+
from .annotation import _prepare_annotations, Annotated
from .elements import AnnotatedColumnElement, ClauseList
from .selectable import AnnotatedFromClause
@@ -75,6 +81,7 @@ def __go():
_prepare_annotations(FromClause, AnnotatedFromClause)
_prepare_annotations(ClauseList, Annotated)
- from .. import util as _sa_util
- _sa_util.importlater.resolve_all("sqlalchemy.sql")
-__go() \ No newline at end of file
+ _sa_util.dependencies.resolve_all("sqlalchemy.sql")
+
+__go(locals())
+
diff --git a/lib/sqlalchemy/sql/base.py b/lib/sqlalchemy/sql/base.py
index e7d83627d..2a3176d04 100644
--- a/lib/sqlalchemy/sql/base.py
+++ b/lib/sqlalchemy/sql/base.py
@@ -152,6 +152,24 @@ class Executable(Generative):
return None
+class SchemaEventTarget(object):
+ """Base class for elements that are the targets of :class:`.DDLEvents`
+ events.
+
+ This includes :class:`.SchemaItem` as well as :class:`.SchemaType`.
+
+ """
+
+ def _set_parent(self, parent):
+ """Associate with this SchemaEvent's parent object."""
+
+ raise NotImplementedError()
+
+ def _set_parent_with_dispatch(self, parent):
+ self.dispatch.before_parent_attach(self, parent)
+ self._set_parent(parent)
+ self.dispatch.after_parent_attach(self, parent)
+
class SchemaVisitor(ClauseVisitor):
"""Define the visiting for ``SchemaItem`` objects."""
diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py
index 1867df123..17fb40628 100644
--- a/lib/sqlalchemy/sql/elements.py
+++ b/lib/sqlalchemy/sql/elements.py
@@ -9,7 +9,6 @@
"""
-
from __future__ import unicode_literals
from .. import util, exc, inspection
diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py
index ee4d81f6e..bbbe0b235 100644
--- a/lib/sqlalchemy/sql/expression.py
+++ b/lib/sqlalchemy/sql/expression.py
@@ -56,37 +56,37 @@ from .dml import Insert, Update, Delete
# the functions to be available in the sqlalchemy.sql.* namespace and
# to be auto-cross-documenting from the function to the class itself.
-bindparam = public_factory(BindParameter)
-select = public_factory(Select)
-text = public_factory(TextClause)
-table = public_factory(TableClause)
-column = public_factory(ColumnClause)
-over = public_factory(Over)
-label = public_factory(Label)
-case = public_factory(Case)
-cast = public_factory(Cast)
-extract = public_factory(Extract)
-tuple_ = public_factory(Tuple)
-except_ = public_factory(CompoundSelect._create_except)
-except_all = public_factory(CompoundSelect._create_except_all)
-intersect = public_factory(CompoundSelect._create_intersect)
-intersect_all = public_factory(CompoundSelect._create_intersect_all)
-union = public_factory(CompoundSelect._create_union)
-union_all = public_factory(CompoundSelect._create_union_all)
-exists = public_factory(Exists)
-nullsfirst = public_factory(UnaryExpression._create_nullsfirst)
-nullslast = public_factory(UnaryExpression._create_nullslast)
-asc = public_factory(UnaryExpression._create_asc)
-desc = public_factory(UnaryExpression._create_desc)
-distinct = public_factory(UnaryExpression._create_distinct)
-true = public_factory(True_)
-false = public_factory(False_)
-null = public_factory(Null)
-join = public_factory(Join._create_join)
-outerjoin = public_factory(Join._create_outerjoin)
-insert = public_factory(Insert)
-update = public_factory(Update)
-delete = public_factory(Delete)
+bindparam = public_factory(BindParameter, ".expression.bindparam")
+select = public_factory(Select, ".expression.select")
+text = public_factory(TextClause, ".expression.tet")
+table = public_factory(TableClause, ".expression.table")
+column = public_factory(ColumnClause, ".expression.column")
+over = public_factory(Over, ".expression.over")
+label = public_factory(Label, ".expression.label")
+case = public_factory(Case, ".expression.case")
+cast = public_factory(Cast, ".expression.cast")
+extract = public_factory(Extract, ".expression.extract")
+tuple_ = public_factory(Tuple, ".expression.tuple_")
+except_ = public_factory(CompoundSelect._create_except, ".expression.except_")
+except_all = public_factory(CompoundSelect._create_except_all, ".expression.except_all")
+intersect = public_factory(CompoundSelect._create_intersect, ".expression.intersect")
+intersect_all = public_factory(CompoundSelect._create_intersect_all, ".expression.intersect_all")
+union = public_factory(CompoundSelect._create_union, ".expression.union")
+union_all = public_factory(CompoundSelect._create_union_all, ".expression.union_all")
+exists = public_factory(Exists, ".expression.exists")
+nullsfirst = public_factory(UnaryExpression._create_nullsfirst, ".expression.nullsfirst")
+nullslast = public_factory(UnaryExpression._create_nullslast, ".expression.nullslast")
+asc = public_factory(UnaryExpression._create_asc, ".expression.asc")
+desc = public_factory(UnaryExpression._create_desc, ".expression.desc")
+distinct = public_factory(UnaryExpression._create_distinct, ".expression.distinct")
+true = public_factory(True_, ".expression.true")
+false = public_factory(False_, ".expression.false")
+null = public_factory(Null, ".expression.null")
+join = public_factory(Join._create_join, ".expression.join")
+outerjoin = public_factory(Join._create_outerjoin, ".expression.outerjoin")
+insert = public_factory(Insert, ".expression.insert")
+update = public_factory(Update, ".expression.update")
+delete = public_factory(Delete, ".expression.delete")
@@ -101,7 +101,6 @@ from .elements import _literal_as_text, _clause_element_as_expr,\
from .selectable import _interpret_as_from
-
# old names for compatibility
_Executable = Executable
_BindParamClause = BindParameter
diff --git a/lib/sqlalchemy/sql/schema.py b/lib/sqlalchemy/sql/schema.py
index 183b30077..ccab31991 100644
--- a/lib/sqlalchemy/sql/schema.py
+++ b/lib/sqlalchemy/sql/schema.py
@@ -31,7 +31,7 @@ as components in SQL expressions.
import re
import inspect
from .. import exc, util, event, inspection
-from ..events import SchemaEventTarget
+from .base import SchemaEventTarget
from . import visitors
from . import type_api
from .base import _bind_or_error, ColumnCollection
@@ -2509,7 +2509,18 @@ class Index(ColumnCollectionMixin, SchemaItem):
:param name:
The name of the index
+<<<<<<< HEAD
+ :param \*expressions:
+ Column expressions to include in the index. The expressions
+ are normally instances of :class:`.Column`, but may also
+ be arbitrary SQL expressions which ultmately refer to a
+ :class:`.Column`.
+
+ .. versionadded:: 0.8 :class:`.Index` supports SQL expressions as
+ well as plain columns.
+=======
:param \*expressions: Column or SQL expressions.
+>>>>>>> master
:param unique:
Defaults to False: create a unique index.
diff --git a/lib/sqlalchemy/sql/sqltypes.py b/lib/sqlalchemy/sql/sqltypes.py
index c7b210be6..38e0d1bd3 100644
--- a/lib/sqlalchemy/sql/sqltypes.py
+++ b/lib/sqlalchemy/sql/sqltypes.py
@@ -14,9 +14,9 @@ import codecs
from .type_api import TypeEngine, TypeDecorator, to_instance
from .default_comparator import _DefaultColumnComparator
from .. import exc, util, processors
-from .base import _bind_or_error
+from .base import _bind_or_error, SchemaEventTarget
from . import operators
-from .. import events, event
+from .. import event
from ..util import pickle
import decimal
@@ -817,7 +817,7 @@ class Binary(LargeBinary):
-class SchemaType(events.SchemaEventTarget):
+class SchemaType(SchemaEventTarget):
"""Mark a type as possibly requiring schema-level DDL for usage.
Supports types that must be explicitly created/dropped (i.e. PG ENUM type)
diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py
index a9bd22cc6..b927f1b3c 100644
--- a/lib/sqlalchemy/sql/util.py
+++ b/lib/sqlalchemy/sql/util.py
@@ -19,7 +19,9 @@ from .elements import BindParameter, ColumnClause, ColumnElement, \
from .selectable import ScalarSelect, Join, FromClause, FromGrouping
from .schema import Column
-join_condition = util.langhelpers.public_factory(Join._join_condition)
+join_condition = util.langhelpers.public_factory(
+ Join._join_condition,
+ ".sql.util.join_condition")
# names that are still being imported from the outside
from .annotation import _shallow_annotate, _deep_annotate, _deep_deannotate
diff --git a/lib/sqlalchemy/util/__init__.py b/lib/sqlalchemy/util/__init__.py
index 2237e1f13..968421c8b 100644
--- a/lib/sqlalchemy/util/__init__.py
+++ b/lib/sqlalchemy/util/__init__.py
@@ -25,7 +25,7 @@ from .langhelpers import iterate_attributes, class_hierarchy, \
getargspec_init, format_argspec_init, format_argspec_plus, \
get_func_kwargs, get_cls_kwargs, decorator, as_interface, \
memoized_property, memoized_instancemethod, md5_hex, \
- group_expirable_memoized_property, importlater, dependencies, decode_slice, \
+ group_expirable_memoized_property, dependencies, decode_slice, \
monkeypatch_proxied_specials, asbool, bool_or_str, coerce_kw_type,\
duck_type_collection, assert_arg_type, symbol, dictlike_iteritems,\
classproperty, set_creation_order, warn_exception, warn, NoneType,\
diff --git a/lib/sqlalchemy/util/langhelpers.py b/lib/sqlalchemy/util/langhelpers.py
index e10a10e27..1fd5ccf30 100644
--- a/lib/sqlalchemy/util/langhelpers.py
+++ b/lib/sqlalchemy/util/langhelpers.py
@@ -107,7 +107,7 @@ def decorator(target):
return update_wrapper(decorate, target)
-def public_factory(target):
+def public_factory(target, location):
"""Produce a wrapping function for the given cls or classmethod.
Rationale here is so that the __init__ method of the
@@ -117,18 +117,26 @@ def public_factory(target):
if isinstance(target, type):
fn = target.__init__
callable_ = target
+ doc = "Construct a new :class:`.%s` object. \n\n"\
+ "This constructor is mirrored as a public API function; see :func:`~%s` "\
+ "for a full usage and argument description." % (
+ target.__name__, location, )
else:
fn = callable_ = target
+ doc = "This function is mirrored; see :func:`~%s` "\
+ "for a description of arguments." % location
+
spec = compat.inspect_getfullargspec(fn)
del spec[0][0]
- #import pdb
- #pdb.set_trace()
metadata = format_argspec_plus(spec, grouped=False)
code = 'lambda %(args)s: cls(%(apply_kw)s)' % metadata
decorated = eval(code, {'cls': callable_, 'symbol': symbol})
decorated.__doc__ = fn.__doc__
+ if compat.py2k or hasattr(fn, '__func__'):
+ fn.__func__.__doc__ = doc
+ else:
+ fn.__doc__ = doc
return decorated
- #return update_wrapper(decorated, fn)
class PluginLoader(object):
@@ -703,85 +711,19 @@ class group_expirable_memoized_property(object):
return memoized_instancemethod(fn)
-class importlater(object):
- """Deferred import object.
-
- e.g.::
-
- somesubmod = importlater("mypackage.somemodule", "somesubmod")
-
- is equivalent to::
-
- from mypackage.somemodule import somesubmod
-
- except evaluted upon attribute access to "somesubmod".
-
- importlater() currently requires that resolve_all() be
- called, typically at the bottom of a package's __init__.py.
- This is so that __import__ still called only at
- module import time, and not potentially within
- a non-main thread later on.
-
- """
-
- _unresolved = set()
-
- _by_key = {}
- def __new__(cls, path, addtl):
- key = path + "." + addtl
- if key in importlater._by_key:
- return importlater._by_key[key]
- else:
- importlater._by_key[key] = imp = object.__new__(cls)
- return imp
-
- def __init__(self, path, addtl):
- self._il_path = path
- self._il_addtl = addtl
- importlater._unresolved.add(self)
-
- @classmethod
- def resolve_all(cls, path):
- for m in list(importlater._unresolved):
- if m._full_path.startswith(path):
- m._resolve()
+def dependency_for(modulename):
+ def decorate(obj):
+ # TODO: would be nice to improve on this import silliness,
+ # unfortunately importlib doesn't work that great either
+ tokens = modulename.split(".")
+ mod = compat.import_(".".join(tokens[0:-1]), globals(), locals(), tokens[-1])
+ mod = getattr(mod, tokens[-1])
+ setattr(mod, obj.__name__, obj)
+ return obj
+ return decorate
- @property
- def _full_path(self):
- return self._il_path + "." + self._il_addtl
-
- @memoized_property
- def module(self):
- if self in importlater._unresolved:
- raise ImportError(
- "importlater.resolve_all() hasn't "
- "been called (this is %s %s)"
- % (self._il_path, self._il_addtl))
-
- return getattr(self._initial_import, self._il_addtl)
-
- def _resolve(self):
- importlater._unresolved.discard(self)
- self._initial_import = compat.import_(
- self._il_path, globals(), locals(),
- [self._il_addtl])
-
- def __getattr__(self, key):
- if key == 'module':
- raise ImportError("Could not resolve module %s"
- % self._full_path)
- try:
- attr = getattr(self.module, key)
- except AttributeError:
- raise AttributeError(
- "Module %s has no attribute '%s'" %
- (self._full_path, key)
- )
- self.__dict__[key] = attr
- return attr
-
-def dependencies(*deps):
+class dependencies(object):
"""Apply imported dependencies as arguments to a function.
E.g.::
@@ -798,17 +740,20 @@ def dependencies(*deps):
and not pollute the module-level namespace.
"""
- import_deps = []
- for dep in deps:
- tokens = dep.split(".")
- import_deps.append(
- importlater(
- ".".join(tokens[0:-1]),
- tokens[-1]
+
+ def __init__(self, *deps):
+ self.import_deps = []
+ for dep in deps:
+ tokens = dep.split(".")
+ self.import_deps.append(
+ dependencies._importlater(
+ ".".join(tokens[0:-1]),
+ tokens[-1]
+ )
)
- )
- def decorate(fn):
+ def __call__(self, fn):
+ import_deps = self.import_deps
spec = compat.inspect_getfullargspec(fn)
spec_zero = list(spec[0])
@@ -833,7 +778,68 @@ def dependencies(*deps):
decorated = eval(code, locals())
decorated.__defaults__ = getattr(fn, 'im_func', fn).__defaults__
return update_wrapper(decorated, fn)
- return decorate
+
+ @classmethod
+ def resolve_all(cls, path):
+ for m in list(dependencies._unresolved):
+ if m._full_path.startswith(path):
+ m._resolve()
+
+ _unresolved = set()
+ _by_key = {}
+
+ class _importlater(object):
+ _unresolved = set()
+
+ _by_key = {}
+
+ def __new__(cls, path, addtl):
+ key = path + "." + addtl
+ if key in dependencies._by_key:
+ return dependencies._by_key[key]
+ else:
+ dependencies._by_key[key] = imp = object.__new__(cls)
+ return imp
+
+ def __init__(self, path, addtl):
+ self._il_path = path
+ self._il_addtl = addtl
+ dependencies._unresolved.add(self)
+
+
+ @property
+ def _full_path(self):
+ return self._il_path + "." + self._il_addtl
+
+ @memoized_property
+ def module(self):
+ if self in dependencies._unresolved:
+ raise ImportError(
+ "importlater.resolve_all() hasn't "
+ "been called (this is %s %s)"
+ % (self._il_path, self._il_addtl))
+
+ return getattr(self._initial_import, self._il_addtl)
+
+ def _resolve(self):
+ dependencies._unresolved.discard(self)
+ self._initial_import = compat.import_(
+ self._il_path, globals(), locals(),
+ [self._il_addtl])
+
+ def __getattr__(self, key):
+ if key == 'module':
+ raise ImportError("Could not resolve module %s"
+ % self._full_path)
+ try:
+ attr = getattr(self.module, key)
+ except AttributeError:
+ raise AttributeError(
+ "Module %s has no attribute '%s'" %
+ (self._full_path, key)
+ )
+ self.__dict__[key] = attr
+ return attr
# from paste.deploy.converters
diff --git a/test/ext/declarative/test_basic.py b/test/ext/declarative/test_basic.py
index 540f1623f..2de0032dd 100644
--- a/test/ext/declarative/test_basic.py
+++ b/test/ext/declarative/test_basic.py
@@ -202,10 +202,10 @@ class DeclarativeTest(DeclarativeTestBase):
user = relationship("User", primaryjoin=user_id == User.id,
backref="addresses")
- assert mapperlib._new_mappers is True
+ assert mapperlib.Mapper._new_mappers is True
u = User()
assert User.addresses
- assert mapperlib._new_mappers is False
+ assert mapperlib.Mapper._new_mappers is False
def test_string_dependency_resolution(self):
from sqlalchemy.sql import desc
diff --git a/test/orm/inheritance/test_assorted_poly.py b/test/orm/inheritance/test_assorted_poly.py
index d05a22f39..da0e3b1a3 100644
--- a/test/orm/inheritance/test_assorted_poly.py
+++ b/test/orm/inheritance/test_assorted_poly.py
@@ -16,6 +16,7 @@ from test.orm import _fixtures
from sqlalchemy.testing import eq_
from sqlalchemy.testing.schema import Table, Column
+
class AttrSettable(object):
def __init__(self, **kwargs):
[setattr(self, k, v) for k, v in kwargs.items()]
diff --git a/test/orm/test_mapper.py b/test/orm/test_mapper.py
index e742505b0..e073093fa 100644
--- a/test/orm/test_mapper.py
+++ b/test/orm/test_mapper.py
@@ -59,7 +59,7 @@ class MapperTest(_fixtures.FixtureTest, AssertsCompiledSQL):
addresses = self.tables.addresses
Address = self.classes.Address
- from sqlalchemy.orm.util import _is_mapped_class, _is_aliased_class
+ from sqlalchemy.orm.base import _is_mapped_class, _is_aliased_class
class Foo(object):
x = "something"
@@ -96,7 +96,7 @@ class MapperTest(_fixtures.FixtureTest, AssertsCompiledSQL):
def test_entity_descriptor(self):
users = self.tables.users
- from sqlalchemy.orm.util import _entity_descriptor
+ from sqlalchemy.orm.base import _entity_descriptor
class Foo(object):
x = "something"
@@ -196,16 +196,16 @@ class MapperTest(_fixtures.FixtureTest, AssertsCompiledSQL):
mapper(User, users)
sa.orm.configure_mappers()
- assert sa.orm.mapperlib._new_mappers is False
+ assert sa.orm.mapperlib.Mapper._new_mappers is False
m = mapper(Address, addresses, properties={
'user': relationship(User, backref="addresses")})
assert m.configured is False
- assert sa.orm.mapperlib._new_mappers is True
+ assert sa.orm.mapperlib.Mapper._new_mappers is True
u = User()
assert User.addresses
- assert sa.orm.mapperlib._new_mappers is False
+ assert sa.orm.mapperlib.Mapper._new_mappers is False
def test_configure_on_session(self):
User, users = self.classes.User, self.tables.users
@@ -2242,18 +2242,18 @@ class ComparatorFactoryTest(_fixtures.FixtureTest, AssertsCompiledSQL):
self.tables.addresses,
self.classes.User)
- from sqlalchemy.orm.properties import PropertyLoader
+ from sqlalchemy.orm.properties import RelationshipProperty
# NOTE: this API changed in 0.8, previously __clause_element__()
# gave the parent selecatable, now it gives the
# primaryjoin/secondaryjoin
- class MyFactory(PropertyLoader.Comparator):
+ class MyFactory(RelationshipProperty.Comparator):
__hash__ = None
def __eq__(self, other):
return func.foobar(self._source_selectable().c.user_id) == \
func.foobar(other.id)
- class MyFactory2(PropertyLoader.Comparator):
+ class MyFactory2(RelationshipProperty.Comparator):
__hash__ = None
def __eq__(self, other):
return func.foobar(self._source_selectable().c.id) == \