mirror of
https://github.com/sqlalchemy/sqlalchemy.git
synced 2026-05-28 11:35:19 -04:00
- express most of the orm.util functions in terms of the inspection system
- modify inspection system:
1. raise a new exception for any case where the inspection
context can't be returned. this supersedes the "not mapped"
errors.
2. don't configure mappers on a mapper inspection. this allows
the inspectors to be used during mapper config time. instead,
the mapper configures on "with_polymorphic_selectable" now,
which is needed for all queries
- add a bunch of new "is_XYZ" attributes to inspectors
- finish making the name change of "compile" -> "configure", for some reason
this was only done partially
This commit is contained in:
+28
-28
@@ -35,21 +35,21 @@ class AmbiguousForeignKeysError(ArgumentError):
|
||||
|
||||
class CircularDependencyError(SQLAlchemyError):
|
||||
"""Raised by topological sorts when a circular dependency is detected.
|
||||
|
||||
|
||||
There are two scenarios where this error occurs:
|
||||
|
||||
|
||||
* In a Session flush operation, if two objects are mutually dependent
|
||||
on each other, they can not be inserted or deleted via INSERT or
|
||||
on each other, they can not be inserted or deleted via INSERT or
|
||||
DELETE statements alone; an UPDATE will be needed to post-associate
|
||||
or pre-deassociate one of the foreign key constrained values.
|
||||
The ``post_update`` flag described at :ref:`post_update` can resolve
|
||||
The ``post_update`` flag described at :ref:`post_update` can resolve
|
||||
this cycle.
|
||||
* In a :meth:`.MetaData.create_all`, :meth:`.MetaData.drop_all`,
|
||||
:attr:`.MetaData.sorted_tables` operation, two :class:`.ForeignKey`
|
||||
or :class:`.ForeignKeyConstraint` objects mutually refer to each
|
||||
other. Apply the ``use_alter=True`` flag to one or both,
|
||||
see :ref:`use_alter`.
|
||||
|
||||
|
||||
"""
|
||||
def __init__(self, message, cycles, edges, msg=None):
|
||||
if msg is None:
|
||||
@@ -61,7 +61,7 @@ class CircularDependencyError(SQLAlchemyError):
|
||||
self.edges = edges
|
||||
|
||||
def __reduce__(self):
|
||||
return self.__class__, (None, self.cycles,
|
||||
return self.__class__, (None, self.cycles,
|
||||
self.edges, self.args[0])
|
||||
|
||||
class CompileError(SQLAlchemyError):
|
||||
@@ -70,23 +70,19 @@ class CompileError(SQLAlchemyError):
|
||||
class IdentifierError(SQLAlchemyError):
|
||||
"""Raised when a schema name is beyond the max character limit"""
|
||||
|
||||
# Moved to orm.exc; compatibility definition installed by orm import until 0.6
|
||||
ConcurrentModificationError = None
|
||||
|
||||
class DisconnectionError(SQLAlchemyError):
|
||||
"""A disconnect is detected on a raw DB-API connection.
|
||||
|
||||
This error is raised and consumed internally by a connection pool. It can
|
||||
be raised by the :meth:`.PoolEvents.checkout` event
|
||||
be raised by the :meth:`.PoolEvents.checkout` event
|
||||
so that the host pool forces a retry; the exception will be caught
|
||||
three times in a row before the pool gives up and raises
|
||||
three times in a row before the pool gives up and raises
|
||||
:class:`~sqlalchemy.exc.InvalidRequestError` regarding the connection attempt.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# Moved to orm.exc; compatibility definition installed by orm import until 0.6
|
||||
FlushError = None
|
||||
|
||||
class TimeoutError(SQLAlchemyError):
|
||||
"""Raised when a connection pool times out on getting a connection."""
|
||||
@@ -99,6 +95,10 @@ class InvalidRequestError(SQLAlchemyError):
|
||||
|
||||
"""
|
||||
|
||||
class NoInspectionAvailable(InvalidRequestError):
|
||||
"""A class to :func:`sqlalchemy.inspection.inspect` produced
|
||||
no context for inspection."""
|
||||
|
||||
class ResourceClosedError(InvalidRequestError):
|
||||
"""An operation was requested from a connection, cursor, or other
|
||||
object that's in a closed state."""
|
||||
@@ -128,7 +128,7 @@ class NoReferencedColumnError(NoReferenceError):
|
||||
self.column_name = cname
|
||||
|
||||
def __reduce__(self):
|
||||
return self.__class__, (self.args[0], self.table_name,
|
||||
return self.__class__, (self.args[0], self.table_name,
|
||||
self.column_name)
|
||||
|
||||
class NoSuchTableError(InvalidRequestError):
|
||||
@@ -143,20 +143,20 @@ class DontWrapMixin(object):
|
||||
"""A mixin class which, when applied to a user-defined Exception class,
|
||||
will not be wrapped inside of :class:`.StatementError` if the error is
|
||||
emitted within the process of executing a statement.
|
||||
|
||||
|
||||
E.g.::
|
||||
from sqlalchemy.exc import DontWrapMixin
|
||||
|
||||
|
||||
class MyCustomException(Exception, DontWrapMixin):
|
||||
pass
|
||||
|
||||
|
||||
class MySpecialType(TypeDecorator):
|
||||
impl = String
|
||||
|
||||
|
||||
def process_bind_param(self, value, dialect):
|
||||
if value == 'invalid':
|
||||
raise MyCustomException("invalid!")
|
||||
|
||||
|
||||
"""
|
||||
import sys
|
||||
if sys.version_info < (2, 5):
|
||||
@@ -168,15 +168,15 @@ UnmappedColumnError = None
|
||||
|
||||
class StatementError(SQLAlchemyError):
|
||||
"""An error occurred during execution of a SQL statement.
|
||||
|
||||
|
||||
:class:`StatementError` wraps the exception raised
|
||||
during execution, and features :attr:`.statement`
|
||||
and :attr:`.params` attributes which supply context regarding
|
||||
the specifics of the statement which had an issue.
|
||||
|
||||
The wrapped exception object is available in
|
||||
The wrapped exception object is available in
|
||||
the :attr:`.orig` attribute.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
statement = None
|
||||
@@ -195,7 +195,7 @@ class StatementError(SQLAlchemyError):
|
||||
self.orig = orig
|
||||
|
||||
def __reduce__(self):
|
||||
return self.__class__, (self.args[0], self.statement,
|
||||
return self.__class__, (self.args[0], self.statement,
|
||||
self.params, self.orig)
|
||||
|
||||
def __str__(self):
|
||||
@@ -218,7 +218,7 @@ class DBAPIError(StatementError):
|
||||
|
||||
:class:`DBAPIError` features :attr:`~.StatementError.statement`
|
||||
and :attr:`~.StatementError.params` attributes which supply context regarding
|
||||
the specifics of the statement which had an issue, for the
|
||||
the specifics of the statement which had an issue, for the
|
||||
typical case when the error was raised within the context of
|
||||
emitting a SQL statement.
|
||||
|
||||
@@ -228,8 +228,8 @@ class DBAPIError(StatementError):
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def instance(cls, statement, params,
|
||||
orig,
|
||||
def instance(cls, statement, params,
|
||||
orig,
|
||||
dbapi_base_err,
|
||||
connection_invalidated=False):
|
||||
# Don't ever wrap these, just return them directly as if
|
||||
@@ -243,7 +243,7 @@ class DBAPIError(StatementError):
|
||||
if not isinstance(orig, dbapi_base_err) and statement:
|
||||
return StatementError(
|
||||
"%s (original cause: %s)" % (
|
||||
str(orig),
|
||||
str(orig),
|
||||
traceback.format_exception_only(orig.__class__, orig)[-1].strip()
|
||||
), statement, params, orig)
|
||||
|
||||
@@ -254,7 +254,7 @@ class DBAPIError(StatementError):
|
||||
return cls(statement, params, orig, connection_invalidated)
|
||||
|
||||
def __reduce__(self):
|
||||
return self.__class__, (self.statement, self.params,
|
||||
return self.__class__, (self.statement, self.params,
|
||||
self.orig, self.connection_invalidated)
|
||||
|
||||
def __init__(self, statement, params, orig, connection_invalidated=False):
|
||||
@@ -265,7 +265,7 @@ class DBAPIError(StatementError):
|
||||
except Exception, e:
|
||||
text = 'Error in str() of DB-API-generated exception: ' + str(e)
|
||||
StatementError.__init__(
|
||||
self,
|
||||
self,
|
||||
'(%s) %s' % (orig.__class__.__name__, text),
|
||||
statement,
|
||||
params,
|
||||
|
||||
+101
-101
@@ -51,7 +51,7 @@ automatically named with the name of the attribute to which they are
|
||||
assigned.
|
||||
|
||||
To name columns explicitly with a name distinct from their mapped attribute,
|
||||
just give the column a name. Below, column "some_table_id" is mapped to the
|
||||
just give the column a name. Below, column "some_table_id" is mapped to the
|
||||
"id" attribute of `SomeClass`, but in SQL will be represented as "some_table_id"::
|
||||
|
||||
class SomeClass(Base):
|
||||
@@ -68,7 +68,7 @@ added to the underlying :class:`.Table` and
|
||||
Classes which are constructed using declarative can interact freely
|
||||
with classes that are mapped explicitly with :func:`mapper`.
|
||||
|
||||
It is recommended, though not required, that all tables
|
||||
It is recommended, though not required, that all tables
|
||||
share the same underlying :class:`~sqlalchemy.schema.MetaData` object,
|
||||
so that string-configured :class:`~sqlalchemy.schema.ForeignKey`
|
||||
references can be resolved without issue.
|
||||
@@ -98,9 +98,9 @@ of construction, the ``bind`` argument is accepted::
|
||||
|
||||
:func:`declarative_base` can also receive a pre-existing
|
||||
:class:`.MetaData` object, which allows a
|
||||
declarative setup to be associated with an already
|
||||
declarative setup to be associated with an already
|
||||
existing traditional collection of :class:`~sqlalchemy.schema.Table`
|
||||
objects::
|
||||
objects::
|
||||
|
||||
mymetadata = MetaData()
|
||||
Base = declarative_base(metadata=mymetadata)
|
||||
@@ -113,7 +113,7 @@ feature that the class specified to :func:`~sqlalchemy.orm.relationship`
|
||||
may be a string name. The "class registry" associated with ``Base``
|
||||
is used at mapper compilation time to resolve the name into the actual
|
||||
class object, which is expected to have been defined once the mapper
|
||||
configuration is used::
|
||||
configuration is used::
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = 'users'
|
||||
@@ -131,7 +131,7 @@ configuration is used::
|
||||
|
||||
Column constructs, since they are just that, are immediately usable,
|
||||
as below where we define a primary join condition on the ``Address``
|
||||
class using them::
|
||||
class using them::
|
||||
|
||||
class Address(Base):
|
||||
__tablename__ = 'addresses'
|
||||
@@ -148,15 +148,15 @@ evaluated as Python expressions. The full namespace available within
|
||||
this evaluation includes all classes mapped for this declarative base,
|
||||
as well as the contents of the ``sqlalchemy`` package, including
|
||||
expression functions like :func:`~sqlalchemy.sql.expression.desc` and
|
||||
:attr:`~sqlalchemy.sql.expression.func`::
|
||||
:attr:`~sqlalchemy.sql.expression.func`::
|
||||
|
||||
class User(Base):
|
||||
# ....
|
||||
addresses = relationship("Address",
|
||||
order_by="desc(Address.email)",
|
||||
order_by="desc(Address.email)",
|
||||
primaryjoin="Address.user_id==User.id")
|
||||
|
||||
As an alternative to string-based attributes, attributes may also be
|
||||
As an alternative to string-based attributes, attributes may also be
|
||||
defined after all classes have been created. Just add them to the target
|
||||
class after the fact::
|
||||
|
||||
@@ -169,8 +169,8 @@ Configuring Many-to-Many Relationships
|
||||
Many-to-many relationships are also declared in the same way
|
||||
with declarative as with traditional mappings. The
|
||||
``secondary`` argument to
|
||||
:func:`.relationship` is as usual passed a
|
||||
:class:`.Table` object, which is typically declared in the
|
||||
:func:`.relationship` is as usual passed a
|
||||
:class:`.Table` object, which is typically declared in the
|
||||
traditional way. The :class:`.Table` usually shares
|
||||
the :class:`.MetaData` object used by the declarative base::
|
||||
|
||||
@@ -185,7 +185,7 @@ the :class:`.MetaData` object used by the declarative base::
|
||||
id = Column(Integer, primary_key=True)
|
||||
keywords = relationship("Keyword", secondary=keywords)
|
||||
|
||||
Like other :func:`.relationship` arguments, a string is accepted as well,
|
||||
Like other :func:`.relationship` arguments, a string is accepted as well,
|
||||
passing the string name of the table as defined in the ``Base.metadata.tables``
|
||||
collection::
|
||||
|
||||
@@ -194,7 +194,7 @@ collection::
|
||||
id = Column(Integer, primary_key=True)
|
||||
keywords = relationship("Keyword", secondary="keywords")
|
||||
|
||||
As with traditional mapping, its generally not a good idea to use
|
||||
As with traditional mapping, its generally not a good idea to use
|
||||
a :class:`.Table` as the "secondary" argument which is also mapped to
|
||||
a class, unless the :class:`.relationship` is declared with ``viewonly=True``.
|
||||
Otherwise, the unit-of-work system may attempt duplicate INSERT and
|
||||
@@ -219,7 +219,7 @@ This attribute accommodates both positional as well as keyword
|
||||
arguments that are normally sent to the
|
||||
:class:`~sqlalchemy.schema.Table` constructor.
|
||||
The attribute can be specified in one of two forms. One is as a
|
||||
dictionary::
|
||||
dictionary::
|
||||
|
||||
class MyClass(Base):
|
||||
__tablename__ = 'sometable'
|
||||
@@ -235,7 +235,7 @@ The other, a tuple, where each argument is positional
|
||||
UniqueConstraint('foo'),
|
||||
)
|
||||
|
||||
Keyword arguments can be specified with the above form by
|
||||
Keyword arguments can be specified with the above form by
|
||||
specifying the last argument as a dictionary::
|
||||
|
||||
class MyClass(Base):
|
||||
@@ -253,7 +253,7 @@ As an alternative to ``__tablename__``, a direct
|
||||
:class:`~sqlalchemy.schema.Table` construct may be used. The
|
||||
:class:`~sqlalchemy.schema.Column` objects, which in this case require
|
||||
their names, will be added to the mapping just like a regular mapping
|
||||
to a table::
|
||||
to a table::
|
||||
|
||||
class MyClass(Base):
|
||||
__table__ = Table('my_table', Base.metadata,
|
||||
@@ -277,9 +277,9 @@ and pass it to declarative classes::
|
||||
class Address(Base):
|
||||
__table__ = metadata.tables['address']
|
||||
|
||||
Some configuration schemes may find it more appropriate to use ``__table__``,
|
||||
such as those which already take advantage of the data-driven nature of
|
||||
:class:`.Table` to customize and/or automate schema definition.
|
||||
Some configuration schemes may find it more appropriate to use ``__table__``,
|
||||
such as those which already take advantage of the data-driven nature of
|
||||
:class:`.Table` to customize and/or automate schema definition.
|
||||
|
||||
Note that when the ``__table__`` approach is used, the object is immediately
|
||||
usable as a plain :class:`.Table` within the class declaration body itself,
|
||||
@@ -292,10 +292,10 @@ by using the ``id`` column in the ``primaryjoin`` condition of a :func:`.relatio
|
||||
Column('name', String(50))
|
||||
)
|
||||
|
||||
widgets = relationship(Widget,
|
||||
widgets = relationship(Widget,
|
||||
primaryjoin=Widget.myclass_id==__table__.c.id)
|
||||
|
||||
Similarly, mapped attributes which refer to ``__table__`` can be placed inline,
|
||||
Similarly, mapped attributes which refer to ``__table__`` can be placed inline,
|
||||
as below where we assign the ``name`` column to the attribute ``_name``, generating
|
||||
a synonym for ``name``::
|
||||
|
||||
@@ -320,13 +320,13 @@ It's easy to set up a :class:`.Table` that uses ``autoload=True``
|
||||
in conjunction with a mapped class::
|
||||
|
||||
class MyClass(Base):
|
||||
__table__ = Table('mytable', Base.metadata,
|
||||
__table__ = Table('mytable', Base.metadata,
|
||||
autoload=True, autoload_with=some_engine)
|
||||
|
||||
However, one improvement that can be made here is to not
|
||||
require the :class:`.Engine` to be available when classes are
|
||||
However, one improvement that can be made here is to not
|
||||
require the :class:`.Engine` to be available when classes are
|
||||
being first declared. To achieve this, use the
|
||||
:class:`.DeferredReflection` mixin, which sets up mappings
|
||||
:class:`.DeferredReflection` mixin, which sets up mappings
|
||||
only after a special ``prepare(engine)`` step is called::
|
||||
|
||||
from sqlalchemy.ext.declarative import declarative_base, DeferredReflection
|
||||
@@ -340,7 +340,7 @@ only after a special ``prepare(engine)`` step is called::
|
||||
class Bar(Base):
|
||||
__tablename__ = 'bar'
|
||||
|
||||
# illustrate overriding of "bar.foo_id" to have
|
||||
# illustrate overriding of "bar.foo_id" to have
|
||||
# a foreign key constraint otherwise not
|
||||
# reflected, such as when using MySQL
|
||||
foo_id = Column(Integer, ForeignKey('foo.id'))
|
||||
@@ -357,7 +357,7 @@ Declarative makes use of the :func:`~.orm.mapper` function internally
|
||||
when it creates the mapping to the declared table. The options
|
||||
for :func:`~.orm.mapper` are passed directly through via the ``__mapper_args__``
|
||||
class attribute. As always, arguments which reference locally
|
||||
mapped columns can reference them directly from within the
|
||||
mapped columns can reference them directly from within the
|
||||
class declaration::
|
||||
|
||||
from datetime import datetime
|
||||
@@ -386,7 +386,7 @@ as declarative will determine this from the class itself. The various
|
||||
Joined Table Inheritance
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Joined table inheritance is defined as a subclass that defines its own
|
||||
Joined table inheritance is defined as a subclass that defines its own
|
||||
table::
|
||||
|
||||
class Person(Base):
|
||||
@@ -419,13 +419,13 @@ only the ``engineers.id`` column, give it a different attribute name::
|
||||
.. versionchanged:: 0.7 joined table inheritance favors the subclass
|
||||
column over that of the superclass, such as querying above
|
||||
for ``Engineer.id``. Prior to 0.7 this was the reverse.
|
||||
|
||||
|
||||
Single Table Inheritance
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Single table inheritance is defined as a subclass that does not have
|
||||
its own table; you just leave out the ``__table__`` and ``__tablename__``
|
||||
attributes::
|
||||
attributes::
|
||||
|
||||
class Person(Base):
|
||||
__tablename__ = 'people'
|
||||
@@ -536,7 +536,7 @@ To have a concrete ``employee`` table, use :class:`.ConcreteBase` instead::
|
||||
employee_id = Column(Integer, primary_key=True)
|
||||
name = Column(String(50))
|
||||
__mapper_args__ = {
|
||||
'polymorphic_identity':'employee',
|
||||
'polymorphic_identity':'employee',
|
||||
'concrete':True}
|
||||
|
||||
|
||||
@@ -548,7 +548,7 @@ Either ``Employee`` base can be used in the normal fashion::
|
||||
name = Column(String(50))
|
||||
manager_data = Column(String(40))
|
||||
__mapper_args__ = {
|
||||
'polymorphic_identity':'manager',
|
||||
'polymorphic_identity':'manager',
|
||||
'concrete':True}
|
||||
|
||||
class Engineer(Employee):
|
||||
@@ -556,7 +556,7 @@ Either ``Employee`` base can be used in the normal fashion::
|
||||
employee_id = Column(Integer, primary_key=True)
|
||||
name = Column(String(50))
|
||||
engineer_info = Column(String(40))
|
||||
__mapper_args__ = {'polymorphic_identity':'engineer',
|
||||
__mapper_args__ = {'polymorphic_identity':'engineer',
|
||||
'concrete':True}
|
||||
|
||||
|
||||
@@ -596,29 +596,29 @@ idioms is below::
|
||||
|
||||
Where above, the class ``MyModel`` will contain an "id" column
|
||||
as the primary key, a ``__tablename__`` attribute that derives
|
||||
from the name of the class itself, as well as ``__table_args__``
|
||||
from the name of the class itself, as well as ``__table_args__``
|
||||
and ``__mapper_args__`` defined by the ``MyMixin`` mixin class.
|
||||
|
||||
There's no fixed convention over whether ``MyMixin`` precedes
|
||||
``Base`` or not. Normal Python method resolution rules apply, and
|
||||
There's no fixed convention over whether ``MyMixin`` precedes
|
||||
``Base`` or not. Normal Python method resolution rules apply, and
|
||||
the above example would work just as well with::
|
||||
|
||||
class MyModel(Base, MyMixin):
|
||||
name = Column(String(1000))
|
||||
|
||||
This works because ``Base`` here doesn't define any of the
|
||||
variables that ``MyMixin`` defines, i.e. ``__tablename__``,
|
||||
``__table_args__``, ``id``, etc. If the ``Base`` did define
|
||||
an attribute of the same name, the class placed first in the
|
||||
inherits list would determine which attribute is used on the
|
||||
This works because ``Base`` here doesn't define any of the
|
||||
variables that ``MyMixin`` defines, i.e. ``__tablename__``,
|
||||
``__table_args__``, ``id``, etc. If the ``Base`` did define
|
||||
an attribute of the same name, the class placed first in the
|
||||
inherits list would determine which attribute is used on the
|
||||
newly defined class.
|
||||
|
||||
Augmenting the Base
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In addition to using a pure mixin, most of the techniques in this
|
||||
In addition to using a pure mixin, most of the techniques in this
|
||||
section can also be applied to the base class itself, for patterns that
|
||||
should apply to all classes derived from a particular base. This
|
||||
should apply to all classes derived from a particular base. This
|
||||
is achieved using the ``cls`` argument of the :func:`.declarative_base` function::
|
||||
|
||||
from sqlalchemy.ext.declarative import declared_attr
|
||||
@@ -639,14 +639,14 @@ is achieved using the ``cls`` argument of the :func:`.declarative_base` function
|
||||
class MyModel(Base):
|
||||
name = Column(String(1000))
|
||||
|
||||
Where above, ``MyModel`` and all other classes that derive from ``Base`` will have
|
||||
a table name derived from the class name, an ``id`` primary key column, as well as
|
||||
Where above, ``MyModel`` and all other classes that derive from ``Base`` will have
|
||||
a table name derived from the class name, an ``id`` primary key column, as well as
|
||||
the "InnoDB" engine for MySQL.
|
||||
|
||||
Mixing in Columns
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
The most basic way to specify a column on a mixin is by simple
|
||||
The most basic way to specify a column on a mixin is by simple
|
||||
declaration::
|
||||
|
||||
class TimestampMixin(object):
|
||||
@@ -659,26 +659,26 @@ declaration::
|
||||
name = Column(String(1000))
|
||||
|
||||
Where above, all declarative classes that include ``TimestampMixin``
|
||||
will also have a column ``created_at`` that applies a timestamp to
|
||||
will also have a column ``created_at`` that applies a timestamp to
|
||||
all row insertions.
|
||||
|
||||
Those familiar with the SQLAlchemy expression language know that
|
||||
Those familiar with the SQLAlchemy expression language know that
|
||||
the object identity of clause elements defines their role in a schema.
|
||||
Two ``Table`` objects ``a`` and ``b`` may both have a column called
|
||||
``id``, but the way these are differentiated is that ``a.c.id``
|
||||
Two ``Table`` objects ``a`` and ``b`` may both have a column called
|
||||
``id``, but the way these are differentiated is that ``a.c.id``
|
||||
and ``b.c.id`` are two distinct Python objects, referencing their
|
||||
parent tables ``a`` and ``b`` respectively.
|
||||
|
||||
In the case of the mixin column, it seems that only one
|
||||
:class:`.Column` object is explicitly created, yet the ultimate
|
||||
:class:`.Column` object is explicitly created, yet the ultimate
|
||||
``created_at`` column above must exist as a distinct Python object
|
||||
for each separate destination class. To accomplish this, the declarative
|
||||
extension creates a **copy** of each :class:`.Column` object encountered on
|
||||
extension creates a **copy** of each :class:`.Column` object encountered on
|
||||
a class that is detected as a mixin.
|
||||
|
||||
This copy mechanism is limited to simple columns that have no foreign
|
||||
keys, as a :class:`.ForeignKey` itself contains references to columns
|
||||
which can't be properly recreated at this level. For columns that
|
||||
which can't be properly recreated at this level. For columns that
|
||||
have foreign keys, as well as for the variety of mapper-level constructs
|
||||
that require destination-explicit context, the
|
||||
:func:`~.declared_attr` decorator is provided so that
|
||||
@@ -695,7 +695,7 @@ patterns common to many classes can be defined as callables::
|
||||
__tablename__ = 'user'
|
||||
id = Column(Integer, primary_key=True)
|
||||
|
||||
Where above, the ``address_id`` class-level callable is executed at the
|
||||
Where above, the ``address_id`` class-level callable is executed at the
|
||||
point at which the ``User`` class is constructed, and the declarative
|
||||
extension can use the resulting :class:`.Column` object as returned by
|
||||
the method without the need to copy it.
|
||||
@@ -704,8 +704,8 @@ the method without the need to copy it.
|
||||
Rename 0.6.5 ``sqlalchemy.util.classproperty`` into :func:`~.declared_attr`.
|
||||
|
||||
Columns generated by :func:`~.declared_attr` can also be
|
||||
referenced by ``__mapper_args__`` to a limited degree, currently
|
||||
by ``polymorphic_on`` and ``version_id_col``, by specifying the
|
||||
referenced by ``__mapper_args__`` to a limited degree, currently
|
||||
by ``polymorphic_on`` and ``version_id_col``, by specifying the
|
||||
classdecorator itself into the dictionary - the declarative extension
|
||||
will resolve them at class construction time::
|
||||
|
||||
@@ -753,7 +753,7 @@ reference a common target class via many-to-one::
|
||||
id = Column(Integer, primary_key=True)
|
||||
|
||||
:func:`~sqlalchemy.orm.relationship` definitions which require explicit
|
||||
primaryjoin, order_by etc. expressions should use the string forms
|
||||
primaryjoin, order_by etc. expressions should use the string forms
|
||||
for these arguments, so that they are evaluated as late as possible.
|
||||
To reference the mixin class in these expressions, use the given ``cls``
|
||||
to get it's name::
|
||||
@@ -775,8 +775,8 @@ Mixing in deferred(), column_property(), etc.
|
||||
Like :func:`~sqlalchemy.orm.relationship`, all
|
||||
:class:`~sqlalchemy.orm.interfaces.MapperProperty` subclasses such as
|
||||
:func:`~sqlalchemy.orm.deferred`, :func:`~sqlalchemy.orm.column_property`,
|
||||
etc. ultimately involve references to columns, and therefore, when
|
||||
used with declarative mixins, have the :func:`.declared_attr`
|
||||
etc. ultimately involve references to columns, and therefore, when
|
||||
used with declarative mixins, have the :func:`.declared_attr`
|
||||
requirement so that no reliance on copying is needed::
|
||||
|
||||
class SomethingMixin(object):
|
||||
@@ -793,7 +793,7 @@ Controlling table inheritance with mixins
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``__tablename__`` attribute in conjunction with the hierarchy of
|
||||
classes involved in a declarative mixin scenario controls what type of
|
||||
classes involved in a declarative mixin scenario controls what type of
|
||||
table inheritance, if any,
|
||||
is configured by the declarative extension.
|
||||
|
||||
@@ -828,7 +828,7 @@ return a ``__tablename__`` in the event that no table is already
|
||||
mapped in the inheritance hierarchy. To help with this, a
|
||||
:func:`~sqlalchemy.ext.declarative.has_inherited_table` helper
|
||||
function is provided that returns ``True`` if a parent class already
|
||||
has a mapped table.
|
||||
has a mapped table.
|
||||
|
||||
As an example, here's a mixin that will only allow single table
|
||||
inheritance::
|
||||
@@ -918,7 +918,7 @@ from multiple collections::
|
||||
Creating Indexes with Mixins
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
To define a named, potentially multicolumn :class:`.Index` that applies to all
|
||||
To define a named, potentially multicolumn :class:`.Index` that applies to all
|
||||
tables derived from a mixin, use the "inline" form of :class:`.Index` and establish
|
||||
it as part of ``__table_args__``::
|
||||
|
||||
@@ -940,7 +940,7 @@ Special Directives
|
||||
``__declare_last__()``
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``__declare_last__()`` hook allows definition of
|
||||
The ``__declare_last__()`` hook allows definition of
|
||||
a class level function that is automatically called by the :meth:`.MapperEvents.after_configured`
|
||||
event, which occurs after mappings are assumed to be completed and the 'configure' step
|
||||
has finished::
|
||||
@@ -989,7 +989,7 @@ bases::
|
||||
__abstract__ = True
|
||||
metadata = MetaData()
|
||||
|
||||
Above, classes which inherit from ``DefaultBase`` will use one :class:`.MetaData` as the
|
||||
Above, classes which inherit from ``DefaultBase`` will use one :class:`.MetaData` as the
|
||||
registry of tables, and those which inherit from ``OtherBase`` will use a different one.
|
||||
The tables themselves can then be created perhaps within distinct databases::
|
||||
|
||||
@@ -1022,7 +1022,7 @@ setup using :func:`~sqlalchemy.orm.scoped_session` might look like::
|
||||
Base = declarative_base()
|
||||
|
||||
Mapped instances then make usage of
|
||||
:class:`~sqlalchemy.orm.session.Session` in the usual way.
|
||||
:class:`~sqlalchemy.orm.session.Session` in the usual way.
|
||||
|
||||
"""
|
||||
|
||||
@@ -1047,14 +1047,14 @@ def _declared_mapping_info(cls):
|
||||
return _MapperConfig.configs[cls]
|
||||
# regular mapping
|
||||
elif _is_mapped_class(cls):
|
||||
return class_mapper(cls, compile=False)
|
||||
return class_mapper(cls, configure=False)
|
||||
else:
|
||||
return None
|
||||
|
||||
def instrument_declarative(cls, registry, metadata):
|
||||
"""Given a class, configure the class declaratively,
|
||||
using the given registry, which can be any dictionary, and
|
||||
MetaData object.
|
||||
MetaData object.
|
||||
|
||||
"""
|
||||
if '_decl_class_registry' in cls.__dict__:
|
||||
@@ -1097,7 +1097,7 @@ def _as_declarative(cls, classname, dict_):
|
||||
def go():
|
||||
cls.__declare_last__()
|
||||
if '__abstract__' in base.__dict__:
|
||||
if (base is cls or
|
||||
if (base is cls or
|
||||
(base in cls.__bases__ and not _is_declarative_inherits)
|
||||
):
|
||||
return
|
||||
@@ -1109,7 +1109,7 @@ def _as_declarative(cls, classname, dict_):
|
||||
for name,obj in vars(base).items():
|
||||
if name == '__mapper_args__':
|
||||
if not mapper_args_fn and (
|
||||
not class_mapped or
|
||||
not class_mapped or
|
||||
isinstance(obj, declarative_props)
|
||||
):
|
||||
# don't even invoke __mapper_args__ until
|
||||
@@ -1118,13 +1118,13 @@ def _as_declarative(cls, classname, dict_):
|
||||
mapper_args_fn = lambda: cls.__mapper_args__
|
||||
elif name == '__tablename__':
|
||||
if not tablename and (
|
||||
not class_mapped or
|
||||
not class_mapped or
|
||||
isinstance(obj, declarative_props)
|
||||
):
|
||||
tablename = cls.__tablename__
|
||||
elif name == '__table_args__':
|
||||
if not table_args and (
|
||||
not class_mapped or
|
||||
not class_mapped or
|
||||
isinstance(obj, declarative_props)
|
||||
):
|
||||
table_args = cls.__table_args__
|
||||
@@ -1139,7 +1139,7 @@ def _as_declarative(cls, classname, dict_):
|
||||
util.warn("Regular (i.e. not __special__) "
|
||||
"attribute '%s.%s' uses @declared_attr, "
|
||||
"but owning class %s is mapped - "
|
||||
"not applying to subclass %s."
|
||||
"not applying to subclass %s."
|
||||
% (base.__name__, name, base, cls))
|
||||
continue
|
||||
elif base is not cls:
|
||||
@@ -1151,7 +1151,7 @@ def _as_declarative(cls, classname, dict_):
|
||||
"must be declared as @declared_attr callables "
|
||||
"on declarative mixin classes. ")
|
||||
if name not in dict_ and not (
|
||||
'__table__' in dict_ and
|
||||
'__table__' in dict_ and
|
||||
(obj.name or name) in dict_['__table__'].c
|
||||
) and name not in potential_columns:
|
||||
potential_columns[name] = \
|
||||
@@ -1231,7 +1231,7 @@ def _as_declarative(cls, classname, dict_):
|
||||
elif isinstance(c, Column):
|
||||
_undefer_column_name(key, c)
|
||||
declared_columns.add(c)
|
||||
# if the column is the same name as the key,
|
||||
# if the column is the same name as the key,
|
||||
# remove it from the explicit properties dict.
|
||||
# the normal rules for assigning column-based properties
|
||||
# will take over, including precedence of columns
|
||||
@@ -1317,17 +1317,17 @@ def _as_declarative(cls, classname, dict_):
|
||||
if c.name in inherited_table.c:
|
||||
raise exc.ArgumentError(
|
||||
"Column '%s' on class %s conflicts with "
|
||||
"existing column '%s'" %
|
||||
"existing column '%s'" %
|
||||
(c, cls, inherited_table.c[c.name])
|
||||
)
|
||||
inherited_table.append_column(c)
|
||||
|
||||
mt = _MapperConfig(mapper_cls,
|
||||
mt = _MapperConfig(mapper_cls,
|
||||
cls, table,
|
||||
inherits,
|
||||
declared_columns,
|
||||
declared_columns,
|
||||
column_copies,
|
||||
our_stuff,
|
||||
our_stuff,
|
||||
mapper_args_fn)
|
||||
if not hasattr(cls, '_sa_decl_prepare'):
|
||||
mt.map()
|
||||
@@ -1335,9 +1335,9 @@ def _as_declarative(cls, classname, dict_):
|
||||
class _MapperConfig(object):
|
||||
configs = util.OrderedDict()
|
||||
|
||||
def __init__(self, mapper_cls,
|
||||
cls,
|
||||
table,
|
||||
def __init__(self, mapper_cls,
|
||||
cls,
|
||||
table,
|
||||
inherits,
|
||||
declared_columns,
|
||||
column_copies,
|
||||
@@ -1361,7 +1361,7 @@ class _MapperConfig(object):
|
||||
else:
|
||||
mapper_args = {}
|
||||
|
||||
# make sure that column copies are used rather
|
||||
# make sure that column copies are used rather
|
||||
# than the original columns from any mixins
|
||||
for k in ('version_id_col', 'polymorphic_on',):
|
||||
if k in mapper_args:
|
||||
@@ -1376,7 +1376,7 @@ class _MapperConfig(object):
|
||||
|
||||
if self.inherits and not mapper_args.get('concrete', False):
|
||||
# single or joined inheritance
|
||||
# exclude any cols on the inherited table which are
|
||||
# exclude any cols on the inherited table which are
|
||||
# not mapped on the parent class, to avoid
|
||||
# mapping columns specific to sibling/nephew classes
|
||||
inherited_mapper = _declared_mapping_info(self.inherits)
|
||||
@@ -1389,7 +1389,7 @@ class _MapperConfig(object):
|
||||
exclude_properties.difference_update(
|
||||
[c.key for c in self.declared_columns])
|
||||
|
||||
# look through columns in the current mapper that
|
||||
# look through columns in the current mapper that
|
||||
# are keyed to a propname different than the colname
|
||||
# (if names were the same, we'd have popped it out above,
|
||||
# in which case the mapper makes this combination).
|
||||
@@ -1440,7 +1440,7 @@ class DeclarativeMeta(type):
|
||||
cls.__mapper__.add_property(key, value)
|
||||
elif isinstance(value, MapperProperty):
|
||||
cls.__mapper__.add_property(
|
||||
key,
|
||||
key,
|
||||
_deferred_relationship(cls, value)
|
||||
)
|
||||
else:
|
||||
@@ -1454,7 +1454,7 @@ class _GetColumns(object):
|
||||
self.cls = cls
|
||||
|
||||
def __getattr__(self, key):
|
||||
mapper = class_mapper(self.cls, compile=False)
|
||||
mapper = class_mapper(self.cls, configure=False)
|
||||
if mapper:
|
||||
if not mapper.has_property(key):
|
||||
raise exc.InvalidRequestError(
|
||||
@@ -1511,7 +1511,7 @@ def _deferred_relationship(cls, prop):
|
||||
"When initializing mapper %s, expression %r failed to "
|
||||
"locate a name (%r). If this is a class name, consider "
|
||||
"adding this relationship() to the %r class after "
|
||||
"both dependent classes have been defined." %
|
||||
"both dependent classes have been defined." %
|
||||
(prop.parent, arg, n.args[0], cls)
|
||||
)
|
||||
return return_cls
|
||||
@@ -1582,13 +1582,13 @@ class declared_attr(property):
|
||||
a mapped property or special declarative member name.
|
||||
|
||||
.. versionchanged:: 0.6.{2,3,4}
|
||||
``@declared_attr`` is available as
|
||||
``@declared_attr`` is available as
|
||||
``sqlalchemy.util.classproperty`` for SQLAlchemy versions
|
||||
0.6.2, 0.6.3, 0.6.4.
|
||||
|
||||
@declared_attr turns the attribute into a scalar-like
|
||||
property that can be invoked from the uninstantiated class.
|
||||
Declarative treats attributes specifically marked with
|
||||
Declarative treats attributes specifically marked with
|
||||
@declared_attr as returning a construct that is specific
|
||||
to mapping or declarative table configuration. The name
|
||||
of the attribute is that of what the non-dynamic version
|
||||
@@ -1620,7 +1620,7 @@ class declared_attr(property):
|
||||
def __mapper_args__(cls):
|
||||
if cls.__name__ == 'Employee':
|
||||
return {
|
||||
"polymorphic_on":cls.type,
|
||||
"polymorphic_on":cls.type,
|
||||
"polymorphic_identity":"Employee"
|
||||
}
|
||||
else:
|
||||
@@ -1668,8 +1668,8 @@ def declarative_base(bind=None, metadata=None, mapper=None, cls=object,
|
||||
|
||||
:param bind: An optional
|
||||
:class:`~sqlalchemy.engine.base.Connectable`, will be assigned
|
||||
the ``bind`` attribute on the :class:`~sqlalchemy.MetaData`
|
||||
instance.
|
||||
the ``bind`` attribute on the :class:`~sqlalchemy.MetaData`
|
||||
instance.
|
||||
|
||||
:param metadata:
|
||||
An optional :class:`~sqlalchemy.MetaData` instance. All
|
||||
@@ -1700,11 +1700,11 @@ def declarative_base(bind=None, metadata=None, mapper=None, cls=object,
|
||||
no __init__ will be provided and construction will fall back to
|
||||
cls.__init__ by way of the normal Python semantics.
|
||||
|
||||
:param class_registry: optional dictionary that will serve as the
|
||||
:param class_registry: optional dictionary that will serve as the
|
||||
registry of class names-> mapped classes when string names
|
||||
are used to identify classes inside of :func:`.relationship`
|
||||
are used to identify classes inside of :func:`.relationship`
|
||||
and others. Allows two or more declarative base classes
|
||||
to share the same registry of class names for simplified
|
||||
to share the same registry of class names for simplified
|
||||
inter-base relationships.
|
||||
|
||||
:param metaclass:
|
||||
@@ -1759,7 +1759,7 @@ class ConcreteBase(object):
|
||||
employee_id = Column(Integer, primary_key=True)
|
||||
name = Column(String(50))
|
||||
__mapper_args__ = {
|
||||
'polymorphic_identity':'employee',
|
||||
'polymorphic_identity':'employee',
|
||||
'concrete':True}
|
||||
|
||||
class Manager(Employee):
|
||||
@@ -1768,7 +1768,7 @@ class ConcreteBase(object):
|
||||
name = Column(String(50))
|
||||
manager_data = Column(String(40))
|
||||
__mapper_args__ = {
|
||||
'polymorphic_identity':'manager',
|
||||
'polymorphic_identity':'manager',
|
||||
'concrete':True}
|
||||
|
||||
"""
|
||||
@@ -1817,7 +1817,7 @@ class AbstractConcreteBase(ConcreteBase):
|
||||
name = Column(String(50))
|
||||
manager_data = Column(String(40))
|
||||
__mapper_args__ = {
|
||||
'polymorphic_identity':'manager',
|
||||
'polymorphic_identity':'manager',
|
||||
'concrete':True}
|
||||
|
||||
"""
|
||||
@@ -1851,7 +1851,7 @@ class AbstractConcreteBase(ConcreteBase):
|
||||
|
||||
|
||||
class DeferredReflection(object):
|
||||
"""A helper class for construction of mappings based on
|
||||
"""A helper class for construction of mappings based on
|
||||
a deferred reflection step.
|
||||
|
||||
Normally, declarative can be used with reflection by
|
||||
@@ -1882,9 +1882,9 @@ class DeferredReflection(object):
|
||||
DeferredReflection.prepare(engine)
|
||||
|
||||
The :class:`.DeferredReflection` mixin can be applied to individual
|
||||
classes, used as the base for the declarative base itself,
|
||||
classes, used as the base for the declarative base itself,
|
||||
or used in a custom abstract class. Using an abstract base
|
||||
allows that only a subset of classes to be prepared for a
|
||||
allows that only a subset of classes to be prepared for a
|
||||
particular prepare step, which is necessary for applications
|
||||
that use more than one engine. For example, if an application
|
||||
has two engines, you might use two bases, and prepare each
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
class level and at the instance level.
|
||||
|
||||
The :mod:`~sqlalchemy.ext.hybrid` extension provides a special form of method
|
||||
decorator, is around 50 lines of code and has almost no dependencies on the rest
|
||||
of SQLAlchemy. It can, in theory, work with any descriptor-based expression
|
||||
decorator, is around 50 lines of code and has almost no dependencies on the rest
|
||||
of SQLAlchemy. It can, in theory, work with any descriptor-based expression
|
||||
system.
|
||||
|
||||
Consider a mapping ``Interval``, representing integer ``start`` and ``end``
|
||||
@@ -25,9 +25,9 @@ as the class itself::
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import Session, aliased
|
||||
from sqlalchemy.ext.hybrid import hybrid_property, hybrid_method
|
||||
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
class Interval(Base):
|
||||
__tablename__ = 'interval'
|
||||
|
||||
@@ -50,7 +50,7 @@ as the class itself::
|
||||
@hybrid_method
|
||||
def intersects(self, other):
|
||||
return self.contains(other.start) | self.contains(other.end)
|
||||
|
||||
|
||||
Above, the ``length`` property returns the difference between the ``end`` and
|
||||
``start`` attributes. With an instance of ``Interval``, this subtraction occurs
|
||||
in Python, using normal Python descriptor mechanics::
|
||||
@@ -60,33 +60,33 @@ in Python, using normal Python descriptor mechanics::
|
||||
5
|
||||
|
||||
When dealing with the ``Interval`` class itself, the :class:`.hybrid_property`
|
||||
descriptor evaluates the function body given the ``Interval`` class as
|
||||
descriptor evaluates the function body given the ``Interval`` class as
|
||||
the argument, which when evaluated with SQLAlchemy expression mechanics
|
||||
returns a new SQL expression::
|
||||
|
||||
|
||||
>>> print Interval.length
|
||||
interval."end" - interval.start
|
||||
|
||||
|
||||
>>> print Session().query(Interval).filter(Interval.length > 10)
|
||||
SELECT interval.id AS interval_id, interval.start AS interval_start,
|
||||
interval."end" AS interval_end
|
||||
FROM interval
|
||||
SELECT interval.id AS interval_id, interval.start AS interval_start,
|
||||
interval."end" AS interval_end
|
||||
FROM interval
|
||||
WHERE interval."end" - interval.start > :param_1
|
||||
|
||||
ORM methods such as :meth:`~.Query.filter_by` generally use ``getattr()`` to
|
||||
|
||||
ORM methods such as :meth:`~.Query.filter_by` generally use ``getattr()`` to
|
||||
locate attributes, so can also be used with hybrid attributes::
|
||||
|
||||
>>> print Session().query(Interval).filter_by(length=5)
|
||||
SELECT interval.id AS interval_id, interval.start AS interval_start,
|
||||
interval."end" AS interval_end
|
||||
FROM interval
|
||||
SELECT interval.id AS interval_id, interval.start AS interval_start,
|
||||
interval."end" AS interval_end
|
||||
FROM interval
|
||||
WHERE interval."end" - interval.start = :param_1
|
||||
|
||||
The ``Interval`` class example also illustrates two methods, ``contains()`` and ``intersects()``,
|
||||
decorated with :class:`.hybrid_method`.
|
||||
This decorator applies the same idea to methods that :class:`.hybrid_property` applies
|
||||
to attributes. The methods return boolean values, and take advantage
|
||||
of the Python ``|`` and ``&`` bitwise operators to produce equivalent instance-level and
|
||||
to attributes. The methods return boolean values, and take advantage
|
||||
of the Python ``|`` and ``&`` bitwise operators to produce equivalent instance-level and
|
||||
SQL expression-level boolean behavior::
|
||||
|
||||
>>> i1.contains(6)
|
||||
@@ -97,24 +97,24 @@ SQL expression-level boolean behavior::
|
||||
True
|
||||
>>> i1.intersects(Interval(25, 29))
|
||||
False
|
||||
|
||||
|
||||
>>> print Session().query(Interval).filter(Interval.contains(15))
|
||||
SELECT interval.id AS interval_id, interval.start AS interval_start,
|
||||
interval."end" AS interval_end
|
||||
FROM interval
|
||||
SELECT interval.id AS interval_id, interval.start AS interval_start,
|
||||
interval."end" AS interval_end
|
||||
FROM interval
|
||||
WHERE interval.start <= :start_1 AND interval."end" > :end_1
|
||||
|
||||
>>> ia = aliased(Interval)
|
||||
>>> print Session().query(Interval, ia).filter(Interval.intersects(ia))
|
||||
SELECT interval.id AS interval_id, interval.start AS interval_start,
|
||||
interval."end" AS interval_end, interval_1.id AS interval_1_id,
|
||||
interval_1.start AS interval_1_start, interval_1."end" AS interval_1_end
|
||||
FROM interval, interval AS interval_1
|
||||
WHERE interval.start <= interval_1.start
|
||||
AND interval."end" > interval_1.start
|
||||
OR interval.start <= interval_1."end"
|
||||
SELECT interval.id AS interval_id, interval.start AS interval_start,
|
||||
interval."end" AS interval_end, interval_1.id AS interval_1_id,
|
||||
interval_1.start AS interval_1_start, interval_1."end" AS interval_1_end
|
||||
FROM interval, interval AS interval_1
|
||||
WHERE interval.start <= interval_1.start
|
||||
AND interval."end" > interval_1.start
|
||||
OR interval.start <= interval_1."end"
|
||||
AND interval."end" > interval_1."end"
|
||||
|
||||
|
||||
Defining Expression Behavior Distinct from Attribute Behavior
|
||||
--------------------------------------------------------------
|
||||
|
||||
@@ -122,18 +122,18 @@ Our usage of the ``&`` and ``|`` bitwise operators above was fortunate, consider
|
||||
our functions operated on two boolean values to return a new one. In many cases, the construction
|
||||
of an in-Python function and a SQLAlchemy SQL expression have enough differences that two
|
||||
separate Python expressions should be defined. The :mod:`~sqlalchemy.ext.hybrid` decorators
|
||||
define the :meth:`.hybrid_property.expression` modifier for this purpose. As an example we'll
|
||||
define the :meth:`.hybrid_property.expression` modifier for this purpose. As an example we'll
|
||||
define the radius of the interval, which requires the usage of the absolute value function::
|
||||
|
||||
from sqlalchemy import func
|
||||
|
||||
|
||||
class Interval(object):
|
||||
# ...
|
||||
|
||||
|
||||
@hybrid_property
|
||||
def radius(self):
|
||||
return abs(self.length) / 2
|
||||
|
||||
|
||||
@radius.expression
|
||||
def radius(cls):
|
||||
return func.abs(cls.length) / 2
|
||||
@@ -143,22 +143,22 @@ Above the Python function ``abs()`` is used for instance-level operations, the S
|
||||
|
||||
>>> i1.radius
|
||||
2
|
||||
|
||||
|
||||
>>> print Session().query(Interval).filter(Interval.radius > 5)
|
||||
SELECT interval.id AS interval_id, interval.start AS interval_start,
|
||||
interval."end" AS interval_end
|
||||
FROM interval
|
||||
SELECT interval.id AS interval_id, interval.start AS interval_start,
|
||||
interval."end" AS interval_end
|
||||
FROM interval
|
||||
WHERE abs(interval."end" - interval.start) / :abs_1 > :param_1
|
||||
|
||||
Defining Setters
|
||||
----------------
|
||||
|
||||
Hybrid properties can also define setter methods. If we wanted ``length`` above, when
|
||||
Hybrid properties can also define setter methods. If we wanted ``length`` above, when
|
||||
set, to modify the endpoint value::
|
||||
|
||||
class Interval(object):
|
||||
# ...
|
||||
|
||||
|
||||
@hybrid_property
|
||||
def length(self):
|
||||
return self.end - self.start
|
||||
@@ -179,7 +179,7 @@ The ``length(self, value)`` method is now called upon set::
|
||||
Working with Relationships
|
||||
--------------------------
|
||||
|
||||
There's no essential difference when creating hybrids that work with related objects as
|
||||
There's no essential difference when creating hybrids that work with related objects as
|
||||
opposed to column-based data. The need for distinct expressions tends to be greater.
|
||||
Consider the following declarative mapping which relates a ``User`` to a ``SavingsAccount``::
|
||||
|
||||
@@ -187,9 +187,9 @@ Consider the following declarative mapping which relates a ``User`` to a ``Savin
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.ext.hybrid import hybrid_property
|
||||
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
class SavingsAccount(Base):
|
||||
__tablename__ = 'account'
|
||||
id = Column(Integer, primary_key=True)
|
||||
@@ -200,9 +200,9 @@ Consider the following declarative mapping which relates a ``User`` to a ``Savin
|
||||
__tablename__ = 'user'
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String(100), nullable=False)
|
||||
|
||||
|
||||
accounts = relationship("SavingsAccount", backref="owner")
|
||||
|
||||
|
||||
@hybrid_property
|
||||
def balance(self):
|
||||
if self.accounts:
|
||||
@@ -222,17 +222,17 @@ Consider the following declarative mapping which relates a ``User`` to a ``Savin
|
||||
def balance(cls):
|
||||
return SavingsAccount.balance
|
||||
|
||||
The above hybrid property ``balance`` works with the first ``SavingsAccount`` entry in the list of
|
||||
The above hybrid property ``balance`` works with the first ``SavingsAccount`` entry in the list of
|
||||
accounts for this user. The in-Python getter/setter methods can treat ``accounts`` as a Python
|
||||
list available on ``self``.
|
||||
list available on ``self``.
|
||||
|
||||
However, at the expression level, we can't travel along relationships to column attributes
|
||||
directly since SQLAlchemy is explicit about joins. So here, it's expected that the ``User`` class will be
|
||||
However, at the expression level, we can't travel along relationships to column attributes
|
||||
directly since SQLAlchemy is explicit about joins. So here, it's expected that the ``User`` class will be
|
||||
used in an appropriate context such that an appropriate join to ``SavingsAccount`` will be present::
|
||||
|
||||
>>> print Session().query(User, User.balance).join(User.accounts).filter(User.balance > 5000)
|
||||
SELECT "user".id AS user_id, "user".name AS user_name, account.balance AS account_balance
|
||||
FROM "user" JOIN account ON "user".id = account.user_id
|
||||
FROM "user" JOIN account ON "user".id = account.user_id
|
||||
WHERE account.balance > :balance_1
|
||||
|
||||
Note however, that while the instance level accessors need to worry about whether ``self.accounts``
|
||||
@@ -242,8 +242,8 @@ would use an outer join::
|
||||
>>> from sqlalchemy import or_
|
||||
>>> print (Session().query(User, User.balance).outerjoin(User.accounts).
|
||||
... filter(or_(User.balance < 5000, User.balance == None)))
|
||||
SELECT "user".id AS user_id, "user".name AS user_name, account.balance AS account_balance
|
||||
FROM "user" LEFT OUTER JOIN account ON "user".id = account.user_id
|
||||
SELECT "user".id AS user_id, "user".name AS user_name, account.balance AS account_balance
|
||||
FROM "user" LEFT OUTER JOIN account ON "user".id = account.user_id
|
||||
WHERE account.balance < :balance_1 OR account.balance IS NULL
|
||||
|
||||
.. _hybrid_custom_comparators:
|
||||
@@ -253,7 +253,7 @@ Building Custom Comparators
|
||||
|
||||
The hybrid property also includes a helper that allows construction of custom comparators.
|
||||
A comparator object allows one to customize the behavior of each SQLAlchemy expression
|
||||
operator individually. They are useful when creating custom types that have
|
||||
operator individually. They are useful when creating custom types that have
|
||||
some highly idiosyncratic behavior on the SQL side.
|
||||
|
||||
The example class below allows case-insensitive comparisons on the attribute
|
||||
@@ -263,9 +263,9 @@ named ``word_insensitive``::
|
||||
from sqlalchemy import func, Column, Integer, String
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
class CaseInsensitiveComparator(Comparator):
|
||||
def __eq__(self, other):
|
||||
return func.lower(self.__clause_element__()) == func.lower(other)
|
||||
@@ -274,27 +274,27 @@ named ``word_insensitive``::
|
||||
__tablename__ = 'searchword'
|
||||
id = Column(Integer, primary_key=True)
|
||||
word = Column(String(255), nullable=False)
|
||||
|
||||
|
||||
@hybrid_property
|
||||
def word_insensitive(self):
|
||||
return self.word.lower()
|
||||
|
||||
|
||||
@word_insensitive.comparator
|
||||
def word_insensitive(cls):
|
||||
return CaseInsensitiveComparator(cls.word)
|
||||
|
||||
Above, SQL expressions against ``word_insensitive`` will apply the ``LOWER()``
|
||||
Above, SQL expressions against ``word_insensitive`` will apply the ``LOWER()``
|
||||
SQL function to both sides::
|
||||
|
||||
>>> print Session().query(SearchWord).filter_by(word_insensitive="Trucks")
|
||||
SELECT searchword.id AS searchword_id, searchword.word AS searchword_word
|
||||
FROM searchword
|
||||
SELECT searchword.id AS searchword_id, searchword.word AS searchword_word
|
||||
FROM searchword
|
||||
WHERE lower(searchword.word) = lower(:lower_1)
|
||||
|
||||
The ``CaseInsensitiveComparator`` above implements part of the :class:`.ColumnOperators`
|
||||
interface. A "coercion" operation like lowercasing can be applied to all comparison operations
|
||||
(i.e. ``eq``, ``lt``, ``gt``, etc.) using :meth:`.Operators.operate`::
|
||||
|
||||
|
||||
class CaseInsensitiveComparator(Comparator):
|
||||
def operate(self, op, other):
|
||||
return op(func.lower(self.__clause_element__()), func.lower(other))
|
||||
@@ -310,7 +310,7 @@ by ``@word_insensitive.comparator``, only applies to the SQL side.
|
||||
A more comprehensive form of the custom comparator is to construct a *Hybrid Value Object*.
|
||||
This technique applies the target value or expression to a value object which is then
|
||||
returned by the accessor in all cases. The value object allows control
|
||||
of all operations upon the value as well as how compared values are treated, both
|
||||
of all operations upon the value as well as how compared values are treated, both
|
||||
on the SQL expression side as well as the Python value side. Replacing the
|
||||
previous ``CaseInsensitiveComparator`` class with a new ``CaseInsensitiveWord`` class::
|
||||
|
||||
@@ -342,8 +342,8 @@ previous ``CaseInsensitiveComparator`` class with a new ``CaseInsensitiveWord``
|
||||
Above, the ``CaseInsensitiveWord`` object represents ``self.word``, which may be a SQL function,
|
||||
or may be a Python native. By overriding ``operate()`` and ``__clause_element__()``
|
||||
to work in terms of ``self.word``, all comparison operations will work against the
|
||||
"converted" form of ``word``, whether it be SQL side or Python side.
|
||||
Our ``SearchWord`` class can now deliver the ``CaseInsensitiveWord`` object unconditionally
|
||||
"converted" form of ``word``, whether it be SQL side or Python side.
|
||||
Our ``SearchWord`` class can now deliver the ``CaseInsensitiveWord`` object unconditionally
|
||||
from a single hybrid call::
|
||||
|
||||
class SearchWord(Base):
|
||||
@@ -356,12 +356,12 @@ from a single hybrid call::
|
||||
return CaseInsensitiveWord(self.word)
|
||||
|
||||
The ``word_insensitive`` attribute now has case-insensitive comparison behavior
|
||||
universally, including SQL expression vs. Python expression (note the Python value is
|
||||
universally, including SQL expression vs. Python expression (note the Python value is
|
||||
converted to lower case on the Python side here)::
|
||||
|
||||
>>> print Session().query(SearchWord).filter_by(word_insensitive="Trucks")
|
||||
SELECT searchword.id AS searchword_id, searchword.word AS searchword_word
|
||||
FROM searchword
|
||||
SELECT searchword.id AS searchword_id, searchword.word AS searchword_word
|
||||
FROM searchword
|
||||
WHERE lower(searchword.word) = :lower_1
|
||||
|
||||
SQL expression versus SQL expression::
|
||||
@@ -369,13 +369,13 @@ SQL expression versus SQL expression::
|
||||
>>> sw1 = aliased(SearchWord)
|
||||
>>> sw2 = aliased(SearchWord)
|
||||
>>> print Session().query(
|
||||
... sw1.word_insensitive,
|
||||
... sw1.word_insensitive,
|
||||
... sw2.word_insensitive).\\
|
||||
... filter(
|
||||
... sw1.word_insensitive > sw2.word_insensitive
|
||||
... )
|
||||
SELECT lower(searchword_1.word) AS lower_1, lower(searchword_2.word) AS lower_2
|
||||
FROM searchword AS searchword_1, searchword AS searchword_2
|
||||
SELECT lower(searchword_1.word) AS lower_1, lower(searchword_2.word) AS lower_2
|
||||
FROM searchword AS searchword_1, searchword AS searchword_2
|
||||
WHERE lower(searchword_1.word) > lower(searchword_2.word)
|
||||
|
||||
Python only expression::
|
||||
@@ -403,7 +403,7 @@ Building Transformers
|
||||
----------------------
|
||||
|
||||
A *transformer* is an object which can receive a :class:`.Query` object and return a
|
||||
new one. The :class:`.Query` object includes a method :meth:`.with_transformation`
|
||||
new one. The :class:`.Query` object includes a method :meth:`.with_transformation`
|
||||
that simply returns a new :class:`.Query` transformed by the given function.
|
||||
|
||||
We can combine this with the :class:`.Comparator` class to produce one type
|
||||
@@ -412,18 +412,18 @@ filtering criterion.
|
||||
|
||||
Consider a mapped class ``Node``, which assembles using adjacency list into a hierarchical
|
||||
tree pattern::
|
||||
|
||||
|
||||
from sqlalchemy import Column, Integer, ForeignKey
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
class Node(Base):
|
||||
__tablename__ = 'node'
|
||||
id =Column(Integer, primary_key=True)
|
||||
parent_id = Column(Integer, ForeignKey('node.id'))
|
||||
parent = relationship("Node", remote_side=id)
|
||||
|
||||
|
||||
Suppose we wanted to add an accessor ``grandparent``. This would return the ``parent`` of
|
||||
``Node.parent``. When we have an instance of ``Node``, this is simple::
|
||||
|
||||
@@ -431,7 +431,7 @@ Suppose we wanted to add an accessor ``grandparent``. This would return the ``p
|
||||
|
||||
class Node(Base):
|
||||
# ...
|
||||
|
||||
|
||||
@hybrid_property
|
||||
def grandparent(self):
|
||||
return self.parent.parent
|
||||
@@ -460,7 +460,7 @@ attribute and filtered based on the given criterion::
|
||||
id =Column(Integer, primary_key=True)
|
||||
parent_id = Column(Integer, ForeignKey('node.id'))
|
||||
parent = relationship("Node", remote_side=id)
|
||||
|
||||
|
||||
@hybrid_property
|
||||
def grandparent(self):
|
||||
return self.parent.parent
|
||||
@@ -486,8 +486,8 @@ using :attr:`.Operators.eq` against the left and right sides, passing into
|
||||
{sql}>>> session.query(Node).\\
|
||||
... with_transformation(Node.grandparent==Node(id=5)).\\
|
||||
... all()
|
||||
SELECT node.id AS node_id, node.parent_id AS node_parent_id
|
||||
FROM node JOIN node AS node_1 ON node_1.id = node.parent_id
|
||||
SELECT node.id AS node_id, node.parent_id AS node_parent_id
|
||||
FROM node JOIN node AS node_1 ON node_1.id = node.parent_id
|
||||
WHERE :param_1 = node_1.parent_id
|
||||
{stop}
|
||||
|
||||
@@ -529,14 +529,14 @@ with each class::
|
||||
{sql}>>> session.query(Node).\\
|
||||
... with_transformation(Node.grandparent.join).\\
|
||||
... filter(Node.grandparent==Node(id=5))
|
||||
SELECT node.id AS node_id, node.parent_id AS node_parent_id
|
||||
FROM node JOIN node AS node_1 ON node_1.id = node.parent_id
|
||||
SELECT node.id AS node_id, node.parent_id AS node_parent_id
|
||||
FROM node JOIN node AS node_1 ON node_1.id = node.parent_id
|
||||
WHERE :param_1 = node_1.parent_id
|
||||
{stop}
|
||||
|
||||
The "transformer" pattern is an experimental pattern that starts
|
||||
to make usage of some functional programming paradigms.
|
||||
While it's only recommended for advanced and/or patient developers,
|
||||
While it's only recommended for advanced and/or patient developers,
|
||||
there's probably a whole lot of amazing things it can be used for.
|
||||
|
||||
"""
|
||||
@@ -546,26 +546,26 @@ from sqlalchemy.orm import attributes, interfaces
|
||||
class hybrid_method(object):
|
||||
"""A decorator which allows definition of a Python object method with both
|
||||
instance-level and class-level behavior.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def __init__(self, func, expr=None):
|
||||
"""Create a new :class:`.hybrid_method`.
|
||||
|
||||
|
||||
Usage is typically via decorator::
|
||||
|
||||
|
||||
from sqlalchemy.ext.hybrid import hybrid_method
|
||||
|
||||
|
||||
class SomeClass(object):
|
||||
@hybrid_method
|
||||
def value(self, x, y):
|
||||
return self._value + x + y
|
||||
|
||||
|
||||
@value.expression
|
||||
def value(self, x, y):
|
||||
return func.some_function(self._value, x, y)
|
||||
|
||||
|
||||
"""
|
||||
self.func = func
|
||||
self.expr = expr or func
|
||||
@@ -585,25 +585,25 @@ class hybrid_method(object):
|
||||
class hybrid_property(object):
|
||||
"""A decorator which allows definition of a Python descriptor with both
|
||||
instance-level and class-level behavior.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, fget, fset=None, fdel=None, expr=None):
|
||||
"""Create a new :class:`.hybrid_property`.
|
||||
|
||||
|
||||
Usage is typically via decorator::
|
||||
|
||||
|
||||
from sqlalchemy.ext.hybrid import hybrid_property
|
||||
|
||||
|
||||
class SomeClass(object):
|
||||
@hybrid_property
|
||||
def value(self):
|
||||
return self._value
|
||||
|
||||
|
||||
@value.setter
|
||||
def value(self, value):
|
||||
self._value = value
|
||||
|
||||
|
||||
"""
|
||||
self.fget = fget
|
||||
self.fset = fset
|
||||
@@ -647,10 +647,10 @@ class hybrid_property(object):
|
||||
|
||||
def comparator(self, comparator):
|
||||
"""Provide a modifying decorator that defines a custom comparator producing method.
|
||||
|
||||
|
||||
The return value of the decorated method should be an instance of
|
||||
:class:`~.hybrid.Comparator`.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
proxy_attr = attributes.\
|
||||
@@ -660,11 +660,11 @@ class hybrid_property(object):
|
||||
self.expr = expr
|
||||
return self
|
||||
|
||||
|
||||
class Comparator(interfaces.PropComparator):
|
||||
"""A helper class that allows easy construction of custom :class:`~.orm.interfaces.PropComparator`
|
||||
classes for usage with hybrids."""
|
||||
|
||||
property = None
|
||||
|
||||
def __init__(self, expression):
|
||||
self.expression = expression
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"""Base inspect API.
|
||||
|
||||
:func:`.inspect` provides access to a contextual object
|
||||
regarding a subject.
|
||||
regarding a subject.
|
||||
|
||||
Various subsections of SQLAlchemy,
|
||||
such as the :class:`.Inspector`, :class:`.Mapper`, and
|
||||
@@ -16,21 +16,28 @@ so that they may return a context object given a certain kind
|
||||
of argument.
|
||||
"""
|
||||
|
||||
from . import util
|
||||
from . import util, exc
|
||||
_registrars = util.defaultdict(list)
|
||||
|
||||
def inspect(subject):
|
||||
def inspect(subject, raiseerr=True):
|
||||
type_ = type(subject)
|
||||
for cls in type_.__mro__:
|
||||
if cls in _registrars:
|
||||
reg = _registrars[cls]
|
||||
break
|
||||
ret = reg(subject)
|
||||
if ret is not None:
|
||||
break
|
||||
else:
|
||||
raise exc.InvalidRequestError(
|
||||
"No inspection system is "
|
||||
"available for object of type %s" %
|
||||
type_)
|
||||
return reg(subject)
|
||||
reg = ret = None
|
||||
|
||||
if raiseerr and (
|
||||
reg is None or ret is None
|
||||
):
|
||||
raise exc.NoInspectionAvailable(
|
||||
"No inspection system is "
|
||||
"available for object of type %s" %
|
||||
type_)
|
||||
return ret
|
||||
|
||||
def _inspects(*types):
|
||||
def decorate(fn_or_cls):
|
||||
@@ -42,3 +49,6 @@ def _inspects(*types):
|
||||
_registrars[type_] = fn_or_cls
|
||||
return fn_or_cls
|
||||
return decorate
|
||||
|
||||
def _self_inspects(*types):
|
||||
_inspects(*types)(lambda subject:subject)
|
||||
+231
-231
File diff suppressed because it is too large
Load Diff
+173
-173
@@ -91,11 +91,11 @@ class InstanceEvents(event.Events):
|
||||
When using :class:`.InstanceEvents`, several modifiers are
|
||||
available to the :func:`.event.listen` function.
|
||||
|
||||
:param propagate=False: When True, the event listener should
|
||||
be applied to all inheriting mappers as well as the
|
||||
:param propagate=False: When True, the event listener should
|
||||
be applied to all inheriting mappers as well as the
|
||||
mapper which is the target of this listener.
|
||||
:param raw=False: When True, the "target" argument passed
|
||||
to applicable event listener functions will be the
|
||||
to applicable event listener functions will be the
|
||||
instance's :class:`.InstanceState` management
|
||||
object, rather than the mapped instance itself.
|
||||
|
||||
@@ -142,17 +142,17 @@ class InstanceEvents(event.Events):
|
||||
def init(self, target, args, kwargs):
|
||||
"""Receive an instance when it's constructor is called.
|
||||
|
||||
This method is only called during a userland construction of
|
||||
This method is only called during a userland construction of
|
||||
an object. It is not called when an object is loaded from the
|
||||
database.
|
||||
|
||||
"""
|
||||
|
||||
def init_failure(self, target, args, kwargs):
|
||||
"""Receive an instance when it's constructor has been called,
|
||||
"""Receive an instance when it's constructor has been called,
|
||||
and raised an exception.
|
||||
|
||||
This method is only called during a userland construction of
|
||||
This method is only called during a userland construction of
|
||||
an object. It is not called when an object is loaded from the
|
||||
database.
|
||||
|
||||
@@ -168,12 +168,12 @@ class InstanceEvents(event.Events):
|
||||
instance's lifetime.
|
||||
|
||||
Note that during a result-row load, this method is called upon
|
||||
the first row received for this instance. Note that some
|
||||
attributes and collections may or may not be loaded or even
|
||||
the first row received for this instance. Note that some
|
||||
attributes and collections may or may not be loaded or even
|
||||
initialized, depending on what's present in the result rows.
|
||||
|
||||
:param target: the mapped instance. If
|
||||
the event is configured with ``raw=True``, this will
|
||||
:param target: the mapped instance. If
|
||||
the event is configured with ``raw=True``, this will
|
||||
instead be the :class:`.InstanceState` state-management
|
||||
object associated with the instance.
|
||||
:param context: the :class:`.QueryContext` corresponding to the
|
||||
@@ -184,16 +184,16 @@ class InstanceEvents(event.Events):
|
||||
"""
|
||||
|
||||
def refresh(self, target, context, attrs):
|
||||
"""Receive an object instance after one or more attributes have
|
||||
"""Receive an object instance after one or more attributes have
|
||||
been refreshed from a query.
|
||||
|
||||
:param target: the mapped instance. If
|
||||
the event is configured with ``raw=True``, this will
|
||||
:param target: the mapped instance. If
|
||||
the event is configured with ``raw=True``, this will
|
||||
instead be the :class:`.InstanceState` state-management
|
||||
object associated with the instance.
|
||||
:param context: the :class:`.QueryContext` corresponding to the
|
||||
current :class:`.Query` in progress.
|
||||
:param attrs: iterable collection of attribute names which
|
||||
:param attrs: iterable collection of attribute names which
|
||||
were populated, or None if all column-mapped, non-deferred
|
||||
attributes were populated.
|
||||
|
||||
@@ -206,23 +206,23 @@ class InstanceEvents(event.Events):
|
||||
'keys' is a list of attribute names. If None, the entire
|
||||
state was expired.
|
||||
|
||||
:param target: the mapped instance. If
|
||||
the event is configured with ``raw=True``, this will
|
||||
:param target: the mapped instance. If
|
||||
the event is configured with ``raw=True``, this will
|
||||
instead be the :class:`.InstanceState` state-management
|
||||
object associated with the instance.
|
||||
:param attrs: iterable collection of attribute
|
||||
names which were expired, or None if all attributes were
|
||||
names which were expired, or None if all attributes were
|
||||
expired.
|
||||
|
||||
"""
|
||||
|
||||
def resurrect(self, target):
|
||||
"""Receive an object instance as it is 'resurrected' from
|
||||
"""Receive an object instance as it is 'resurrected' from
|
||||
garbage collection, which occurs when a "dirty" state falls
|
||||
out of scope.
|
||||
|
||||
:param target: the mapped instance. If
|
||||
the event is configured with ``raw=True``, this will
|
||||
:param target: the mapped instance. If
|
||||
the event is configured with ``raw=True``, this will
|
||||
instead be the :class:`.InstanceState` state-management
|
||||
object associated with the instance.
|
||||
|
||||
@@ -232,28 +232,28 @@ class InstanceEvents(event.Events):
|
||||
"""Receive an object instance when its associated state is
|
||||
being pickled.
|
||||
|
||||
:param target: the mapped instance. If
|
||||
the event is configured with ``raw=True``, this will
|
||||
:param target: the mapped instance. If
|
||||
the event is configured with ``raw=True``, this will
|
||||
instead be the :class:`.InstanceState` state-management
|
||||
object associated with the instance.
|
||||
:param state_dict: the dictionary returned by
|
||||
:param state_dict: the dictionary returned by
|
||||
:class:`.InstanceState.__getstate__`, containing the state
|
||||
to be pickled.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
def unpickle(self, target, state_dict):
|
||||
"""Receive an object instance after it's associated state has
|
||||
been unpickled.
|
||||
|
||||
:param target: the mapped instance. If
|
||||
the event is configured with ``raw=True``, this will
|
||||
:param target: the mapped instance. If
|
||||
the event is configured with ``raw=True``, this will
|
||||
instead be the :class:`.InstanceState` state-management
|
||||
object associated with the instance.
|
||||
:param state_dict: the dictionary sent to
|
||||
:class:`.InstanceState.__setstate__`, containing the state
|
||||
dictionary which was pickled.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
class MapperEvents(event.Events):
|
||||
@@ -267,7 +267,7 @@ class MapperEvents(event.Events):
|
||||
# execute a stored procedure upon INSERT,
|
||||
# apply the value to the row to be inserted
|
||||
target.calculated_value = connection.scalar(
|
||||
"select my_special_function(%d)"
|
||||
"select my_special_function(%d)"
|
||||
% target.special_number)
|
||||
|
||||
# associate the listener function with SomeMappedClass,
|
||||
@@ -304,16 +304,16 @@ class MapperEvents(event.Events):
|
||||
When using :class:`.MapperEvents`, several modifiers are
|
||||
available to the :func:`.event.listen` function.
|
||||
|
||||
:param propagate=False: When True, the event listener should
|
||||
be applied to all inheriting mappers as well as the
|
||||
:param propagate=False: When True, the event listener should
|
||||
be applied to all inheriting mappers as well as the
|
||||
mapper which is the target of this listener.
|
||||
:param raw=False: When True, the "target" argument passed
|
||||
to applicable event listener functions will be the
|
||||
to applicable event listener functions will be the
|
||||
instance's :class:`.InstanceState` management
|
||||
object, rather than the mapped instance itself.
|
||||
:param retval=False: when True, the user-defined event function
|
||||
must have a return value, the purpose of which is either to
|
||||
control subsequent event propagation, or to otherwise alter
|
||||
control subsequent event propagation, or to otherwise alter
|
||||
the operation in progress by the mapper. Possible return
|
||||
values are:
|
||||
|
||||
@@ -322,7 +322,7 @@ class MapperEvents(event.Events):
|
||||
* ``sqlalchemy.orm.interfaces.EXT_STOP`` - cancel all subsequent
|
||||
event handlers in the chain.
|
||||
* other values - the return value specified by specific listeners,
|
||||
such as :meth:`~.MapperEvents.translate_row` or
|
||||
such as :meth:`~.MapperEvents.translate_row` or
|
||||
:meth:`~.MapperEvents.create_instance`.
|
||||
|
||||
"""
|
||||
@@ -335,12 +335,12 @@ class MapperEvents(event.Events):
|
||||
if issubclass(target, orm.Mapper):
|
||||
return target
|
||||
else:
|
||||
return orm.class_mapper(target, compile=False)
|
||||
return orm.class_mapper(target, configure=False)
|
||||
else:
|
||||
return target
|
||||
|
||||
@classmethod
|
||||
def _listen(cls, target, identifier, fn,
|
||||
def _listen(cls, target, identifier, fn,
|
||||
raw=False, retval=False, propagate=False):
|
||||
|
||||
if not raw or not retval:
|
||||
@@ -370,7 +370,7 @@ class MapperEvents(event.Events):
|
||||
event.Events._listen(target, identifier, fn)
|
||||
|
||||
def instrument_class(self, mapper, class_):
|
||||
"""Receive a class when the mapper is first constructed,
|
||||
"""Receive a class when the mapper is first constructed,
|
||||
before instrumentation is applied to the mapped class.
|
||||
|
||||
This event is the earliest phase of mapper construction.
|
||||
@@ -404,11 +404,11 @@ class MapperEvents(event.Events):
|
||||
This corresponds to the :func:`.orm.configure_mappers` call, which
|
||||
note is usually called automatically as mappings are first
|
||||
used.
|
||||
|
||||
|
||||
Theoretically this event is called once per
|
||||
application, but is actually called any time new mappers
|
||||
have been affected by a :func:`.orm.configure_mappers` call. If new mappings
|
||||
are constructed after existing ones have already been used,
|
||||
are constructed after existing ones have already been used,
|
||||
this event can be called again.
|
||||
|
||||
"""
|
||||
@@ -420,9 +420,9 @@ class MapperEvents(event.Events):
|
||||
This listener is typically registered with ``retval=True``.
|
||||
It is called when the mapper first receives a row, before
|
||||
the object identity or the instance itself has been derived
|
||||
from that row. The given row may or may not be a
|
||||
from that row. The given row may or may not be a
|
||||
:class:`.RowProxy` object - it will always be a dictionary-like
|
||||
object which contains mapped columns as keys. The
|
||||
object which contains mapped columns as keys. The
|
||||
returned object should also be a dictionary-like object
|
||||
which recognizes mapped columns as keys.
|
||||
|
||||
@@ -431,7 +431,7 @@ class MapperEvents(event.Events):
|
||||
:param context: the :class:`.QueryContext`, which includes
|
||||
a handle to the current :class:`.Query` in progress as well
|
||||
as additional state information.
|
||||
:param row: the result row being handled. This may be
|
||||
:param row: the result row being handled. This may be
|
||||
an actual :class:`.RowProxy` or may be a dictionary containing
|
||||
:class:`.Column` objects as keys.
|
||||
:return: When configured with ``retval=True``, the function
|
||||
@@ -454,18 +454,18 @@ class MapperEvents(event.Events):
|
||||
:param context: the :class:`.QueryContext`, which includes
|
||||
a handle to the current :class:`.Query` in progress as well
|
||||
as additional state information.
|
||||
:param row: the result row being handled. This may be
|
||||
:param row: the result row being handled. This may be
|
||||
an actual :class:`.RowProxy` or may be a dictionary containing
|
||||
:class:`.Column` objects as keys.
|
||||
:param class\_: the mapped class.
|
||||
:return: When configured with ``retval=True``, the return value
|
||||
should be a newly created instance of the mapped class,
|
||||
should be a newly created instance of the mapped class,
|
||||
or ``EXT_CONTINUE`` indicating that default object construction
|
||||
should take place.
|
||||
|
||||
"""
|
||||
|
||||
def append_result(self, mapper, context, row, target,
|
||||
def append_result(self, mapper, context, row, target,
|
||||
result, **flags):
|
||||
"""Receive an object instance before that instance is appended
|
||||
to a result list.
|
||||
@@ -478,27 +478,27 @@ class MapperEvents(event.Events):
|
||||
:param context: the :class:`.QueryContext`, which includes
|
||||
a handle to the current :class:`.Query` in progress as well
|
||||
as additional state information.
|
||||
:param row: the result row being handled. This may be
|
||||
:param row: the result row being handled. This may be
|
||||
an actual :class:`.RowProxy` or may be a dictionary containing
|
||||
:class:`.Column` objects as keys.
|
||||
:param target: the mapped instance being populated. If
|
||||
the event is configured with ``raw=True``, this will
|
||||
:param target: the mapped instance being populated. If
|
||||
the event is configured with ``raw=True``, this will
|
||||
instead be the :class:`.InstanceState` state-management
|
||||
object associated with the instance.
|
||||
:param result: a list-like object where results are being
|
||||
appended.
|
||||
:param \**flags: Additional state information about the
|
||||
:param \**flags: Additional state information about the
|
||||
current handling of the row.
|
||||
:return: If this method is registered with ``retval=True``,
|
||||
a return value of ``EXT_STOP`` will prevent the instance
|
||||
from being appended to the given result list, whereas a
|
||||
from being appended to the given result list, whereas a
|
||||
return value of ``EXT_CONTINUE`` will result in the default
|
||||
behavior of appending the value to the result list.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def populate_instance(self, mapper, context, row,
|
||||
def populate_instance(self, mapper, context, row,
|
||||
target, **flags):
|
||||
"""Receive an instance before that instance has
|
||||
its attributes populated.
|
||||
@@ -518,11 +518,11 @@ class MapperEvents(event.Events):
|
||||
:param context: the :class:`.QueryContext`, which includes
|
||||
a handle to the current :class:`.Query` in progress as well
|
||||
as additional state information.
|
||||
:param row: the result row being handled. This may be
|
||||
:param row: the result row being handled. This may be
|
||||
an actual :class:`.RowProxy` or may be a dictionary containing
|
||||
:class:`.Column` objects as keys.
|
||||
:param target: the mapped instance. If
|
||||
the event is configured with ``raw=True``, this will
|
||||
:param target: the mapped instance. If
|
||||
the event is configured with ``raw=True``, this will
|
||||
instead be the :class:`.InstanceState` state-management
|
||||
object associated with the instance.
|
||||
:return: When configured with ``retval=True``, a return
|
||||
@@ -536,9 +536,9 @@ class MapperEvents(event.Events):
|
||||
"""Receive an object instance before an INSERT statement
|
||||
is emitted corresponding to that instance.
|
||||
|
||||
This event is used to modify local, non-object related
|
||||
This event is used to modify local, non-object related
|
||||
attributes on the instance before an INSERT occurs, as well
|
||||
as to emit additional SQL statements on the given
|
||||
as to emit additional SQL statements on the given
|
||||
connection.
|
||||
|
||||
The event is often called for a batch of objects of the
|
||||
@@ -552,23 +552,23 @@ class MapperEvents(event.Events):
|
||||
|
||||
.. warning::
|
||||
Mapper-level flush events are designed to operate **on attributes
|
||||
local to the immediate object being handled
|
||||
local to the immediate object being handled
|
||||
and via SQL operations with the given** :class:`.Connection` **only.**
|
||||
Handlers here should **not** make alterations to the state of
|
||||
Handlers here should **not** make alterations to the state of
|
||||
the :class:`.Session` overall, and in general should not
|
||||
affect any :func:`.relationship` -mapped attributes, as
|
||||
affect any :func:`.relationship` -mapped attributes, as
|
||||
session cascade rules will not function properly, nor is it
|
||||
always known if the related class has already been handled.
|
||||
always known if the related class has already been handled.
|
||||
Operations that **are not supported in mapper events** include:
|
||||
|
||||
|
||||
* :meth:`.Session.add`
|
||||
* :meth:`.Session.delete`
|
||||
* Mapped collection append, add, remove, delete, discard, etc.
|
||||
* Mapped relationship attribute set/del events, i.e. ``someobject.related = someotherobject``
|
||||
|
||||
|
||||
Operations which manipulate the state of the object
|
||||
relative to other objects are better handled:
|
||||
|
||||
|
||||
* In the ``__init__()`` method of the mapped object itself, or another method
|
||||
designed to establish some particular state.
|
||||
* In a ``@validates`` handler, see :ref:`simple_validators`
|
||||
@@ -576,12 +576,12 @@ class MapperEvents(event.Events):
|
||||
|
||||
:param mapper: the :class:`.Mapper` which is the target
|
||||
of this event.
|
||||
:param connection: the :class:`.Connection` being used to
|
||||
:param connection: the :class:`.Connection` being used to
|
||||
emit INSERT statements for this instance. This
|
||||
provides a handle into the current transaction on the
|
||||
provides a handle into the current transaction on the
|
||||
target database specific to this instance.
|
||||
:param target: the mapped instance being persisted. If
|
||||
the event is configured with ``raw=True``, this will
|
||||
:param target: the mapped instance being persisted. If
|
||||
the event is configured with ``raw=True``, this will
|
||||
instead be the :class:`.InstanceState` state-management
|
||||
object associated with the instance.
|
||||
:return: No return value is supported by this event.
|
||||
@@ -594,7 +594,7 @@ class MapperEvents(event.Events):
|
||||
|
||||
This event is used to modify in-Python-only
|
||||
state on the instance after an INSERT occurs, as well
|
||||
as to emit additional SQL statements on the given
|
||||
as to emit additional SQL statements on the given
|
||||
connection.
|
||||
|
||||
The event is often called for a batch of objects of the
|
||||
@@ -608,23 +608,23 @@ class MapperEvents(event.Events):
|
||||
|
||||
.. warning::
|
||||
Mapper-level flush events are designed to operate **on attributes
|
||||
local to the immediate object being handled
|
||||
local to the immediate object being handled
|
||||
and via SQL operations with the given** :class:`.Connection` **only.**
|
||||
Handlers here should **not** make alterations to the state of
|
||||
Handlers here should **not** make alterations to the state of
|
||||
the :class:`.Session` overall, and in general should not
|
||||
affect any :func:`.relationship` -mapped attributes, as
|
||||
affect any :func:`.relationship` -mapped attributes, as
|
||||
session cascade rules will not function properly, nor is it
|
||||
always known if the related class has already been handled.
|
||||
always known if the related class has already been handled.
|
||||
Operations that **are not supported in mapper events** include:
|
||||
|
||||
|
||||
* :meth:`.Session.add`
|
||||
* :meth:`.Session.delete`
|
||||
* Mapped collection append, add, remove, delete, discard, etc.
|
||||
* Mapped relationship attribute set/del events, i.e. ``someobject.related = someotherobject``
|
||||
|
||||
|
||||
Operations which manipulate the state of the object
|
||||
relative to other objects are better handled:
|
||||
|
||||
|
||||
* In the ``__init__()`` method of the mapped object itself, or another method
|
||||
designed to establish some particular state.
|
||||
* In a ``@validates`` handler, see :ref:`simple_validators`
|
||||
@@ -632,12 +632,12 @@ class MapperEvents(event.Events):
|
||||
|
||||
:param mapper: the :class:`.Mapper` which is the target
|
||||
of this event.
|
||||
:param connection: the :class:`.Connection` being used to
|
||||
:param connection: the :class:`.Connection` being used to
|
||||
emit INSERT statements for this instance. This
|
||||
provides a handle into the current transaction on the
|
||||
provides a handle into the current transaction on the
|
||||
target database specific to this instance.
|
||||
:param target: the mapped instance being persisted. If
|
||||
the event is configured with ``raw=True``, this will
|
||||
:param target: the mapped instance being persisted. If
|
||||
the event is configured with ``raw=True``, this will
|
||||
instead be the :class:`.InstanceState` state-management
|
||||
object associated with the instance.
|
||||
:return: No return value is supported by this event.
|
||||
@@ -648,9 +648,9 @@ class MapperEvents(event.Events):
|
||||
"""Receive an object instance before an UPDATE statement
|
||||
is emitted corresponding to that instance.
|
||||
|
||||
This event is used to modify local, non-object related
|
||||
This event is used to modify local, non-object related
|
||||
attributes on the instance before an UPDATE occurs, as well
|
||||
as to emit additional SQL statements on the given
|
||||
as to emit additional SQL statements on the given
|
||||
connection.
|
||||
|
||||
This method is called for all instances that are
|
||||
@@ -683,23 +683,23 @@ class MapperEvents(event.Events):
|
||||
|
||||
.. warning::
|
||||
Mapper-level flush events are designed to operate **on attributes
|
||||
local to the immediate object being handled
|
||||
local to the immediate object being handled
|
||||
and via SQL operations with the given** :class:`.Connection` **only.**
|
||||
Handlers here should **not** make alterations to the state of
|
||||
Handlers here should **not** make alterations to the state of
|
||||
the :class:`.Session` overall, and in general should not
|
||||
affect any :func:`.relationship` -mapped attributes, as
|
||||
affect any :func:`.relationship` -mapped attributes, as
|
||||
session cascade rules will not function properly, nor is it
|
||||
always known if the related class has already been handled.
|
||||
always known if the related class has already been handled.
|
||||
Operations that **are not supported in mapper events** include:
|
||||
|
||||
|
||||
* :meth:`.Session.add`
|
||||
* :meth:`.Session.delete`
|
||||
* Mapped collection append, add, remove, delete, discard, etc.
|
||||
* Mapped relationship attribute set/del events, i.e. ``someobject.related = someotherobject``
|
||||
|
||||
|
||||
Operations which manipulate the state of the object
|
||||
relative to other objects are better handled:
|
||||
|
||||
|
||||
* In the ``__init__()`` method of the mapped object itself, or another method
|
||||
designed to establish some particular state.
|
||||
* In a ``@validates`` handler, see :ref:`simple_validators`
|
||||
@@ -707,12 +707,12 @@ class MapperEvents(event.Events):
|
||||
|
||||
:param mapper: the :class:`.Mapper` which is the target
|
||||
of this event.
|
||||
:param connection: the :class:`.Connection` being used to
|
||||
:param connection: the :class:`.Connection` being used to
|
||||
emit UPDATE statements for this instance. This
|
||||
provides a handle into the current transaction on the
|
||||
provides a handle into the current transaction on the
|
||||
target database specific to this instance.
|
||||
:param target: the mapped instance being persisted. If
|
||||
the event is configured with ``raw=True``, this will
|
||||
:param target: the mapped instance being persisted. If
|
||||
the event is configured with ``raw=True``, this will
|
||||
instead be the :class:`.InstanceState` state-management
|
||||
object associated with the instance.
|
||||
:return: No return value is supported by this event.
|
||||
@@ -724,12 +724,12 @@ class MapperEvents(event.Events):
|
||||
|
||||
This event is used to modify in-Python-only
|
||||
state on the instance after an UPDATE occurs, as well
|
||||
as to emit additional SQL statements on the given
|
||||
as to emit additional SQL statements on the given
|
||||
connection.
|
||||
|
||||
This method is called for all instances that are
|
||||
marked as "dirty", *even those which have no net changes
|
||||
to their column-based attributes*, and for which
|
||||
to their column-based attributes*, and for which
|
||||
no UPDATE statement has proceeded. An object is marked
|
||||
as dirty when any of its column-based attributes have a
|
||||
"set attribute" operation called or when any of its
|
||||
@@ -756,23 +756,23 @@ class MapperEvents(event.Events):
|
||||
|
||||
.. warning::
|
||||
Mapper-level flush events are designed to operate **on attributes
|
||||
local to the immediate object being handled
|
||||
local to the immediate object being handled
|
||||
and via SQL operations with the given** :class:`.Connection` **only.**
|
||||
Handlers here should **not** make alterations to the state of
|
||||
Handlers here should **not** make alterations to the state of
|
||||
the :class:`.Session` overall, and in general should not
|
||||
affect any :func:`.relationship` -mapped attributes, as
|
||||
affect any :func:`.relationship` -mapped attributes, as
|
||||
session cascade rules will not function properly, nor is it
|
||||
always known if the related class has already been handled.
|
||||
always known if the related class has already been handled.
|
||||
Operations that **are not supported in mapper events** include:
|
||||
|
||||
|
||||
* :meth:`.Session.add`
|
||||
* :meth:`.Session.delete`
|
||||
* Mapped collection append, add, remove, delete, discard, etc.
|
||||
* Mapped relationship attribute set/del events, i.e. ``someobject.related = someotherobject``
|
||||
|
||||
|
||||
Operations which manipulate the state of the object
|
||||
relative to other objects are better handled:
|
||||
|
||||
|
||||
* In the ``__init__()`` method of the mapped object itself, or another method
|
||||
designed to establish some particular state.
|
||||
* In a ``@validates`` handler, see :ref:`simple_validators`
|
||||
@@ -780,12 +780,12 @@ class MapperEvents(event.Events):
|
||||
|
||||
:param mapper: the :class:`.Mapper` which is the target
|
||||
of this event.
|
||||
:param connection: the :class:`.Connection` being used to
|
||||
:param connection: the :class:`.Connection` being used to
|
||||
emit UPDATE statements for this instance. This
|
||||
provides a handle into the current transaction on the
|
||||
provides a handle into the current transaction on the
|
||||
target database specific to this instance.
|
||||
:param target: the mapped instance being persisted. If
|
||||
the event is configured with ``raw=True``, this will
|
||||
:param target: the mapped instance being persisted. If
|
||||
the event is configured with ``raw=True``, this will
|
||||
instead be the :class:`.InstanceState` state-management
|
||||
object associated with the instance.
|
||||
:return: No return value is supported by this event.
|
||||
@@ -796,33 +796,33 @@ class MapperEvents(event.Events):
|
||||
"""Receive an object instance before a DELETE statement
|
||||
is emitted corresponding to that instance.
|
||||
|
||||
This event is used to emit additional SQL statements on
|
||||
This event is used to emit additional SQL statements on
|
||||
the given connection as well as to perform application
|
||||
specific bookkeeping related to a deletion event.
|
||||
|
||||
The event is often called for a batch of objects of the
|
||||
same class before their DELETE statements are emitted at
|
||||
once in a later step.
|
||||
once in a later step.
|
||||
|
||||
.. warning::
|
||||
Mapper-level flush events are designed to operate **on attributes
|
||||
local to the immediate object being handled
|
||||
local to the immediate object being handled
|
||||
and via SQL operations with the given** :class:`.Connection` **only.**
|
||||
Handlers here should **not** make alterations to the state of
|
||||
Handlers here should **not** make alterations to the state of
|
||||
the :class:`.Session` overall, and in general should not
|
||||
affect any :func:`.relationship` -mapped attributes, as
|
||||
affect any :func:`.relationship` -mapped attributes, as
|
||||
session cascade rules will not function properly, nor is it
|
||||
always known if the related class has already been handled.
|
||||
always known if the related class has already been handled.
|
||||
Operations that **are not supported in mapper events** include:
|
||||
|
||||
|
||||
* :meth:`.Session.add`
|
||||
* :meth:`.Session.delete`
|
||||
* Mapped collection append, add, remove, delete, discard, etc.
|
||||
* Mapped relationship attribute set/del events, i.e. ``someobject.related = someotherobject``
|
||||
|
||||
|
||||
Operations which manipulate the state of the object
|
||||
relative to other objects are better handled:
|
||||
|
||||
|
||||
* In the ``__init__()`` method of the mapped object itself, or another method
|
||||
designed to establish some particular state.
|
||||
* In a ``@validates`` handler, see :ref:`simple_validators`
|
||||
@@ -830,12 +830,12 @@ class MapperEvents(event.Events):
|
||||
|
||||
:param mapper: the :class:`.Mapper` which is the target
|
||||
of this event.
|
||||
:param connection: the :class:`.Connection` being used to
|
||||
:param connection: the :class:`.Connection` being used to
|
||||
emit DELETE statements for this instance. This
|
||||
provides a handle into the current transaction on the
|
||||
provides a handle into the current transaction on the
|
||||
target database specific to this instance.
|
||||
:param target: the mapped instance being deleted. If
|
||||
the event is configured with ``raw=True``, this will
|
||||
:param target: the mapped instance being deleted. If
|
||||
the event is configured with ``raw=True``, this will
|
||||
instead be the :class:`.InstanceState` state-management
|
||||
object associated with the instance.
|
||||
:return: No return value is supported by this event.
|
||||
@@ -846,33 +846,33 @@ class MapperEvents(event.Events):
|
||||
"""Receive an object instance after a DELETE statement
|
||||
has been emitted corresponding to that instance.
|
||||
|
||||
This event is used to emit additional SQL statements on
|
||||
This event is used to emit additional SQL statements on
|
||||
the given connection as well as to perform application
|
||||
specific bookkeeping related to a deletion event.
|
||||
|
||||
The event is often called for a batch of objects of the
|
||||
same class after their DELETE statements have been emitted at
|
||||
once in a previous step.
|
||||
once in a previous step.
|
||||
|
||||
.. warning::
|
||||
Mapper-level flush events are designed to operate **on attributes
|
||||
local to the immediate object being handled
|
||||
local to the immediate object being handled
|
||||
and via SQL operations with the given** :class:`.Connection` **only.**
|
||||
Handlers here should **not** make alterations to the state of
|
||||
Handlers here should **not** make alterations to the state of
|
||||
the :class:`.Session` overall, and in general should not
|
||||
affect any :func:`.relationship` -mapped attributes, as
|
||||
affect any :func:`.relationship` -mapped attributes, as
|
||||
session cascade rules will not function properly, nor is it
|
||||
always known if the related class has already been handled.
|
||||
always known if the related class has already been handled.
|
||||
Operations that **are not supported in mapper events** include:
|
||||
|
||||
|
||||
* :meth:`.Session.add`
|
||||
* :meth:`.Session.delete`
|
||||
* Mapped collection append, add, remove, delete, discard, etc.
|
||||
* Mapped relationship attribute set/del events, i.e. ``someobject.related = someotherobject``
|
||||
|
||||
|
||||
Operations which manipulate the state of the object
|
||||
relative to other objects are better handled:
|
||||
|
||||
|
||||
* In the ``__init__()`` method of the mapped object itself, or another method
|
||||
designed to establish some particular state.
|
||||
* In a ``@validates`` handler, see :ref:`simple_validators`
|
||||
@@ -880,12 +880,12 @@ class MapperEvents(event.Events):
|
||||
|
||||
:param mapper: the :class:`.Mapper` which is the target
|
||||
of this event.
|
||||
:param connection: the :class:`.Connection` being used to
|
||||
:param connection: the :class:`.Connection` being used to
|
||||
emit DELETE statements for this instance. This
|
||||
provides a handle into the current transaction on the
|
||||
provides a handle into the current transaction on the
|
||||
target database specific to this instance.
|
||||
:param target: the mapped instance being deleted. If
|
||||
the event is configured with ``raw=True``, this will
|
||||
:param target: the mapped instance being deleted. If
|
||||
the event is configured with ``raw=True``, this will
|
||||
instead be the :class:`.InstanceState` state-management
|
||||
object associated with the instance.
|
||||
:return: No return value is supported by this event.
|
||||
@@ -952,7 +952,7 @@ class SessionEvents(event.Events):
|
||||
transaction is ongoing.
|
||||
|
||||
:param session: The target :class:`.Session`.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
def after_commit(self, session):
|
||||
@@ -960,19 +960,19 @@ class SessionEvents(event.Events):
|
||||
|
||||
Note that this may not be per-flush if a longer running
|
||||
transaction is ongoing.
|
||||
|
||||
|
||||
:param session: The target :class:`.Session`.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
def after_rollback(self, session):
|
||||
"""Execute after a real DBAPI rollback has occurred.
|
||||
|
||||
|
||||
Note that this event only fires when the *actual* rollback against
|
||||
the database occurs - it does *not* fire each time the
|
||||
:meth:`.Session.rollback` method is called, if the underlying
|
||||
the database occurs - it does *not* fire each time the
|
||||
:meth:`.Session.rollback` method is called, if the underlying
|
||||
DBAPI transaction has already been rolled back. In many
|
||||
cases, the :class:`.Session` will not be in
|
||||
cases, the :class:`.Session` will not be in
|
||||
an "active" state during this event, as the current
|
||||
transaction is not valid. To acquire a :class:`.Session`
|
||||
which is active after the outermost rollback has proceeded,
|
||||
@@ -984,23 +984,23 @@ class SessionEvents(event.Events):
|
||||
"""
|
||||
|
||||
def after_soft_rollback(self, session, previous_transaction):
|
||||
"""Execute after any rollback has occurred, including "soft"
|
||||
"""Execute after any rollback has occurred, including "soft"
|
||||
rollbacks that don't actually emit at the DBAPI level.
|
||||
|
||||
|
||||
This corresponds to both nested and outer rollbacks, i.e.
|
||||
the innermost rollback that calls the DBAPI's
|
||||
rollback() method, as well as the enclosing rollback
|
||||
the innermost rollback that calls the DBAPI's
|
||||
rollback() method, as well as the enclosing rollback
|
||||
calls that only pop themselves from the transaction stack.
|
||||
|
||||
The given :class:`.Session` can be used to invoke SQL and
|
||||
:meth:`.Session.query` operations after an outermost rollback
|
||||
|
||||
The given :class:`.Session` can be used to invoke SQL and
|
||||
:meth:`.Session.query` operations after an outermost rollback
|
||||
by first checking the :attr:`.Session.is_active` flag::
|
||||
|
||||
@event.listens_for(Session, "after_soft_rollback")
|
||||
def do_something(session, previous_transaction):
|
||||
if session.is_active:
|
||||
session.execute("select * from some_table")
|
||||
|
||||
|
||||
:param session: The target :class:`.Session`.
|
||||
:param previous_transaction: The :class:`.SessionTransaction` transactional
|
||||
marker object which was just closed. The current :class:`.SessionTransaction`
|
||||
@@ -1030,7 +1030,7 @@ class SessionEvents(event.Events):
|
||||
Note that the session's state is still in pre-flush, i.e. 'new',
|
||||
'dirty', and 'deleted' lists still show pre-flush state as well
|
||||
as the history settings on instance attributes.
|
||||
|
||||
|
||||
:param session: The target :class:`.Session`.
|
||||
:param flush_context: Internal :class:`.UOWTransaction` object
|
||||
which handles the details of the flush.
|
||||
@@ -1044,8 +1044,8 @@ class SessionEvents(event.Events):
|
||||
This will be when the 'new', 'dirty', and 'deleted' lists are in
|
||||
their final state. An actual commit() may or may not have
|
||||
occurred, depending on whether or not the flush started its own
|
||||
transaction or participated in a larger transaction.
|
||||
|
||||
transaction or participated in a larger transaction.
|
||||
|
||||
:param session: The target :class:`.Session`.
|
||||
:param flush_context: Internal :class:`.UOWTransaction` object
|
||||
which handles the details of the flush.
|
||||
@@ -1056,9 +1056,9 @@ class SessionEvents(event.Events):
|
||||
|
||||
:param session: The target :class:`.Session`.
|
||||
:param transaction: The :class:`.SessionTransaction`.
|
||||
:param connection: The :class:`~.engine.base.Connection` object
|
||||
:param connection: The :class:`~.engine.base.Connection` object
|
||||
which will be used for SQL statements.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
def before_attach(self, session, instance):
|
||||
@@ -1066,30 +1066,30 @@ class SessionEvents(event.Events):
|
||||
|
||||
This is called before an add, delete or merge causes
|
||||
the object to be part of the session.
|
||||
|
||||
.. versionadded:: 0.8. Note that :meth:`.after_attach` now
|
||||
fires off after the item is part of the session.
|
||||
|
||||
.. versionadded:: 0.8. Note that :meth:`.after_attach` now
|
||||
fires off after the item is part of the session.
|
||||
:meth:`.before_attach` is provided for those cases where
|
||||
the item should not yet be part of the session state.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
def after_attach(self, session, instance):
|
||||
"""Execute after an instance is attached to a session.
|
||||
|
||||
This is called after an add, delete or merge.
|
||||
|
||||
This is called after an add, delete or merge.
|
||||
|
||||
.. note::
|
||||
|
||||
|
||||
As of 0.8, this event fires off *after* the item
|
||||
has been fully associated with the session, which is
|
||||
different than previous releases. For event
|
||||
handlers that require the object not yet
|
||||
handlers that require the object not yet
|
||||
be part of session state (such as handlers which
|
||||
may autoflush while the target object is not
|
||||
yet complete) consider the
|
||||
new :meth:`.before_attach` event.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
def after_bulk_update( self, session, query, query_context, result):
|
||||
@@ -1098,7 +1098,7 @@ class SessionEvents(event.Events):
|
||||
This is called as a result of the :meth:`.Query.update` method.
|
||||
|
||||
:param query: the :class:`.Query` object that this update operation was
|
||||
called upon.
|
||||
called upon.
|
||||
:param query_context: The :class:`.QueryContext` object, corresponding
|
||||
to the invocation of an ORM query.
|
||||
:param result: the :class:`.ResultProxy` returned as a result of the
|
||||
@@ -1112,7 +1112,7 @@ class SessionEvents(event.Events):
|
||||
This is called as a result of the :meth:`.Query.delete` method.
|
||||
|
||||
:param query: the :class:`.Query` object that this update operation was
|
||||
called upon.
|
||||
called upon.
|
||||
:param query_context: The :class:`.QueryContext` object, corresponding
|
||||
to the invocation of an ORM query.
|
||||
:param result: the :class:`.ResultProxy` returned as a result of the
|
||||
@@ -1163,15 +1163,15 @@ class AttributeEvents(event.Events):
|
||||
|
||||
:param propagate=False: When True, the listener function will
|
||||
be established not just for the class attribute given, but
|
||||
for attributes of the same name on all current subclasses
|
||||
of that class, as well as all future subclasses of that
|
||||
class, using an additional listener that listens for
|
||||
for attributes of the same name on all current subclasses
|
||||
of that class, as well as all future subclasses of that
|
||||
class, using an additional listener that listens for
|
||||
instrumentation events.
|
||||
:param raw=False: When True, the "target" argument to the
|
||||
event will be the :class:`.InstanceState` management
|
||||
object, rather than the mapped instance itself.
|
||||
:param retval=False: when True, the user-defined event
|
||||
listening must return the "value" argument from the
|
||||
:param retval=False: when True, the user-defined event
|
||||
listening must return the "value" argument from the
|
||||
function. This gives the listening function the opportunity
|
||||
to change the value that is ultimately used for a "set"
|
||||
or "append" event.
|
||||
@@ -1187,7 +1187,7 @@ class AttributeEvents(event.Events):
|
||||
return target
|
||||
|
||||
@classmethod
|
||||
def _listen(cls, target, identifier, fn, active_history=False,
|
||||
def _listen(cls, target, identifier, fn, active_history=False,
|
||||
raw=False, retval=False,
|
||||
propagate=False):
|
||||
if active_history:
|
||||
@@ -1228,9 +1228,9 @@ class AttributeEvents(event.Events):
|
||||
be the :class:`.InstanceState` object.
|
||||
:param value: the value being appended. If this listener
|
||||
is registered with ``retval=True``, the listener
|
||||
function must return this value, or a new value which
|
||||
function must return this value, or a new value which
|
||||
replaces it.
|
||||
:param initiator: the attribute implementation object
|
||||
:param initiator: the attribute implementation object
|
||||
which initiated this event.
|
||||
:return: if the event was registered with ``retval=True``,
|
||||
the given value, or a new effective value, should be returned.
|
||||
@@ -1244,7 +1244,7 @@ class AttributeEvents(event.Events):
|
||||
If the listener is registered with ``raw=True``, this will
|
||||
be the :class:`.InstanceState` object.
|
||||
:param value: the value being removed.
|
||||
:param initiator: the attribute implementation object
|
||||
:param initiator: the attribute implementation object
|
||||
which initiated this event.
|
||||
:return: No return value is defined for this event.
|
||||
"""
|
||||
@@ -1257,15 +1257,15 @@ class AttributeEvents(event.Events):
|
||||
be the :class:`.InstanceState` object.
|
||||
:param value: the value being set. If this listener
|
||||
is registered with ``retval=True``, the listener
|
||||
function must return this value, or a new value which
|
||||
function must return this value, or a new value which
|
||||
replaces it.
|
||||
:param oldvalue: the previous value being replaced. This
|
||||
may also be the symbol ``NEVER_SET`` or ``NO_VALUE``.
|
||||
If the listener is registered with ``active_history=True``,
|
||||
the previous value of the attribute will be loaded from
|
||||
the database if the existing value is currently unloaded
|
||||
the database if the existing value is currently unloaded
|
||||
or expired.
|
||||
:param initiator: the attribute implementation object
|
||||
:param initiator: the attribute implementation object
|
||||
which initiated this event.
|
||||
:return: if the event was registered with ``retval=True``,
|
||||
the given value, or a new effective value, should be returned.
|
||||
|
||||
@@ -55,8 +55,17 @@ MANYTOMANY = util.symbol('MANYTOMANY')
|
||||
from .deprecated_interfaces import AttributeExtension, SessionExtension, \
|
||||
MapperExtension
|
||||
|
||||
class _InspectionAttr(object):
|
||||
"""Define a series of attributes that all ORM inspection
|
||||
targets need to have."""
|
||||
|
||||
class MapperProperty(object):
|
||||
is_selectable = False
|
||||
is_aliased_class = False
|
||||
is_instance = False
|
||||
is_mapper = False
|
||||
is_property = False
|
||||
|
||||
class MapperProperty(_InspectionAttr):
|
||||
"""Manage the relationship of a ``Mapper`` to a single class
|
||||
attribute, as well as that attribute as it appears on individual
|
||||
instances of the class, including attribute instrumentation,
|
||||
@@ -77,6 +86,8 @@ class MapperProperty(object):
|
||||
|
||||
"""
|
||||
|
||||
is_property = True
|
||||
|
||||
def setup(self, context, entity, path, adapter, **kwargs):
|
||||
"""Called by Query for the purposes of constructing a SQL statement.
|
||||
|
||||
@@ -115,8 +126,8 @@ class MapperProperty(object):
|
||||
def instrument_class(self, mapper): # pragma: no-coverage
|
||||
raise NotImplementedError()
|
||||
|
||||
_compile_started = False
|
||||
_compile_finished = False
|
||||
_configure_started = False
|
||||
_configure_finished = False
|
||||
|
||||
def init(self):
|
||||
"""Called after all mappers are created to assemble
|
||||
@@ -124,9 +135,9 @@ class MapperProperty(object):
|
||||
initialization steps.
|
||||
|
||||
"""
|
||||
self._compile_started = True
|
||||
self._configure_started = True
|
||||
self.do_init()
|
||||
self._compile_finished = True
|
||||
self._configure_finished = True
|
||||
|
||||
@property
|
||||
def class_attribute(self):
|
||||
|
||||
@@ -19,11 +19,11 @@ import weakref
|
||||
from itertools import chain
|
||||
from collections import deque
|
||||
|
||||
from .. import sql, util, log, exc as sa_exc, event, schema
|
||||
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 .interfaces import MapperProperty
|
||||
from .interfaces import MapperProperty, _InspectionAttr
|
||||
|
||||
from .util import _INSTRUMENTOR, _class_to_mapper, \
|
||||
_state_mapper, class_mapper, \
|
||||
@@ -51,10 +51,10 @@ _memoized_configured_property = util.group_expirable_memoized_property()
|
||||
# column
|
||||
NO_ATTRIBUTE = util.symbol('NO_ATTRIBUTE')
|
||||
|
||||
# lock used to synchronize the "mapper compile" step
|
||||
_COMPILE_MUTEX = util.threading.RLock()
|
||||
# lock used to synchronize the "mapper configure" step
|
||||
_CONFIGURE_MUTEX = util.threading.RLock()
|
||||
|
||||
class Mapper(object):
|
||||
class Mapper(_InspectionAttr):
|
||||
"""Define the correlation of class attributes to database table
|
||||
columns.
|
||||
|
||||
@@ -182,7 +182,7 @@ class Mapper(object):
|
||||
# prevent this mapper from being constructed
|
||||
# while a configure_mappers() is occurring (and defer a
|
||||
# configure_mappers() until construction succeeds)
|
||||
_COMPILE_MUTEX.acquire()
|
||||
_CONFIGURE_MUTEX.acquire()
|
||||
try:
|
||||
self._configure_inheritance()
|
||||
self._configure_legacy_instrument_class()
|
||||
@@ -196,11 +196,23 @@ class Mapper(object):
|
||||
self._log("constructed")
|
||||
self._expire_memoizations()
|
||||
finally:
|
||||
_COMPILE_MUTEX.release()
|
||||
_CONFIGURE_MUTEX.release()
|
||||
|
||||
# major attributes initialized at the classlevel so that
|
||||
# they can be Sphinx-documented.
|
||||
|
||||
is_mapper = True
|
||||
"""Part of the inspection API."""
|
||||
|
||||
@property
|
||||
def mapper(self):
|
||||
"""Part of the inspection API.
|
||||
|
||||
Returns self.
|
||||
|
||||
"""
|
||||
return self
|
||||
|
||||
local_table = None
|
||||
"""The :class:`.Selectable` which this :class:`.Mapper` manages.
|
||||
|
||||
@@ -435,7 +447,7 @@ class Mapper(object):
|
||||
|
||||
if self.inherits:
|
||||
if isinstance(self.inherits, type):
|
||||
self.inherits = class_mapper(self.inherits, compile=False)
|
||||
self.inherits = class_mapper(self.inherits, configure=False)
|
||||
if not issubclass(self.class_, self.inherits.class_):
|
||||
raise sa_exc.ArgumentError(
|
||||
"Class '%s' does not inherit from '%s'" %
|
||||
@@ -1185,10 +1197,10 @@ class Mapper(object):
|
||||
for key, prop in l:
|
||||
self._log("initialize prop %s", key)
|
||||
|
||||
if prop.parent is self and not prop._compile_started:
|
||||
if prop.parent is self and not prop._configure_started:
|
||||
prop.init()
|
||||
|
||||
if prop._compile_finished:
|
||||
if prop._configure_finished:
|
||||
prop.post_instrument_class(self)
|
||||
|
||||
self._log("_post_configure_properties() complete")
|
||||
@@ -1263,11 +1275,11 @@ class Mapper(object):
|
||||
def has_property(self, key):
|
||||
return key in self._props
|
||||
|
||||
def get_property(self, key, _compile_mappers=True):
|
||||
def get_property(self, key, _configure_mappers=True):
|
||||
"""return a MapperProperty associated with the given key.
|
||||
"""
|
||||
|
||||
if _compile_mappers and _new_mappers:
|
||||
if _configure_mappers and _new_mappers:
|
||||
configure_mappers()
|
||||
|
||||
try:
|
||||
@@ -1352,10 +1364,13 @@ class Mapper(object):
|
||||
|
||||
@_memoized_configured_property
|
||||
def _with_polymorphic_mappers(self):
|
||||
if _new_mappers:
|
||||
configure_mappers()
|
||||
if not self.with_polymorphic:
|
||||
return [self]
|
||||
return self._mappers_from_spec(*self.with_polymorphic)
|
||||
|
||||
|
||||
@_memoized_configured_property
|
||||
def _with_polymorphic_selectable(self):
|
||||
if not self.with_polymorphic:
|
||||
@@ -1369,6 +1384,22 @@ class Mapper(object):
|
||||
self._mappers_from_spec(spec, selectable),
|
||||
False)
|
||||
|
||||
with_polymorphic_mappers = _with_polymorphic_mappers
|
||||
"""The list of :class:`.Mapper` objects included in the
|
||||
default "polymorphic" query.
|
||||
|
||||
"""
|
||||
|
||||
selectable = _with_polymorphic_selectable
|
||||
"""The :func:`.select` construct this :class:`.Mapper` selects from
|
||||
by default.
|
||||
|
||||
Normally, this is equivalent to :attr:`.mapped_table`, unless
|
||||
the ``with_polymorphic`` feature is in use, in which case the
|
||||
full "polymoprhic" selectable is returned.
|
||||
|
||||
"""
|
||||
|
||||
def _with_polymorphic_args(self, spec=None, selectable=False,
|
||||
innerjoin=False):
|
||||
if self.with_polymorphic:
|
||||
@@ -1886,6 +1917,7 @@ class Mapper(object):
|
||||
|
||||
return result
|
||||
|
||||
inspection._self_inspects(Mapper)
|
||||
log.class_logger(Mapper)
|
||||
|
||||
def configure_mappers():
|
||||
@@ -1902,7 +1934,7 @@ def configure_mappers():
|
||||
return
|
||||
|
||||
_call_configured = None
|
||||
_COMPILE_MUTEX.acquire()
|
||||
_CONFIGURE_MUTEX.acquire()
|
||||
try:
|
||||
global _already_compiling
|
||||
if _already_compiling:
|
||||
@@ -1944,7 +1976,7 @@ def configure_mappers():
|
||||
finally:
|
||||
_already_compiling = False
|
||||
finally:
|
||||
_COMPILE_MUTEX.release()
|
||||
_CONFIGURE_MUTEX.release()
|
||||
if _call_configured is not None:
|
||||
_call_configured.dispatch.after_configured()
|
||||
|
||||
|
||||
@@ -886,7 +886,7 @@ class RelationshipProperty(StrategizedProperty):
|
||||
|
||||
|
||||
def _add_reverse_property(self, key):
|
||||
other = self.mapper.get_property(key, _compile_mappers=False)
|
||||
other = self.mapper.get_property(key, _configure_mappers=False)
|
||||
self._reverse_property.add(other)
|
||||
other._reverse_property.add(self)
|
||||
|
||||
@@ -912,7 +912,7 @@ class RelationshipProperty(StrategizedProperty):
|
||||
"""
|
||||
if isinstance(self.argument, type):
|
||||
mapper_ = mapper.class_mapper(self.argument,
|
||||
compile=False)
|
||||
configure=False)
|
||||
elif isinstance(self.argument, mapper.Mapper):
|
||||
mapper_ = self.argument
|
||||
elif util.callable(self.argument):
|
||||
@@ -921,7 +921,7 @@ class RelationshipProperty(StrategizedProperty):
|
||||
# configurational schemes
|
||||
|
||||
mapper_ = mapper.class_mapper(self.argument(),
|
||||
compile=False)
|
||||
configure=False)
|
||||
else:
|
||||
raise sa_exc.ArgumentError("relationship '%s' expects "
|
||||
"a class or a mapper argument (received: %s)"
|
||||
@@ -1037,7 +1037,7 @@ class RelationshipProperty(StrategizedProperty):
|
||||
if not self.is_primary() \
|
||||
and not mapper.class_mapper(
|
||||
self.parent.class_,
|
||||
compile=False).has_property(self.key):
|
||||
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 "
|
||||
|
||||
+234
-235
File diff suppressed because it is too large
Load Diff
@@ -13,7 +13,7 @@ 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
|
||||
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,\
|
||||
@@ -24,7 +24,7 @@ instrumentation = util.importlater("sqlalchemy.orm", "instrumentation")
|
||||
mapperlib = util.importlater("sqlalchemy.orm", "mapperlib")
|
||||
|
||||
|
||||
class InstanceState(object):
|
||||
class InstanceState(interfaces._InspectionAttr):
|
||||
"""tracks state information at the instance level."""
|
||||
|
||||
session_id = None
|
||||
@@ -39,6 +39,8 @@ class InstanceState(object):
|
||||
deleted = False
|
||||
_load_pending = False
|
||||
|
||||
is_instance = True
|
||||
|
||||
def __init__(self, obj, manager):
|
||||
self.class_ = obj.__class__
|
||||
self.manager = manager
|
||||
|
||||
+262
-244
@@ -7,7 +7,7 @@
|
||||
|
||||
from .. import sql, util, event, exc as sa_exc, inspection
|
||||
from ..sql import expression, util as sql_util, operators
|
||||
from .interfaces import PropComparator, MapperProperty
|
||||
from .interfaces import PropComparator, MapperProperty, _InspectionAttr
|
||||
from itertools import chain
|
||||
from . import attributes, exc
|
||||
import re
|
||||
@@ -31,15 +31,15 @@ class CascadeOptions(frozenset):
|
||||
|
||||
def __new__(cls, arg):
|
||||
values = set([
|
||||
c for c
|
||||
c for c
|
||||
in re.split('\s*,\s*', arg or "")
|
||||
if c
|
||||
])
|
||||
|
||||
if values.difference(cls._allowed_cascades):
|
||||
raise sa_exc.ArgumentError(
|
||||
"Invalid cascade option(s): %s" %
|
||||
", ".join([repr(x) for x in
|
||||
"Invalid cascade option(s): %s" %
|
||||
", ".join([repr(x) for x in
|
||||
sorted(
|
||||
values.difference(cls._allowed_cascades)
|
||||
)])
|
||||
@@ -99,12 +99,12 @@ def polymorphic_union(table_map, typecolname, aliasname='p_union', cast_nulls=Tr
|
||||
See :ref:`concrete_inheritance` for an example of how
|
||||
this is used.
|
||||
|
||||
:param table_map: mapping of polymorphic identities to
|
||||
:param table_map: mapping of polymorphic identities to
|
||||
:class:`.Table` objects.
|
||||
:param typecolname: string name of a "discriminator" column, which will be
|
||||
:param typecolname: string name of a "discriminator" column, which will be
|
||||
derived from the query, producing the polymorphic identity for each row. If
|
||||
``None``, no polymorphic discriminator is generated.
|
||||
:param aliasname: name of the :func:`~sqlalchemy.sql.expression.alias()`
|
||||
:param aliasname: name of the :func:`~sqlalchemy.sql.expression.alias()`
|
||||
construct generated.
|
||||
:param cast_nulls: if True, non-existent columns, which are represented as labeled
|
||||
NULLs, will be passed into CAST. This is a legacy behavior that is problematic
|
||||
@@ -118,7 +118,7 @@ def polymorphic_union(table_map, typecolname, aliasname='p_union', cast_nulls=Tr
|
||||
for key in table_map.keys():
|
||||
table = table_map[key]
|
||||
|
||||
# mysql doesnt like selecting from a select;
|
||||
# mysql doesnt like selecting from a select;
|
||||
# make it an alias of the select
|
||||
if isinstance(table, sql.Select):
|
||||
table = table.alias()
|
||||
@@ -216,14 +216,14 @@ class ORMAdapter(sql_util.ColumnAdapter):
|
||||
and the AliasedClass if any is referenced.
|
||||
|
||||
"""
|
||||
def __init__(self, entity, equivalents=None,
|
||||
def __init__(self, entity, equivalents=None,
|
||||
chain_to=None, adapt_required=False):
|
||||
self.mapper, selectable, is_aliased_class = _entity_info(entity)
|
||||
if is_aliased_class:
|
||||
self.aliased_class = entity
|
||||
else:
|
||||
self.aliased_class = None
|
||||
sql_util.ColumnAdapter.__init__(self, selectable,
|
||||
sql_util.ColumnAdapter.__init__(self, selectable,
|
||||
equivalents, chain_to,
|
||||
adapt_required=adapt_required)
|
||||
|
||||
@@ -253,9 +253,9 @@ class PathRegistry(object):
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
"""
|
||||
@@ -290,7 +290,7 @@ class PathRegistry(object):
|
||||
def serialize(self):
|
||||
path = self.path
|
||||
return zip(
|
||||
[m.class_ for m in [path[i] for i in range(0, len(path), 2)]],
|
||||
[m.class_ for m in [path[i] for i in range(0, len(path), 2)]],
|
||||
[path[i] for i in range(1, len(path), 2)] + [None]
|
||||
)
|
||||
|
||||
@@ -391,7 +391,7 @@ class AliasedClass(object):
|
||||
The resulting object is an instance of :class:`.AliasedClass`, however
|
||||
it implements a ``__getattribute__()`` scheme which will proxy attribute
|
||||
access to that of the ORM class being aliased. All classmethods
|
||||
on the mapped entity should also be available here, including
|
||||
on the mapped entity should also be available here, including
|
||||
hybrids created with the :ref:`hybrids_toplevel` extension,
|
||||
which will receive the :class:`.AliasedClass` as the "class" argument
|
||||
when classmethods are called.
|
||||
@@ -399,17 +399,18 @@ class AliasedClass(object):
|
||||
:param cls: ORM mapped entity which will be "wrapped" around an alias.
|
||||
:param alias: a selectable, such as an :func:`.alias` or :func:`.select`
|
||||
construct, which will be rendered in place of the mapped table of the
|
||||
ORM entity. If left as ``None``, an ordinary :class:`.Alias` of the
|
||||
ORM entity. If left as ``None``, an ordinary :class:`.Alias` of the
|
||||
ORM entity's mapped table will be generated.
|
||||
:param name: A name which will be applied both to the :class:`.Alias`
|
||||
if one is generated, as well as the name present in the "named tuple"
|
||||
returned by the :class:`.Query` object when results are returned.
|
||||
:param adapt_on_names: if True, more liberal "matching" will be used when
|
||||
mapping the mapped columns of the ORM entity to those of the given selectable -
|
||||
a name-based match will be performed if the given selectable doesn't
|
||||
otherwise have a column that corresponds to one on the entity. The
|
||||
use case for this is when associating an entity with some derived
|
||||
selectable such as one that uses aggregate functions::
|
||||
mapping the mapped columns of the ORM entity to those of the
|
||||
given selectable - a name-based match will be performed if the
|
||||
given selectable doesn't otherwise have a column that corresponds
|
||||
to one on the entity. The use case for this is when associating
|
||||
an entity with some derived selectable such as one that uses
|
||||
aggregate functions::
|
||||
|
||||
class UnitPrice(Base):
|
||||
__tablename__ = 'unit_price'
|
||||
@@ -421,43 +422,52 @@ class AliasedClass(object):
|
||||
func.sum(UnitPrice.price).label('price')
|
||||
).group_by(UnitPrice.unit_id).subquery()
|
||||
|
||||
aggregated_unit_price = aliased(UnitPrice, alias=aggregated_unit_price, adapt_on_names=True)
|
||||
aggregated_unit_price = aliased(UnitPrice,
|
||||
alias=aggregated_unit_price, adapt_on_names=True)
|
||||
|
||||
Above, functions on ``aggregated_unit_price`` which
|
||||
refer to ``.price`` will return the
|
||||
``fund.sum(UnitPrice.price).label('price')`` column,
|
||||
as it is matched on the name "price". Ordinarily, the "price" function wouldn't
|
||||
have any "column correspondence" to the actual ``UnitPrice.price`` column
|
||||
as it is not a proxy of the original.
|
||||
Above, functions on ``aggregated_unit_price`` which refer to
|
||||
``.price`` will return the
|
||||
``fund.sum(UnitPrice.price).label('price')`` column, as it is
|
||||
matched on the name "price". Ordinarily, the "price" function
|
||||
wouldn't have any "column correspondence" to the actual
|
||||
``UnitPrice.price`` column as it is not a proxy of the original.
|
||||
|
||||
.. versionadded:: 0.7.3
|
||||
|
||||
"""
|
||||
def __init__(self, cls, alias=None,
|
||||
name=None,
|
||||
def __init__(self, cls, alias=None,
|
||||
name=None,
|
||||
adapt_on_names=False,
|
||||
with_polymorphic_mappers=(),
|
||||
with_polymorphic_discriminator=None):
|
||||
self.__mapper = _class_to_mapper(cls)
|
||||
self.__target = self.__mapper.class_
|
||||
self.__adapt_on_names = adapt_on_names
|
||||
mapper = _class_to_mapper(cls)
|
||||
if alias is None:
|
||||
alias = self.__mapper._with_polymorphic_selectable.alias(
|
||||
name=name)
|
||||
alias = mapper._with_polymorphic_selectable.alias(name=name)
|
||||
self._aliased_insp = AliasedInsp(
|
||||
mapper,
|
||||
alias,
|
||||
name,
|
||||
with_polymorphic_mappers,
|
||||
with_polymorphic_discriminator
|
||||
)
|
||||
self._setup(self._aliased_insp, adapt_on_names)
|
||||
|
||||
def _setup(self, aliased_insp, adapt_on_names):
|
||||
self.__adapt_on_names = adapt_on_names
|
||||
mapper = aliased_insp.mapper
|
||||
alias = aliased_insp.selectable
|
||||
self.__target = mapper.class_
|
||||
self.__adapt_on_names = adapt_on_names
|
||||
self.__adapter = sql_util.ClauseAdapter(alias,
|
||||
equivalents=self.__mapper._equivalent_columns,
|
||||
equivalents=mapper._equivalent_columns,
|
||||
adapt_on_names=self.__adapt_on_names)
|
||||
self.__alias = alias
|
||||
self.__with_polymorphic_mappers = with_polymorphic_mappers
|
||||
self.__with_polymorphic_discriminator = \
|
||||
with_polymorphic_discriminator
|
||||
for poly in with_polymorphic_mappers:
|
||||
setattr(self, poly.class_.__name__,
|
||||
for poly in aliased_insp.with_polymorphic_mappers:
|
||||
setattr(self, poly.class_.__name__,
|
||||
AliasedClass(poly.class_, alias))
|
||||
|
||||
# used to assign a name to the RowTuple object
|
||||
# returned by Query.
|
||||
self._sa_label_name = name
|
||||
self._sa_label_name = aliased_insp.name
|
||||
self.__name__ = 'AliasedClass_' + str(self.__target)
|
||||
|
||||
@util.memoized_property
|
||||
@@ -466,45 +476,41 @@ class AliasedClass(object):
|
||||
|
||||
def __getstate__(self):
|
||||
return {
|
||||
'mapper':self.__mapper,
|
||||
'alias':self.__alias,
|
||||
'name':self._sa_label_name,
|
||||
'adapt_on_names':self.__adapt_on_names,
|
||||
'mapper': self._aliased_insp.mapper,
|
||||
'alias': self._aliased_insp.selectable,
|
||||
'name': self._aliased_insp.name,
|
||||
'adapt_on_names': self.__adapt_on_names,
|
||||
'with_polymorphic_mappers':
|
||||
self.__with_polymorphic_mappers,
|
||||
self._aliased_insp.with_polymorphic_mappers,
|
||||
'with_polymorphic_discriminator':
|
||||
self.__with_polymorphic_discriminator
|
||||
self._aliased_insp.polymorphic_on
|
||||
}
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.__mapper = state['mapper']
|
||||
self.__target = self.__mapper.class_
|
||||
self.__adapt_on_names = state['adapt_on_names']
|
||||
alias = state['alias']
|
||||
self.__adapter = sql_util.ClauseAdapter(alias,
|
||||
equivalents=self.__mapper._equivalent_columns,
|
||||
adapt_on_names=self.__adapt_on_names)
|
||||
self.__alias = alias
|
||||
self.__with_polymorphic_mappers = \
|
||||
state.get('with_polymorphic_mappers')
|
||||
self.__with_polymorphic_discriminator = \
|
||||
state.get('with_polymorphic_discriminator')
|
||||
name = state['name']
|
||||
self._sa_label_name = name
|
||||
self.__name__ = 'AliasedClass_' + str(self.__target)
|
||||
self._aliased_insp = AliasedInsp(
|
||||
state['mapper'],
|
||||
state['alias'],
|
||||
state['name'],
|
||||
state.get('with_polymorphic_mappers'),
|
||||
state.get('with_polymorphic_discriminator')
|
||||
)
|
||||
self._setup(self._aliased_insp, state['adapt_on_names'])
|
||||
|
||||
def __adapt_element(self, elem):
|
||||
return self.__adapter.traverse(elem).\
|
||||
_annotate({
|
||||
'parententity': self,
|
||||
'parentmapper':self.__mapper}
|
||||
'parententity': self,
|
||||
'parentmapper': self._aliased_insp.mapper}
|
||||
)
|
||||
|
||||
def __adapt_prop(self, existing, key):
|
||||
comparator = existing.comparator.adapted(self.__adapt_element)
|
||||
|
||||
queryattr = attributes.QueryableAttribute(self, key,
|
||||
impl=existing.impl, parententity=self, comparator=comparator)
|
||||
queryattr = attributes.QueryableAttribute(
|
||||
self, key,
|
||||
impl=existing.impl,
|
||||
parententity=self,
|
||||
comparator=comparator)
|
||||
setattr(self, key, queryattr)
|
||||
return queryattr
|
||||
|
||||
@@ -539,6 +545,19 @@ class AliasedClass(object):
|
||||
return '<AliasedClass at 0x%x; %s>' % (
|
||||
id(self), self.__target.__name__)
|
||||
|
||||
AliasedInsp = util.namedtuple("AliasedInsp", [
|
||||
"mapper",
|
||||
"selectable",
|
||||
"name",
|
||||
"with_polymorphic_mappers",
|
||||
"polymorphic_on"
|
||||
])
|
||||
|
||||
class AliasedInsp(_InspectionAttr, AliasedInsp):
|
||||
is_aliased_class = True
|
||||
|
||||
inspection._inspects(AliasedClass)(lambda target: target._aliased_insp)
|
||||
|
||||
def aliased(element, alias=None, name=None, adapt_on_names=False):
|
||||
if isinstance(element, expression.FromClause):
|
||||
if adapt_on_names:
|
||||
@@ -547,10 +566,10 @@ def aliased(element, alias=None, name=None, adapt_on_names=False):
|
||||
)
|
||||
return element.alias(name)
|
||||
else:
|
||||
return AliasedClass(element, alias=alias,
|
||||
return AliasedClass(element, alias=alias,
|
||||
name=name, adapt_on_names=adapt_on_names)
|
||||
|
||||
def with_polymorphic(base, classes, selectable=False,
|
||||
def with_polymorphic(base, classes, selectable=False,
|
||||
polymorphic_on=None, aliased=False,
|
||||
innerjoin=False):
|
||||
"""Produce an :class:`.AliasedClass` construct which specifies
|
||||
@@ -595,8 +614,8 @@ def with_polymorphic(base, classes, selectable=False,
|
||||
|
||||
:param polymorphic_on: a column to be used as the "discriminator"
|
||||
column for the given selectable. If not given, the polymorphic_on
|
||||
attribute of the base classes' mapper will be used, if any. This
|
||||
is useful for mappings that don't have polymorphic loading
|
||||
attribute of the base classes' mapper will be used, if any. This
|
||||
is useful for mappings that don't have polymorphic loading
|
||||
behavior by default.
|
||||
|
||||
:param innerjoin: if True, an INNER JOIN will be used. This should
|
||||
@@ -604,12 +623,13 @@ def with_polymorphic(base, classes, selectable=False,
|
||||
"""
|
||||
primary_mapper = _class_to_mapper(base)
|
||||
mappers, selectable = primary_mapper.\
|
||||
_with_polymorphic_args(classes, selectable, innerjoin=innerjoin)
|
||||
_with_polymorphic_args(classes, selectable,
|
||||
innerjoin=innerjoin)
|
||||
if aliased:
|
||||
selectable = selectable.alias()
|
||||
return AliasedClass(base,
|
||||
selectable,
|
||||
with_polymorphic_mappers=mappers,
|
||||
return AliasedClass(base,
|
||||
selectable,
|
||||
with_polymorphic_mappers=mappers,
|
||||
with_polymorphic_discriminator=polymorphic_on)
|
||||
|
||||
|
||||
@@ -620,7 +640,7 @@ def _orm_annotate(element, exclude=None):
|
||||
Elements within the exclude collection will be cloned but not annotated.
|
||||
|
||||
"""
|
||||
return sql_util._deep_annotate(element, {'_orm_adapt':True}, exclude)
|
||||
return sql_util._deep_annotate(element, {'_orm_adapt': True}, exclude)
|
||||
|
||||
def _orm_deannotate(element):
|
||||
"""Remove annotations that link a column to a particular mapping.
|
||||
@@ -631,7 +651,7 @@ def _orm_deannotate(element):
|
||||
|
||||
"""
|
||||
|
||||
return sql_util._deep_deannotate(element,
|
||||
return sql_util._deep_deannotate(element,
|
||||
values=("_orm_adapt", "parententity")
|
||||
)
|
||||
|
||||
@@ -643,7 +663,7 @@ class _ORMJoin(expression.Join):
|
||||
|
||||
__visit_name__ = expression.Join.__visit_name__
|
||||
|
||||
def __init__(self, left, right, onclause=None,
|
||||
def __init__(self, left, right, onclause=None,
|
||||
isouter=False, join_to_left=True):
|
||||
adapt_from = None
|
||||
|
||||
@@ -720,8 +740,8 @@ def join(left, right, onclause=None, isouter=False, join_to_left=True):
|
||||
as its functionality is encapsulated within that of the
|
||||
:meth:`.Query.join` method, which features a
|
||||
significant amount of automation beyond :func:`.orm.join`
|
||||
by itself. Explicit usage of :func:`.orm.join`
|
||||
with :class:`.Query` involves usage of the
|
||||
by itself. Explicit usage of :func:`.orm.join`
|
||||
with :class:`.Query` involves usage of the
|
||||
:meth:`.Query.select_from` method, as in::
|
||||
|
||||
from sqlalchemy.orm import join
|
||||
@@ -729,7 +749,7 @@ def join(left, right, onclause=None, isouter=False, join_to_left=True):
|
||||
select_from(join(User, Address, User.addresses)).\\
|
||||
filter(Address.email_address=='foo@bar.com')
|
||||
|
||||
In modern SQLAlchemy the above join can be written more
|
||||
In modern SQLAlchemy the above join can be written more
|
||||
succinctly as::
|
||||
|
||||
session.query(User).\\
|
||||
@@ -759,12 +779,12 @@ def with_parent(instance, prop):
|
||||
|
||||
The SQL rendered is the same as that rendered when a lazy loader
|
||||
would fire off from the given parent on that attribute, meaning
|
||||
that the appropriate state is taken from the parent object in
|
||||
that the appropriate state is taken from the parent object in
|
||||
Python without the need to render joins to the parent table
|
||||
in the rendered statement.
|
||||
|
||||
.. versionchanged:: 0.6.4
|
||||
This method accepts parent instances in all
|
||||
This method accepts parent instances in all
|
||||
persistence states, including transient, persistent, and detached.
|
||||
Only the requisite primary key/foreign key attributes need to
|
||||
be populated. Previous versions didn't work with transient
|
||||
@@ -775,8 +795,8 @@ def with_parent(instance, prop):
|
||||
|
||||
:param property:
|
||||
String property name, or class-bound attribute, which indicates
|
||||
what relationship from the instance should be used to reconcile the
|
||||
parent/child relationship.
|
||||
what relationship from the instance should be used to reconcile the
|
||||
parent/child relationship.
|
||||
|
||||
"""
|
||||
if isinstance(prop, basestring):
|
||||
@@ -785,112 +805,44 @@ def with_parent(instance, prop):
|
||||
elif isinstance(prop, attributes.QueryableAttribute):
|
||||
prop = prop.property
|
||||
|
||||
return prop.compare(operators.eq,
|
||||
instance,
|
||||
return prop.compare(operators.eq,
|
||||
instance,
|
||||
value_is_parent=True)
|
||||
|
||||
|
||||
extended_entity_info = util.namedtuple("extended_entity_info", [
|
||||
"entity",
|
||||
"mapper",
|
||||
"selectable",
|
||||
"is_aliased_class",
|
||||
"with_polymorphic_mappers",
|
||||
"with_polymorphic_discriminator"
|
||||
])
|
||||
def _extended_entity_info(entity, compile=True):
|
||||
if isinstance(entity, AliasedClass):
|
||||
return extended_entity_info(
|
||||
entity,
|
||||
entity._AliasedClass__mapper, \
|
||||
entity._AliasedClass__alias, \
|
||||
True, \
|
||||
entity._AliasedClass__with_polymorphic_mappers, \
|
||||
entity._AliasedClass__with_polymorphic_discriminator
|
||||
)
|
||||
|
||||
if isinstance(entity, mapperlib.Mapper):
|
||||
mapper = entity
|
||||
|
||||
elif isinstance(entity, type):
|
||||
class_manager = attributes.manager_of_class(entity)
|
||||
|
||||
if class_manager is None:
|
||||
return extended_entity_info(entity, None, entity, False, [], None)
|
||||
|
||||
mapper = class_manager.mapper
|
||||
else:
|
||||
return extended_entity_info(entity, None, entity, False, [], None)
|
||||
|
||||
if compile and mapperlib.module._new_mappers:
|
||||
mapperlib.configure_mappers()
|
||||
return extended_entity_info(
|
||||
entity,
|
||||
mapper, \
|
||||
mapper._with_polymorphic_selectable, \
|
||||
False, \
|
||||
mapper._with_polymorphic_mappers, \
|
||||
mapper.polymorphic_on
|
||||
)
|
||||
|
||||
def _entity_info(entity, compile=True):
|
||||
"""Return mapping information given a class, mapper, or AliasedClass.
|
||||
|
||||
Returns 3-tuple of: mapper, mapped selectable, boolean indicating if this
|
||||
is an aliased() construct.
|
||||
|
||||
If the given entity is not a mapper, mapped class, or aliased construct,
|
||||
returns None, the entity, False. This is typically used to allow
|
||||
unmapped selectables through.
|
||||
|
||||
"""
|
||||
return _extended_entity_info(entity, compile)[1:4]
|
||||
|
||||
def _entity_descriptor(entity, key):
|
||||
"""Return a class attribute given an entity and string name.
|
||||
|
||||
May return :class:`.InstrumentedAttribute` or user-defined
|
||||
attribute.
|
||||
|
||||
"""
|
||||
if isinstance(entity, expression.FromClause):
|
||||
description = entity
|
||||
entity = entity.c
|
||||
elif not isinstance(entity, (AliasedClass, type)):
|
||||
description = entity = entity.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):
|
||||
mapper, selectable, is_aliased_class = _entity_info(entity)
|
||||
if isinstance(selectable, expression.Selectable):
|
||||
return [c for c in selectable.c]
|
||||
else:
|
||||
return [selectable]
|
||||
|
||||
def _orm_selectable(entity):
|
||||
mapper, selectable, is_aliased_class = _entity_info(entity)
|
||||
return selectable
|
||||
|
||||
def _attr_as_key(attr):
|
||||
if hasattr(attr, 'key'):
|
||||
return attr.key
|
||||
else:
|
||||
return expression._column_as_key(attr)
|
||||
|
||||
def _is_aliased_class(entity):
|
||||
return isinstance(entity, AliasedClass)
|
||||
|
||||
_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_)
|
||||
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.
|
||||
@@ -904,102 +856,168 @@ def object_mapper(instance):
|
||||
"""
|
||||
return object_state(instance).mapper
|
||||
|
||||
@inspection._inspects(object)
|
||||
def object_state(instance):
|
||||
"""Given an object, return the primary Mapper associated with the object
|
||||
instance.
|
||||
|
||||
Raises UnmappedInstanceError if no mapping is configured.
|
||||
|
||||
This function is available via the inspection system as::
|
||||
Equivalent functionality is available via the inspection system as::
|
||||
|
||||
inspect(instance)
|
||||
|
||||
Using the inspection system will raise plain
|
||||
:class:`.InvalidRequestError` if the instance is not part of
|
||||
a mapping.
|
||||
|
||||
"""
|
||||
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:
|
||||
raise exc.UnmappedInstanceError(instance)
|
||||
except exc.NO_STATE:
|
||||
state = _inspect_mapped_object(instance)
|
||||
if state is None:
|
||||
raise exc.UnmappedInstanceError(instance)
|
||||
else:
|
||||
return state
|
||||
|
||||
|
||||
@inspection._inspects(type)
|
||||
def class_mapper(class_, compile=True):
|
||||
"""Given a class, return the primary :class:`.Mapper` associated
|
||||
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.
|
||||
|
||||
This function is available via the inspection system as::
|
||||
Equivalent functionality is available via the inspection system as::
|
||||
|
||||
inspect(some_mapped_class)
|
||||
|
||||
Using the inspection system will raise plain
|
||||
:class:`.InvalidRequestError` if the class is not mapped.
|
||||
|
||||
"""
|
||||
|
||||
try:
|
||||
class_manager = attributes.manager_of_class(class_)
|
||||
mapper = class_manager.mapper
|
||||
|
||||
except exc.NO_STATE:
|
||||
if not isinstance(class_, type):
|
||||
raise sa_exc.ArgumentError("Class object expected, got '%r'." % class_)
|
||||
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
|
||||
|
||||
if compile and mapperlib.module._new_mappers:
|
||||
mapperlib.configure_mappers()
|
||||
return mapper
|
||||
|
||||
def _class_to_mapper(class_or_mapper, compile=True):
|
||||
if _is_aliased_class(class_or_mapper):
|
||||
return class_or_mapper._AliasedClass__mapper
|
||||
|
||||
elif isinstance(class_or_mapper, type):
|
||||
try:
|
||||
class_manager = attributes.manager_of_class(class_or_mapper)
|
||||
mapper = class_manager.mapper
|
||||
except exc.NO_STATE:
|
||||
raise exc.UnmappedClassError(class_or_mapper)
|
||||
elif isinstance(class_or_mapper, mapperlib.Mapper):
|
||||
mapper = class_or_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)
|
||||
|
||||
if compile and mapperlib.module._new_mappers:
|
||||
mapperlib.configure_mappers()
|
||||
return 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)
|
||||
|
||||
extended_entity_info = util.namedtuple("extended_entity_info", [
|
||||
"entity",
|
||||
"mapper",
|
||||
"selectable",
|
||||
"is_aliased_class",
|
||||
"with_polymorphic_mappers",
|
||||
"with_polymorphic_discriminator"
|
||||
])
|
||||
def _extended_entity_info(entity):
|
||||
insp = inspection.inspect(entity)
|
||||
return extended_entity_info(
|
||||
entity,
|
||||
insp.mapper if not insp.is_selectable else None,
|
||||
insp.selectable,
|
||||
insp.is_aliased_class if not insp.is_selectable else False,
|
||||
insp.with_polymorphic_mappers if not insp.is_selectable else None,
|
||||
insp.polymorphic_on if not insp.is_selectable else None
|
||||
)
|
||||
|
||||
def _entity_info(entity, compile=True):
|
||||
"""Return mapping information given a class, mapper, or AliasedClass.
|
||||
|
||||
Returns 3-tuple of: mapper, mapped selectable, boolean indicating if this
|
||||
is an aliased() construct.
|
||||
|
||||
If the given entity is not a mapper, mapped class, or aliased construct,
|
||||
returns None, the entity, False. This is typically used to allow
|
||||
unmapped selectables through.
|
||||
|
||||
"""
|
||||
insp = inspection.inspect(entity)
|
||||
return \
|
||||
insp.mapper if not insp.is_selectable else None,\
|
||||
insp.selectable,\
|
||||
insp.is_aliased_class if not insp.is_selectable else 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:
|
||||
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 _orm_selectable(entity):
|
||||
insp = inspection.inspect(entity, False)
|
||||
if hasattr(insp, 'selectable'):
|
||||
return insp.selectable
|
||||
else:
|
||||
return entity
|
||||
|
||||
def has_identity(object):
|
||||
state = attributes.instance_state(object)
|
||||
return state.has_identity
|
||||
|
||||
def _is_mapped_class(cls):
|
||||
"""Return True if the given object is a mapped class,
|
||||
:class:`.Mapper`, or :class:`.AliasedClass`."""
|
||||
|
||||
if isinstance(cls, (AliasedClass, mapperlib.Mapper)):
|
||||
return True
|
||||
if isinstance(cls, expression.ClauseElement):
|
||||
return False
|
||||
if isinstance(cls, type):
|
||||
manager = attributes.manager_of_class(cls)
|
||||
return manager and _INSTRUMENTOR in manager.info
|
||||
return False
|
||||
|
||||
def _mapper_or_none(cls):
|
||||
"""Return the :class:`.Mapper` for the given class or None if the
|
||||
class is not mapped."""
|
||||
|
||||
manager = attributes.manager_of_class(cls)
|
||||
if manager is not None and _INSTRUMENTOR in manager.info:
|
||||
return manager.info[_INSTRUMENTOR]
|
||||
else:
|
||||
return None
|
||||
|
||||
def instance_str(instance):
|
||||
"""Return a string describing an instance."""
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ to stay the same in future releases.
|
||||
import itertools, re
|
||||
from operator import attrgetter
|
||||
|
||||
from .. import util, exc
|
||||
from .. import util, exc, inspection
|
||||
from . import operators
|
||||
from .operators import ColumnOperators
|
||||
from .visitors import Visitable, cloned_traverse
|
||||
@@ -1525,6 +1525,7 @@ class ClauseElement(Visitable):
|
||||
_from_objects = []
|
||||
bind = None
|
||||
_is_clone_of = None
|
||||
is_selectable = False
|
||||
|
||||
def _clone(self):
|
||||
"""Create a shallow copy of this ClauseElement.
|
||||
@@ -1841,6 +1842,8 @@ class ClauseElement(Visitable):
|
||||
return '<%s.%s at 0x%x; %s>' % (
|
||||
self.__module__, self.__class__.__name__, id(self), friendly)
|
||||
|
||||
inspection._self_inspects(ClauseElement)
|
||||
|
||||
|
||||
class _Immutable(object):
|
||||
"""mark a ClauseElement as 'immutable' when expressions are cloned."""
|
||||
@@ -2394,6 +2397,12 @@ class Selectable(ClauseElement):
|
||||
"""mark a class as being selectable"""
|
||||
__visit_name__ = 'selectable'
|
||||
|
||||
is_selectable = True
|
||||
|
||||
@property
|
||||
def selectable(self):
|
||||
return self
|
||||
|
||||
class FromClause(Selectable):
|
||||
"""Represent an element that can be used within the ``FROM``
|
||||
clause of a ``SELECT`` statement.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""test the inspection registry system."""
|
||||
|
||||
from test.lib.testing import eq_, assert_raises
|
||||
from test.lib.testing import eq_, assert_raises_message
|
||||
from sqlalchemy import exc, util
|
||||
from sqlalchemy import inspection, inspect
|
||||
from test.lib import fixtures
|
||||
@@ -8,8 +8,7 @@ from test.lib import fixtures
|
||||
class TestFixture(object):
|
||||
pass
|
||||
|
||||
class TestEvents(fixtures.TestBase):
|
||||
"""Test class- and instance-level event registration."""
|
||||
class TestInspection(fixtures.TestBase):
|
||||
|
||||
def tearDown(self):
|
||||
for type_ in list(inspection._registrars):
|
||||
@@ -28,6 +27,16 @@ class TestEvents(fixtures.TestBase):
|
||||
insp = inspect(somefoo)
|
||||
assert insp["insp"] is somefoo
|
||||
|
||||
def test_no_inspect(self):
|
||||
class SomeFoo(TestFixture):
|
||||
pass
|
||||
|
||||
assert_raises_message(
|
||||
exc.NoInspectionAvailable,
|
||||
"No inspection system is available for object of type ",
|
||||
inspect, SomeFoo
|
||||
)
|
||||
|
||||
def test_class_insp(self):
|
||||
class SomeFoo(TestFixture):
|
||||
pass
|
||||
|
||||
@@ -62,7 +62,7 @@ class PropertyComparatorTest(fixtures.TestBase, AssertsCompiledSQL):
|
||||
"SELECT a.value AS a_value FROM a"
|
||||
)
|
||||
|
||||
def test_alised_query(self):
|
||||
def test_aliased_query(self):
|
||||
A = self._fixture()
|
||||
sess = Session()
|
||||
self.assert_compile(
|
||||
@@ -214,7 +214,7 @@ class PropertyValueTest(fixtures.TestBase, AssertsCompiledSQL):
|
||||
A = self._fixture(False)
|
||||
a1 = A(_value=5)
|
||||
assert_raises_message(
|
||||
AttributeError,
|
||||
AttributeError,
|
||||
"can't set attribute",
|
||||
setattr, a1, 'value', 10
|
||||
)
|
||||
@@ -223,7 +223,7 @@ class PropertyValueTest(fixtures.TestBase, AssertsCompiledSQL):
|
||||
A = self._fixture(False)
|
||||
a1 = A(_value=5)
|
||||
assert_raises_message(
|
||||
AttributeError,
|
||||
AttributeError,
|
||||
"can't delete attribute",
|
||||
delattr, a1, 'value'
|
||||
)
|
||||
|
||||
+66
-11
@@ -1,10 +1,10 @@
|
||||
"""test the inspection registry system."""
|
||||
|
||||
from test.lib.testing import eq_, assert_raises, is_
|
||||
from test.lib.testing import eq_, assert_raises_message, is_
|
||||
from sqlalchemy import exc, util
|
||||
from sqlalchemy import inspect
|
||||
from test.orm import _fixtures
|
||||
from sqlalchemy.orm import class_mapper, synonym, Session
|
||||
from sqlalchemy.orm import class_mapper, synonym, Session, aliased
|
||||
from sqlalchemy.orm.attributes import instance_state, NO_VALUE
|
||||
from test.lib import testing
|
||||
|
||||
@@ -21,11 +21,6 @@ class TestORMInspection(_fixtures.FixtureTest):
|
||||
|
||||
assert inspect(User) is class_mapper(User)
|
||||
|
||||
def test_instance_state(self):
|
||||
User = self.classes.User
|
||||
u1 = User()
|
||||
|
||||
assert inspect(u1) is instance_state(u1)
|
||||
|
||||
def test_column_collection_iterate(self):
|
||||
User = self.classes.User
|
||||
@@ -43,7 +38,7 @@ class TestORMInspection(_fixtures.FixtureTest):
|
||||
User = self.classes.User
|
||||
user_table = self.tables.users
|
||||
insp = inspect(User)
|
||||
eq_(insp.primary_key,
|
||||
eq_(insp.primary_key,
|
||||
(user_table.c.id,)
|
||||
)
|
||||
|
||||
@@ -53,12 +48,59 @@ class TestORMInspection(_fixtures.FixtureTest):
|
||||
insp = inspect(User)
|
||||
is_(insp.local_table, user_table)
|
||||
|
||||
def test_property(self):
|
||||
def test_mapped_table(self):
|
||||
User = self.classes.User
|
||||
user_table = self.tables.users
|
||||
insp = inspect(User)
|
||||
is_(insp.mapped_table, user_table)
|
||||
|
||||
def test_mapper_selectable(self):
|
||||
User = self.classes.User
|
||||
user_table = self.tables.users
|
||||
insp = inspect(User)
|
||||
is_(insp.selectable, user_table)
|
||||
assert not insp.is_selectable
|
||||
assert not insp.is_aliased_class
|
||||
|
||||
def test_aliased_class(self):
|
||||
Address = self.classes.Address
|
||||
ualias = aliased(Address)
|
||||
insp = inspect(ualias)
|
||||
is_(insp.mapper, inspect(Address))
|
||||
is_(insp.selectable, ualias._AliasedClass__alias)
|
||||
assert not insp.is_selectable
|
||||
assert insp.is_aliased_class
|
||||
|
||||
def test_not_mapped_class(self):
|
||||
class Foo(object):
|
||||
pass
|
||||
|
||||
assert_raises_message(
|
||||
exc.NoInspectionAvailable,
|
||||
"No inspection system is available for object of type",
|
||||
inspect, Foo
|
||||
)
|
||||
|
||||
def test_not_mapped_instance(self):
|
||||
class Foo(object):
|
||||
pass
|
||||
|
||||
assert_raises_message(
|
||||
exc.NoInspectionAvailable,
|
||||
"No inspection system is available for object of type",
|
||||
inspect, Foo()
|
||||
)
|
||||
|
||||
def test_property(self):
|
||||
User = self.classes.User
|
||||
insp = inspect(User)
|
||||
is_(insp.attr.id, class_mapper(User).get_property('id'))
|
||||
|
||||
def test_with_polymorphic(self):
|
||||
User = self.classes.User
|
||||
insp = inspect(User)
|
||||
eq_(insp.with_polymorphic_mappers, [insp])
|
||||
|
||||
def test_col_property(self):
|
||||
User = self.classes.User
|
||||
user_table = self.tables.users
|
||||
@@ -74,7 +116,7 @@ class TestORMInspection(_fixtures.FixtureTest):
|
||||
User = self.classes.User
|
||||
insp = inspect(User)
|
||||
eq_(
|
||||
set(insp.attr.keys()),
|
||||
set(insp.attr.keys()),
|
||||
set(['addresses', 'orders', 'id', 'name', 'name_syn'])
|
||||
)
|
||||
|
||||
@@ -115,7 +157,7 @@ class TestORMInspection(_fixtures.FixtureTest):
|
||||
User.addresses.property
|
||||
)
|
||||
eq_(
|
||||
set(rel.keys()),
|
||||
set(rel.keys()),
|
||||
set(['orders', 'addresses'])
|
||||
)
|
||||
|
||||
@@ -134,6 +176,7 @@ class TestORMInspection(_fixtures.FixtureTest):
|
||||
assert not hasattr(prop, 'columns')
|
||||
assert not hasattr(prop, 'expression')
|
||||
|
||||
|
||||
def test_instance_state(self):
|
||||
User = self.classes.User
|
||||
u1 = User()
|
||||
@@ -228,6 +271,18 @@ class TestORMInspection(_fixtures.FixtureTest):
|
||||
eq_(insp.identity, (u1.id,))
|
||||
is_(s.query(User).get(insp.identity), u1)
|
||||
|
||||
def test_is_instance(self):
|
||||
User = self.classes.User
|
||||
u1 = User(name='ed')
|
||||
insp = inspect(u1)
|
||||
assert insp.is_instance
|
||||
|
||||
insp = inspect(User)
|
||||
assert not insp.is_instance
|
||||
|
||||
insp = inspect(aliased(User))
|
||||
assert not insp.is_instance
|
||||
|
||||
def test_identity_key(self):
|
||||
User = self.classes.User
|
||||
u1 = User(name='ed')
|
||||
|
||||
+85
-57
@@ -55,6 +55,8 @@ class MapperTest(_fixtures.FixtureTest, AssertsCompiledSQL):
|
||||
|
||||
def test_utils(self):
|
||||
users = self.tables.users
|
||||
addresses = self.tables.addresses
|
||||
Address = self.classes.Address
|
||||
|
||||
from sqlalchemy.orm.util import _is_mapped_class, _is_aliased_class
|
||||
|
||||
@@ -63,7 +65,10 @@ class MapperTest(_fixtures.FixtureTest, AssertsCompiledSQL):
|
||||
@property
|
||||
def y(self):
|
||||
return "somethign else"
|
||||
m = mapper(Foo, users)
|
||||
|
||||
|
||||
m = mapper(Foo, users, properties={"addresses":relationship(Address)})
|
||||
mapper(Address, addresses)
|
||||
a1 = aliased(Foo)
|
||||
|
||||
f = Foo()
|
||||
@@ -71,6 +76,8 @@ class MapperTest(_fixtures.FixtureTest, AssertsCompiledSQL):
|
||||
for fn, arg, ret in [
|
||||
(_is_mapped_class, Foo.x, False),
|
||||
(_is_mapped_class, Foo.y, False),
|
||||
(_is_mapped_class, Foo.name, False),
|
||||
(_is_mapped_class, Foo.addresses, False),
|
||||
(_is_mapped_class, Foo, True),
|
||||
(_is_mapped_class, f, False),
|
||||
(_is_mapped_class, a1, True),
|
||||
@@ -85,7 +92,28 @@ class MapperTest(_fixtures.FixtureTest, AssertsCompiledSQL):
|
||||
]:
|
||||
assert fn(arg) == ret
|
||||
|
||||
def test_entity_descriptor(self):
|
||||
users = self.tables.users
|
||||
|
||||
from sqlalchemy.orm.util import _entity_descriptor
|
||||
|
||||
class Foo(object):
|
||||
x = "something"
|
||||
@property
|
||||
def y(self):
|
||||
return "somethign else"
|
||||
m = mapper(Foo, users)
|
||||
a1 = aliased(Foo)
|
||||
|
||||
f = Foo()
|
||||
|
||||
for arg, key, ret in [
|
||||
(m, "x", Foo.x),
|
||||
(Foo, "x", Foo.x),
|
||||
(a1, "x", a1.x),
|
||||
(users, "name", users.c.name)
|
||||
]:
|
||||
assert _entity_descriptor(arg, key) is ret
|
||||
|
||||
def test_friendly_attribute_str_on_uncompiled_boom(self):
|
||||
User, users = self.classes.User, self.tables.users
|
||||
@@ -96,13 +124,13 @@ class MapperTest(_fixtures.FixtureTest, AssertsCompiledSQL):
|
||||
'addresses':relationship(boom)
|
||||
})
|
||||
|
||||
# test that QueryableAttribute.__str__() doesn't
|
||||
# test that QueryableAttribute.__str__() doesn't
|
||||
# cause a compile.
|
||||
eq_(str(User.addresses), "User.addresses")
|
||||
|
||||
def test_exceptions_sticky(self):
|
||||
"""test preservation of mapper compile errors raised during hasattr(),
|
||||
as well as for redundant mapper compile calls. Test that
|
||||
as well as for redundant mapper compile calls. Test that
|
||||
repeated calls don't stack up error messages.
|
||||
|
||||
"""
|
||||
@@ -157,7 +185,7 @@ class MapperTest(_fixtures.FixtureTest, AssertsCompiledSQL):
|
||||
assert_raises(sa.exc.ArgumentError, mapper, User, s)
|
||||
|
||||
def test_reconfigure_on_other_mapper(self):
|
||||
"""A configure trigger on an already-configured mapper
|
||||
"""A configure trigger on an already-configured mapper
|
||||
still triggers a check against all mappers."""
|
||||
|
||||
users, Address, addresses, User = (self.tables.users,
|
||||
@@ -211,7 +239,7 @@ class MapperTest(_fixtures.FixtureTest, AssertsCompiledSQL):
|
||||
: addresses.c.user_id})
|
||||
|
||||
def test_constructor_exc(self):
|
||||
"""TypeError is raised for illegal constructor args,
|
||||
"""TypeError is raised for illegal constructor args,
|
||||
whether or not explicit __init__ is present [ticket:908]."""
|
||||
|
||||
users, addresses = self.tables.users, self.tables.addresses
|
||||
@@ -229,7 +257,7 @@ class MapperTest(_fixtures.FixtureTest, AssertsCompiledSQL):
|
||||
assert_raises(TypeError, Bar, x=5)
|
||||
|
||||
def test_sort_states_comparisons(self):
|
||||
"""test that _sort_states() doesn't compare
|
||||
"""test that _sort_states() doesn't compare
|
||||
insert_order to state.key, for set of mixed
|
||||
persistent/pending. In particular Python 3 disallows
|
||||
this.
|
||||
@@ -239,7 +267,7 @@ class MapperTest(_fixtures.FixtureTest, AssertsCompiledSQL):
|
||||
def __init__(self, id):
|
||||
self.id = id
|
||||
m = MetaData()
|
||||
foo_t = Table('foo', m,
|
||||
foo_t = Table('foo', m,
|
||||
Column('id', String, primary_key=True)
|
||||
)
|
||||
m = mapper(Foo, foo_t)
|
||||
@@ -500,7 +528,7 @@ class MapperTest(_fixtures.FixtureTest, AssertsCompiledSQL):
|
||||
)
|
||||
|
||||
def test_column_prop_deannotate(self):
|
||||
"""test that column property deannotates,
|
||||
"""test that column property deannotates,
|
||||
bringing expressions down to the original mapped columns.
|
||||
"""
|
||||
User, users = self.classes.User, self.tables.users
|
||||
@@ -514,7 +542,7 @@ class MapperTest(_fixtures.FixtureTest, AssertsCompiledSQL):
|
||||
|
||||
assert User.x.property.columns[0] is not expr
|
||||
assert User.x.property.columns[0].element.left is users.c.name
|
||||
# a deannotate needs to clone the base, in case
|
||||
# a deannotate needs to clone the base, in case
|
||||
# the original one referenced annotated elements.
|
||||
assert User.x.property.columns[0].element.right is not expr.right
|
||||
|
||||
@@ -587,7 +615,7 @@ class MapperTest(_fixtures.FixtureTest, AssertsCompiledSQL):
|
||||
class AddressUser(User):
|
||||
pass
|
||||
m1 = mapper(User, users, polymorphic_identity='user')
|
||||
m2 = mapper(AddressUser, addresses, inherits=User,
|
||||
m2 = mapper(AddressUser, addresses, inherits=User,
|
||||
polymorphic_identity='address')
|
||||
m3 = mapper(AddressUser, addresses, non_primary=True)
|
||||
assert m3._identity_class is m2._identity_class
|
||||
@@ -632,7 +660,7 @@ class MapperTest(_fixtures.FixtureTest, AssertsCompiledSQL):
|
||||
class Sub(Base):
|
||||
pass
|
||||
mapper(Base, users)
|
||||
assert_raises_message(sa.exc.InvalidRequestError,
|
||||
assert_raises_message(sa.exc.InvalidRequestError,
|
||||
"Configure a primary mapper first",
|
||||
mapper, Sub, addresses, non_primary=True
|
||||
)
|
||||
@@ -660,7 +688,7 @@ class MapperTest(_fixtures.FixtureTest, AssertsCompiledSQL):
|
||||
pass
|
||||
class Empty(object):pass
|
||||
|
||||
empty = mapper(Empty, t, properties={'empty_id' : t.c.id},
|
||||
empty = mapper(Empty, t, properties={'empty_id' : t.c.id},
|
||||
include_properties=[])
|
||||
p_m = mapper(Person, t, polymorphic_on=t.c.type,
|
||||
include_properties=('id', 'type', 'name'))
|
||||
@@ -698,7 +726,7 @@ class MapperTest(_fixtures.FixtureTest, AssertsCompiledSQL):
|
||||
want = set(want)
|
||||
eq_(have, want)
|
||||
|
||||
assert_props(HasDef, ['h_boss_id', 'h_employee_number', 'h_id',
|
||||
assert_props(HasDef, ['h_boss_id', 'h_employee_number', 'h_id',
|
||||
'name', 'h_name', 'h_vendor_id', 'h_type'])
|
||||
assert_props(Person, ['id', 'name', 'type'])
|
||||
assert_instrumented(Person, ['id', 'name', 'type'])
|
||||
@@ -719,7 +747,7 @@ class MapperTest(_fixtures.FixtureTest, AssertsCompiledSQL):
|
||||
assert_props(Frob, ['f_id', 'f_type', 'f_name', ])
|
||||
|
||||
|
||||
# putting the discriminator column in exclude_properties,
|
||||
# putting the discriminator column in exclude_properties,
|
||||
# very weird. As of 0.7.4 this re-maps it.
|
||||
class Foo(Person):
|
||||
pass
|
||||
@@ -835,8 +863,8 @@ class MapperTest(_fixtures.FixtureTest, AssertsCompiledSQL):
|
||||
self.tables.addresses,
|
||||
self.classes.Address)
|
||||
|
||||
m = mapper(Address,
|
||||
addresses.join(email_bounces),
|
||||
m = mapper(Address,
|
||||
addresses.join(email_bounces),
|
||||
properties={'id':[addresses.c.id, email_bounces.c.id]}
|
||||
)
|
||||
configure_mappers()
|
||||
@@ -1285,8 +1313,8 @@ class MapperTest(_fixtures.FixtureTest, AssertsCompiledSQL):
|
||||
eq_(User.uc_name.method2('x'), "method2")
|
||||
|
||||
assert_raises_message(
|
||||
AttributeError,
|
||||
"Neither 'extendedproperty' object nor 'UCComparator' object has an attribute 'nonexistent'",
|
||||
AttributeError,
|
||||
"Neither 'extendedproperty' object nor 'UCComparator' object has an attribute 'nonexistent'",
|
||||
getattr, User.uc_name, 'nonexistent')
|
||||
|
||||
# test compile
|
||||
@@ -1332,8 +1360,8 @@ class MapperTest(_fixtures.FixtureTest, AssertsCompiledSQL):
|
||||
})
|
||||
|
||||
assert_raises_message(
|
||||
AttributeError,
|
||||
"Neither 'InstrumentedAttribute' object nor 'MyComparator' object has an attribute 'nonexistent'",
|
||||
AttributeError,
|
||||
"Neither 'InstrumentedAttribute' object nor 'MyComparator' object has an attribute 'nonexistent'",
|
||||
getattr, User.name, "nonexistent")
|
||||
|
||||
eq_(str((User.name == 'ed').compile(dialect=sa.engine.default.DefaultDialect())) , "lower(users.name) = lower(:lower_1)")
|
||||
@@ -1458,13 +1486,13 @@ class MapperTest(_fixtures.FixtureTest, AssertsCompiledSQL):
|
||||
})
|
||||
|
||||
assert_raises_message(
|
||||
sa.orm.exc.UnmappedClassError,
|
||||
sa.orm.exc.UnmappedClassError,
|
||||
"Class 'test.orm._fixtures.Address' is not mapped",
|
||||
sa.orm.configure_mappers)
|
||||
|
||||
def test_unmapped_not_type_error(self):
|
||||
assert_raises_message(
|
||||
sa.exc.ArgumentError,
|
||||
sa.exc.ArgumentError,
|
||||
"Class object expected, got '5'.",
|
||||
class_mapper, 5
|
||||
)
|
||||
@@ -1561,8 +1589,8 @@ class DocumentTest(fixtures.TestBase):
|
||||
pass
|
||||
|
||||
mapper(Foo, t1, properties={
|
||||
'bars':relationship(Bar,
|
||||
doc="bar relationship",
|
||||
'bars':relationship(Bar,
|
||||
doc="bar relationship",
|
||||
backref=backref('foo',doc='foo relationship')
|
||||
),
|
||||
'foober':column_property(t1.c.col3, doc='alternate data col'),
|
||||
@@ -1699,7 +1727,7 @@ class OptionsTest(_fixtures.FixtureTest):
|
||||
self.sql_count_(1, go)
|
||||
|
||||
def test_eager_degrade(self):
|
||||
"""An eager relationship automatically degrades to a lazy relationship
|
||||
"""An eager relationship automatically degrades to a lazy relationship
|
||||
if eager columns are not available"""
|
||||
|
||||
Address, addresses, users, User = (self.classes.Address,
|
||||
@@ -1708,7 +1736,7 @@ class OptionsTest(_fixtures.FixtureTest):
|
||||
self.classes.User)
|
||||
|
||||
mapper(User, users, properties=dict(
|
||||
addresses = relationship(mapper(Address, addresses),
|
||||
addresses = relationship(mapper(Address, addresses),
|
||||
lazy='joined', order_by=addresses.c.id)))
|
||||
|
||||
sess = create_session()
|
||||
@@ -1990,7 +2018,7 @@ class ValidatorTest(_fixtures.FixtureTest):
|
||||
sess.flush()
|
||||
sess.expunge_all()
|
||||
eq_(
|
||||
sess.query(User).filter_by(name='edward').one(),
|
||||
sess.query(User).filter_by(name='edward').one(),
|
||||
User(name='edward', addresses=[Address(email_address='foo@bar.com')])
|
||||
)
|
||||
|
||||
@@ -2021,7 +2049,7 @@ class ValidatorTest(_fixtures.FixtureTest):
|
||||
|
||||
eq_(
|
||||
dict((k, v[0].__name__) for k, v in u_m.validators.items()),
|
||||
{'name':'validate_name',
|
||||
{'name':'validate_name',
|
||||
'addresses':'validate_address'}
|
||||
)
|
||||
|
||||
@@ -2058,20 +2086,20 @@ class ValidatorTest(_fixtures.FixtureTest):
|
||||
u1.addresses = [a2, a3]
|
||||
|
||||
eq_(canary, [
|
||||
('name', 'ed', False),
|
||||
('name', 'mary', False),
|
||||
('name', 'mary', True),
|
||||
('name', 'ed', False),
|
||||
('name', 'mary', False),
|
||||
('name', 'mary', True),
|
||||
# append a1
|
||||
('addresses', a1, False),
|
||||
('addresses', a1, False),
|
||||
# remove a1
|
||||
('addresses', a1, True),
|
||||
('addresses', a1, True),
|
||||
# set to [a1, a2] - this is two appends
|
||||
('addresses', a1, False), ('addresses', a2, False),
|
||||
# set to [a2, a3] - this is a remove of a1,
|
||||
# append of a3. the appends are first.
|
||||
('addresses', a3, False),
|
||||
('addresses', a1, True),
|
||||
]
|
||||
('addresses', a1, True),
|
||||
]
|
||||
)
|
||||
|
||||
class ComparatorFactoryTest(_fixtures.FixtureTest, AssertsCompiledSQL):
|
||||
@@ -2129,12 +2157,12 @@ class ComparatorFactoryTest(_fixtures.FixtureTest, AssertsCompiledSQL):
|
||||
comparator_factory=MyFactory)
|
||||
})
|
||||
self.assert_compile(
|
||||
User.name == 'ed',
|
||||
User.name == 'ed',
|
||||
"foobar(users.name) = foobar(:foobar_1)",
|
||||
dialect=default.DefaultDialect())
|
||||
|
||||
self.assert_compile(
|
||||
aliased(User).name == 'ed',
|
||||
aliased(User).name == 'ed',
|
||||
"foobar(users_1.name) = foobar(:foobar_1)",
|
||||
dialect=default.DefaultDialect())
|
||||
|
||||
@@ -2158,7 +2186,7 @@ class ComparatorFactoryTest(_fixtures.FixtureTest, AssertsCompiledSQL):
|
||||
|
||||
mapper(User, users)
|
||||
mapper(Address, addresses, properties={
|
||||
'user':relationship(User, comparator_factory=MyFactory,
|
||||
'user':relationship(User, comparator_factory=MyFactory,
|
||||
backref=backref("addresses", comparator_factory=MyFactory2)
|
||||
)
|
||||
}
|
||||
@@ -2466,9 +2494,9 @@ class DeferredTest(_fixtures.FixtureTest):
|
||||
|
||||
|
||||
order_select = sa.select([
|
||||
orders.c.id,
|
||||
orders.c.user_id,
|
||||
orders.c.address_id,
|
||||
orders.c.id,
|
||||
orders.c.user_id,
|
||||
orders.c.address_id,
|
||||
orders.c.description,
|
||||
orders.c.isopen]).alias()
|
||||
mapper(Order, order_select, properties={
|
||||
@@ -2523,7 +2551,7 @@ class SecondaryOptionsTest(fixtures.MappedTest):
|
||||
|
||||
@classmethod
|
||||
def define_tables(cls, metadata):
|
||||
Table("base", metadata,
|
||||
Table("base", metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('type', String(50), nullable=False)
|
||||
)
|
||||
@@ -2556,11 +2584,11 @@ class SecondaryOptionsTest(fixtures.MappedTest):
|
||||
mapper(Base, base, polymorphic_on=base.c.type, properties={
|
||||
'related':relationship(Related, uselist=False)
|
||||
})
|
||||
mapper(Child1, child1, inherits=Base,
|
||||
polymorphic_identity='child1',
|
||||
mapper(Child1, child1, inherits=Base,
|
||||
polymorphic_identity='child1',
|
||||
properties={
|
||||
'child2':relationship(Child2,
|
||||
primaryjoin=child1.c.child2id==base.c.id,
|
||||
'child2':relationship(Child2,
|
||||
primaryjoin=child1.c.child2id==base.c.id,
|
||||
foreign_keys=child1.c.child2id)
|
||||
})
|
||||
mapper(Child2, child2, inherits=Base, polymorphic_identity='child2')
|
||||
@@ -2614,18 +2642,18 @@ class SecondaryOptionsTest(fixtures.MappedTest):
|
||||
eq_(
|
||||
child1s.all(),
|
||||
[
|
||||
Child1(id=1, related=Related(id=1)),
|
||||
Child1(id=2, related=Related(id=2)),
|
||||
Child1(id=1, related=Related(id=1)),
|
||||
Child1(id=2, related=Related(id=2)),
|
||||
Child1(id=3, related=Related(id=3))
|
||||
]
|
||||
)
|
||||
self.assert_sql_count(testing.db, go, 1)
|
||||
|
||||
c1 = child1s[0]
|
||||
|
||||
|
||||
self.assert_sql_execution(
|
||||
testing.db,
|
||||
lambda: c1.child2,
|
||||
testing.db,
|
||||
lambda: c1.child2,
|
||||
CompiledSQL(
|
||||
"SELECT child2.id AS child2_id, base.id AS base_id, base.type AS base_type "
|
||||
"FROM base JOIN child2 ON base.id = child2.id "
|
||||
@@ -2651,8 +2679,8 @@ class SecondaryOptionsTest(fixtures.MappedTest):
|
||||
c1 = child1s[0]
|
||||
|
||||
self.assert_sql_execution(
|
||||
testing.db,
|
||||
lambda: c1.child2,
|
||||
testing.db,
|
||||
lambda: c1.child2,
|
||||
CompiledSQL(
|
||||
"SELECT child2.id AS child2_id, base.id AS base_id, base.type AS base_type "
|
||||
"FROM base JOIN child2 ON base.id = child2.id WHERE base.id = :param_1",
|
||||
@@ -2685,8 +2713,8 @@ class SecondaryOptionsTest(fixtures.MappedTest):
|
||||
|
||||
# this *does* joinedload
|
||||
self.assert_sql_execution(
|
||||
testing.db,
|
||||
lambda: c1.child2,
|
||||
testing.db,
|
||||
lambda: c1.child2,
|
||||
CompiledSQL(
|
||||
"SELECT child2.id AS child2_id, base.id AS base_id, base.type AS base_type, "
|
||||
"related_1.id AS related_1_id FROM base JOIN child2 ON base.id = child2.id "
|
||||
@@ -3027,8 +3055,8 @@ class RequirementsTest(fixtures.MappedTest):
|
||||
])
|
||||
s.commit()
|
||||
eq_(
|
||||
[(h1.value, h1.id, h2.value, h2.id)
|
||||
for h1, h2 in
|
||||
[(h1.value, h1.id, h2.value, h2.id)
|
||||
for h1, h2 in
|
||||
s.query(H1, H2).join(H1.h2s).order_by(H1.id, H2.id)],
|
||||
[
|
||||
('abc', 1, 'abc', 1),
|
||||
|
||||
Reference in New Issue
Block a user