]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- apply an import refactoring to the ORM as well
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 14 Aug 2013 23:58:34 +0000 (19:58 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 14 Aug 2013 23:58:34 +0000 (19:58 -0400)
- 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

46 files changed:
doc/build/changelog/changelog_09.rst
lib/sqlalchemy/__init__.py
lib/sqlalchemy/engine/interfaces.py
lib/sqlalchemy/event/__init__.py
lib/sqlalchemy/event/attr.py
lib/sqlalchemy/event/base.py
lib/sqlalchemy/events.py
lib/sqlalchemy/ext/declarative/api.py
lib/sqlalchemy/ext/declarative/base.py
lib/sqlalchemy/ext/instrumentation.py
lib/sqlalchemy/orm/__init__.py
lib/sqlalchemy/orm/attributes.py
lib/sqlalchemy/orm/base.py [new file with mode: 0644]
lib/sqlalchemy/orm/collections.py
lib/sqlalchemy/orm/deprecated_interfaces.py
lib/sqlalchemy/orm/descriptor_props.py
lib/sqlalchemy/orm/dynamic.py
lib/sqlalchemy/orm/events.py
lib/sqlalchemy/orm/exc.py
lib/sqlalchemy/orm/instrumentation.py
lib/sqlalchemy/orm/interfaces.py
lib/sqlalchemy/orm/loading.py
lib/sqlalchemy/orm/mapper.py
lib/sqlalchemy/orm/path_registry.py [new file with mode: 0644]
lib/sqlalchemy/orm/persistence.py
lib/sqlalchemy/orm/properties.py
lib/sqlalchemy/orm/query.py
lib/sqlalchemy/orm/relationships.py
lib/sqlalchemy/orm/session.py
lib/sqlalchemy/orm/state.py
lib/sqlalchemy/orm/strategies.py
lib/sqlalchemy/orm/unitofwork.py
lib/sqlalchemy/orm/util.py
lib/sqlalchemy/pool.py
lib/sqlalchemy/sql/__init__.py
lib/sqlalchemy/sql/base.py
lib/sqlalchemy/sql/elements.py
lib/sqlalchemy/sql/expression.py
lib/sqlalchemy/sql/schema.py
lib/sqlalchemy/sql/sqltypes.py
lib/sqlalchemy/sql/util.py
lib/sqlalchemy/util/__init__.py
lib/sqlalchemy/util/langhelpers.py
test/ext/declarative/test_basic.py
test/orm/inheritance/test_assorted_poly.py
test/orm/test_mapper.py

index ea04ee024a51c3b7748e3157d5796f8526be4811..16d0b63a2f21cc859016e54b777ad5d421470db4 100644 (file)
         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
index d21d0fbb9a6ee5fe43f7f0963dcc696728a709ef..98156cdc9cdc4350150e4d00b061c0e12a033337 100644 (file)
@@ -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
index 8f4af6db5c7187478ed0ace4176e4f1d88595e94..c94af5b1c75f79752c58706602cc44f75c25ac5c 100644 (file)
@@ -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.
 
index b996d0bbef73571ca0c2949a9cc3154304ad5336..0a0131e23c67fc6140094e20ba6d3ad49b364017 100644 (file)
@@ -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
index d667736a129ad797cb8b866b5a9c5fc30efd7e30..629ea5800e7fde648f79eaff231073e80fec4889 100644 (file)
@@ -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
-
index bb7b3b1b44abe8022c10116dd6198d5b9644a8de..1d7bb9cd147120fef29f1e81e8d64b4f6e63b385 100644 (file)
@@ -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
+
index a5dc6e326f0fde5517012f8472fcf36abbdf8d75..b58b53916b6a3fbb8fb404265715fe163c0f3c7a 100644 (file)
@@ -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):
index 90076fdbb5b90471d33f7b8391c2d3413121fe26..b309a783a4adff9d4a690c8b3fbc24414c33c28b 100644 (file)
@@ -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
 
index 5a2b88db46e457e21a1b81e41da3cda8be9c0825..820c0874befcaef308948bad3a3169b6feda1993 100644 (file)
@@ -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
index bb44a492ca1e15471bd8f8c9fcbee525f83b21d9..714a76e686b1c14f8cfa52b068a998c8009e297f 100644 (file)
@@ -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
index 112035ac5cc380f5699ff88338a4215dfc1fc809..e70cc1c55184a4a80365b18b4941f93795618d68 100644 (file)
@@ -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
index c257923545e2aef094daa599a69235dee090f593..949eafca4db0becae7e0ccff0ffe8081918c04ec 100644 (file)
@@ -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 (file)
index 0000000..f7d9dd4
--- /dev/null
@@ -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.
+
+    """
index 2ac94f826bf0387b24a91f17a82d1dd317fe5801..f8f4b958347c65f9c6a132f6c195d2e34685d666 100644 (file)
@@ -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])
index e50967253516b5a3f61869543a5e841b0dc992a7..34a976c910d7540cc043dc9cf1d323763be8d60e 100644 (file)
@@ -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.
index c589513393713cf5eb2f142b4831b4fe41e146bf..457b26523df6b41d431c5930ff1f9dfdaaf0f703 100644 (file)
@@ -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)
+
+
index 4ad204357ce441a5088722866e08b0088d37b090..4631e806f57335fa9ee7e80d9f359cf2e6637d6d 100644 (file)
@@ -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
index b38b64f248fc705f204e58bc3c8fe342c2870cf7..e2ca39137961301f658f3efde1fac0eb49449959 100644 (file)
@@ -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.
 
         """
+
index 0faa7bd29f0a3470c10ad217a8162f68cdfea3ae..0d59da43f7c27b25ed3b4fb77d44f43e87cbdae5 100644 (file)
@@ -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:
index 877a7219348f4a33f6308255b8b19da167a3e233..a86a6086d3aca4ebbb62de6ebe16fc6246d46604 100644 (file)
@@ -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."""
index 150277be2081c81ef4d753a8b5fe8f546b06e99d..00906a262085a796bdc736d3a2c956d870f2aa70 100644 (file)
@@ -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 = []
index c219ea096a210e96a60449ccf818acab88f9a5ac..512a07d66ea6b28347560cdde462814dcaaf1eb0 100644 (file)
@@ -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; "
index a169d6d6316e921475d87422c85aeca50b93fbb0..31e7e9141558add42117995aaf1df0f1492888d7 100644 (file)
@@ -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 (file)
index 0000000..c9c91f9
--- /dev/null
@@ -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
+
+
index 44da881189549fe11707d719cde58140b650a858..14970ef25fec985a557c800f595e90bcf24c5bc5 100644 (file)
@@ -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
 
index 5514618188ea2830e003ef6c181cc845e56b4b36..ef71d663c2dfaf5b8540c2cb3e7f437b909abc1b 100644 (file)
@@ -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
index ad8183b0df503ee1422946357a4a8bf2effe5afd..1c322d5b9b3bfc1d522b6ab2685099e45bbd13bd 100644 (file)
@@ -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):
index 33377d3ec17aeb3ce51b7286f998d73803451e10..f37bb8a4dbd87aabe25d8fdd0adcaac45e4d6227 100644 (file)
@@ -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
index e5742f018c50a0ca509d8738b709d6566b376718..857ef066681c2f331f3ea682019756df81684202 100644 (file)
@@ -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()
index c479d880d87cb7e852498be9b683d201b428547c..7805a47e4774d81783644ac565bafe3c5c87b153 100644 (file)
@@ -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
 
index 39ddaa7b829f6871fab7428776c60ad53236a6e5..fb63de807ed3ba5b940cbfc66a690752bc9d09ba 100644 (file)
@@ -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):
index aa5f7836c0c723b792a7e516f1d8b27fcd9c7999..2fc1c3b2240fc32e603a9bc4bc5c69639f5bfedf 100644 (file)
@@ -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:
index ae1ca2013ff888cc8f77e09c623204948bfe56a5..d10a78dd78c9e69b532887de9e68be29d4c5c4eb 100644 (file)
@@ -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():
index f2d6ca6ea4350640e34f92f4c3f4953f29fc22cb..88d17a997778748fc4a741e98ad75e2d0f127272 100644 (file)
@@ -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:
index 3503f8c976661bc9725cb93cb1b44bde64aba011..e1497e9fa30d173ae136b548baf5706a03214380 100644 (file)
@@ -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())
+
index e7d83627d581dffa583e178ac8aececbfd1d447d..2a3176d04b833c51c1998269fe093b12e89d9322 100644 (file)
@@ -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."""
 
index 1867df12361fd4874cd9a7f4b12c67075bb8141d..17fb40628d18a8a0ca262eafdae6fa380cab0b99 100644 (file)
@@ -9,7 +9,6 @@
 
 """
 
-
 from __future__ import unicode_literals
 
 from .. import util, exc, inspection
index ee4d81f6e90716c61d5c698a3cb9e76683e12486..bbbe0b235ea53f9798a68986c95092443dbc12c8 100644 (file)
@@ -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
index 183b30077325bc6f74bd29b562de19f8aac7e3f0..ccab31991edc46d6ec5d717d8767dd6839aafa24 100644 (file)
@@ -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.
index c7b210be640cdd5b0879bcad55825471b33189a8..38e0d1bd343b696330fc5f58e884148947ffba1d 100644 (file)
@@ -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)
index a9bd22cc66a99e6930a0adbc1e5117b49700d04a..b927f1b3cc9b1880ecff73dbcc948337797afda8 100644 (file)
@@ -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
index 2237e1f13752fbfd40085025e8036301624f6f01..968421c8bdc9003e35dc15c3b7b6c86821c4d3bb 100644 (file)
@@ -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,\
index e10a10e27247839bea1da68d70db0d706ec45141..1fd5ccf30d44f038df9102da4e6e2a3ee3536914 100644 (file)
@@ -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
index 540f1623f832ef28fbc91972e1f16d4eaaa3bd9d..2de0032ddc6decce3af47750e9c6bda0599a38a1 100644 (file)
@@ -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
index d05a22f3906eb49185b9345c9fb0486832b50575..da0e3b1a3c640dcf56ca014ccaa019f75009d7a2 100644 (file)
@@ -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()]
index e742505b0e1f3e599c63e58049af5ec6342a2667..e073093fa6df6b0951158002e1e789f7f1a4c62a 100644 (file)
@@ -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) == \