Update SQLAlchemy
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
# ext/__init__.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# ext/associationproxy.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -24,17 +24,17 @@ from sqlalchemy.sql import not_
|
||||
|
||||
def association_proxy(target_collection, attr, **kw):
|
||||
"""Return a Python property implementing a view of a target
|
||||
attribute which references an attribute on members of the
|
||||
attribute which references an attribute on members of the
|
||||
target.
|
||||
|
||||
|
||||
The returned value is an instance of :class:`.AssociationProxy`.
|
||||
|
||||
|
||||
Implements a Python property representing a relationship as a collection of
|
||||
simpler values, or a scalar value. The proxied property will mimic the collection type of
|
||||
the target (list, dict or set), or, in the case of a one to one relationship,
|
||||
a simple scalar value.
|
||||
|
||||
:param target_collection: Name of the attribute we'll proxy to.
|
||||
:param target_collection: Name of the attribute we'll proxy to.
|
||||
This attribute is typically mapped by
|
||||
:func:`~sqlalchemy.orm.relationship` to link to a target collection, but
|
||||
can also be a many-to-one or non-scalar relationship.
|
||||
@@ -80,15 +80,15 @@ class AssociationProxy(object):
|
||||
"""A descriptor that presents a read/write view of an object attribute."""
|
||||
|
||||
def __init__(self, target_collection, attr, creator=None,
|
||||
getset_factory=None, proxy_factory=None,
|
||||
getset_factory=None, proxy_factory=None,
|
||||
proxy_bulk_set=None):
|
||||
"""Construct a new :class:`.AssociationProxy`.
|
||||
|
||||
|
||||
The :func:`.association_proxy` function is provided as the usual
|
||||
entrypoint here, though :class:`.AssociationProxy` can be instantiated
|
||||
and/or subclassed directly.
|
||||
|
||||
:param target_collection: Name of the collection we'll proxy to,
|
||||
:param target_collection: Name of the collection we'll proxy to,
|
||||
usually created with :func:`.relationship`.
|
||||
|
||||
:param attr: Attribute on the collected instances we'll proxy for. For example,
|
||||
@@ -120,7 +120,7 @@ class AssociationProxy(object):
|
||||
collection implementation, you may supply a factory function to
|
||||
produce those collections. Only applicable to non-scalar relationships.
|
||||
|
||||
:param proxy_bulk_set: Optional, use with proxy_factory. See
|
||||
:param proxy_bulk_set: Optional, use with proxy_factory. See
|
||||
the _set() method for details.
|
||||
|
||||
"""
|
||||
@@ -140,11 +140,11 @@ class AssociationProxy(object):
|
||||
def remote_attr(self):
|
||||
"""The 'remote' :class:`.MapperProperty` referenced by this
|
||||
:class:`.AssociationProxy`.
|
||||
|
||||
New in 0.7.3.
|
||||
|
||||
|
||||
.. versionadded:: 0.7.3
|
||||
|
||||
See also:
|
||||
|
||||
|
||||
:attr:`.AssociationProxy.attr`
|
||||
|
||||
:attr:`.AssociationProxy.local_attr`
|
||||
@@ -157,10 +157,10 @@ class AssociationProxy(object):
|
||||
"""The 'local' :class:`.MapperProperty` referenced by this
|
||||
:class:`.AssociationProxy`.
|
||||
|
||||
New in 0.7.3.
|
||||
|
||||
.. versionadded:: 0.7.3
|
||||
|
||||
See also:
|
||||
|
||||
|
||||
:attr:`.AssociationProxy.attr`
|
||||
|
||||
:attr:`.AssociationProxy.remote_attr`
|
||||
@@ -171,20 +171,20 @@ class AssociationProxy(object):
|
||||
@property
|
||||
def attr(self):
|
||||
"""Return a tuple of ``(local_attr, remote_attr)``.
|
||||
|
||||
This attribute is convenient when specifying a join
|
||||
|
||||
This attribute is convenient when specifying a join
|
||||
using :meth:`.Query.join` across two relationships::
|
||||
|
||||
|
||||
sess.query(Parent).join(*Parent.proxied.attr)
|
||||
|
||||
New in 0.7.3.
|
||||
|
||||
.. versionadded:: 0.7.3
|
||||
|
||||
See also:
|
||||
|
||||
|
||||
:attr:`.AssociationProxy.local_attr`
|
||||
|
||||
:attr:`.AssociationProxy.remote_attr`
|
||||
|
||||
|
||||
"""
|
||||
return (self.local_attr, self.remote_attr)
|
||||
|
||||
@@ -195,10 +195,10 @@ class AssociationProxy(object):
|
||||
@util.memoized_property
|
||||
def target_class(self):
|
||||
"""The intermediary class handled by this :class:`.AssociationProxy`.
|
||||
|
||||
|
||||
Intercepted append/set/assignment events will result
|
||||
in the generation of new instances of this class.
|
||||
|
||||
|
||||
"""
|
||||
return self._get_property().mapper.class_
|
||||
|
||||
@@ -333,10 +333,10 @@ class AssociationProxy(object):
|
||||
|
||||
def any(self, criterion=None, **kwargs):
|
||||
"""Produce a proxied 'any' expression using EXISTS.
|
||||
|
||||
|
||||
This expression will be a composed product
|
||||
using the :meth:`.RelationshipProperty.Comparator.any`
|
||||
and/or :meth:`.RelationshipProperty.Comparator.has`
|
||||
and/or :meth:`.RelationshipProperty.Comparator.has`
|
||||
operators of the underlying proxied attributes.
|
||||
|
||||
"""
|
||||
@@ -360,12 +360,12 @@ class AssociationProxy(object):
|
||||
|
||||
def has(self, criterion=None, **kwargs):
|
||||
"""Produce a proxied 'has' expression using EXISTS.
|
||||
|
||||
|
||||
This expression will be a composed product
|
||||
using the :meth:`.RelationshipProperty.Comparator.any`
|
||||
and/or :meth:`.RelationshipProperty.Comparator.has`
|
||||
and/or :meth:`.RelationshipProperty.Comparator.has`
|
||||
operators of the underlying proxied attributes.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
return self._comparator.has(
|
||||
@@ -375,7 +375,7 @@ class AssociationProxy(object):
|
||||
|
||||
def contains(self, obj):
|
||||
"""Produce a proxied 'contains' expression using EXISTS.
|
||||
|
||||
|
||||
This expression will be a composed product
|
||||
using the :meth:`.RelationshipProperty.Comparator.any`
|
||||
, :meth:`.RelationshipProperty.Comparator.has`,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# ext/compiler.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -91,9 +91,9 @@ Produces::
|
||||
|
||||
"INSERT INTO mytable (SELECT mytable.x, mytable.y, mytable.z FROM mytable WHERE mytable.x > :x_1)"
|
||||
|
||||
.. note::
|
||||
.. note::
|
||||
|
||||
The above ``InsertFromSelect`` construct probably wants to have "autocommit"
|
||||
The above ``InsertFromSelect`` construct probably wants to have "autocommit"
|
||||
enabled. See :ref:`enabling_compiled_autocommit` for this step.
|
||||
|
||||
Cross Compiling between SQL and DDL compilers
|
||||
@@ -118,12 +118,12 @@ Enabling Autocommit on a Construct
|
||||
|
||||
Recall from the section :ref:`autocommit` that the :class:`.Engine`, when asked to execute
|
||||
a construct in the absence of a user-defined transaction, detects if the given
|
||||
construct represents DML or DDL, that is, a data modification or data definition statement, which
|
||||
construct represents DML or DDL, that is, a data modification or data definition statement, which
|
||||
requires (or may require, in the case of DDL) that the transaction generated by the DBAPI be committed
|
||||
(recall that DBAPI always has a transaction going on regardless of what SQLAlchemy does). Checking
|
||||
(recall that DBAPI always has a transaction going on regardless of what SQLAlchemy does). Checking
|
||||
for this is actually accomplished
|
||||
by checking for the "autocommit" execution option on the construct. When building a construct like
|
||||
an INSERT derivation, a new DDL type, or perhaps a stored procedure that alters data, the "autocommit"
|
||||
an INSERT derivation, a new DDL type, or perhaps a stored procedure that alters data, the "autocommit"
|
||||
option needs to be set in order for the statement to function with "connectionless" execution
|
||||
(as described in :ref:`dbengine_implicit`).
|
||||
|
||||
@@ -146,13 +146,13 @@ can be used, which already is a subclass of :class:`.Executable`, :class:`.Claus
|
||||
class MyInsertThing(UpdateBase):
|
||||
def __init__(self, ...):
|
||||
...
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
DDL elements that subclass :class:`.DDLElement` already have the "autocommit" flag turned on.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Changing the default compilation of existing constructs
|
||||
@@ -163,7 +163,7 @@ the compilation of a built in SQL construct, the @compiles decorator is invoked
|
||||
the appropriate class (be sure to use the class, i.e. ``Insert`` or ``Select``, instead of the creation function such as ``insert()`` or ``select()``).
|
||||
|
||||
Within the new compilation function, to get at the "original" compilation routine,
|
||||
use the appropriate visit_XXX method - this because compiler.process() will call upon the
|
||||
use the appropriate visit_XXX method - this because compiler.process() will call upon the
|
||||
overriding routine and cause an endless loop. Such as, to add "prefix" to all insert statements::
|
||||
|
||||
from sqlalchemy.sql.expression import Insert
|
||||
@@ -205,7 +205,7 @@ A synopsis is as follows:
|
||||
expression class. Any SQL expression can be derived from this base, and is
|
||||
probably the best choice for longer constructs such as specialized INSERT
|
||||
statements.
|
||||
|
||||
|
||||
* :class:`~sqlalchemy.sql.expression.ColumnElement` - The root of all
|
||||
"column-like" elements. Anything that you'd place in the "columns" clause of
|
||||
a SELECT statement (as well as order by and group by) can derive from this -
|
||||
@@ -218,7 +218,7 @@ A synopsis is as follows:
|
||||
|
||||
class timestamp(ColumnElement):
|
||||
type = TIMESTAMP()
|
||||
|
||||
|
||||
* :class:`~sqlalchemy.sql.expression.FunctionElement` - This is a hybrid of a
|
||||
``ColumnElement`` and a "from clause" like object, and represents a SQL
|
||||
function or stored procedure type of call. Since most databases support
|
||||
@@ -250,7 +250,7 @@ A synopsis is as follows:
|
||||
|
||||
* :class:`~sqlalchemy.sql.expression.Executable` - This is a mixin which should be
|
||||
used with any expression class that represents a "standalone" SQL statement that
|
||||
can be passed directly to an ``execute()`` method. It is already implicit
|
||||
can be passed directly to an ``execute()`` method. It is already implicit
|
||||
within ``DDLElement`` and ``FunctionElement``.
|
||||
|
||||
Further Examples
|
||||
@@ -263,15 +263,15 @@ A function that works like "CURRENT_TIMESTAMP" except applies the appropriate co
|
||||
so that the time is in UTC time. Timestamps are best stored in relational databases
|
||||
as UTC, without time zones. UTC so that your database doesn't think time has gone
|
||||
backwards in the hour when daylight savings ends, without timezones because timezones
|
||||
are like character encodings - they're best applied only at the endpoints of an
|
||||
are like character encodings - they're best applied only at the endpoints of an
|
||||
application (i.e. convert to UTC upon user input, re-apply desired timezone upon display).
|
||||
|
||||
For Postgresql and Microsoft SQL Server::
|
||||
|
||||
|
||||
from sqlalchemy.sql import expression
|
||||
from sqlalchemy.ext.compiler import compiles
|
||||
from sqlalchemy.types import DateTime
|
||||
|
||||
|
||||
class utcnow(expression.FunctionElement):
|
||||
type = DateTime()
|
||||
|
||||
@@ -284,7 +284,7 @@ For Postgresql and Microsoft SQL Server::
|
||||
return "GETUTCDATE()"
|
||||
|
||||
Example usage::
|
||||
|
||||
|
||||
from sqlalchemy import (
|
||||
Table, Column, Integer, String, DateTime, MetaData
|
||||
)
|
||||
@@ -299,8 +299,8 @@ Example usage::
|
||||
-------------------
|
||||
|
||||
The "GREATEST" function is given any number of arguments and returns the one that is
|
||||
of the highest value - it's equivalent to Python's ``max`` function. A SQL
|
||||
standard version versus a CASE based version which only accommodates two
|
||||
of the highest value - it's equivalent to Python's ``max`` function. A SQL
|
||||
standard version versus a CASE based version which only accommodates two
|
||||
arguments::
|
||||
|
||||
from sqlalchemy.sql import expression
|
||||
@@ -332,7 +332,7 @@ Example usage::
|
||||
Session.query(Account).\\
|
||||
filter(
|
||||
greatest(
|
||||
Account.checking_balance,
|
||||
Account.checking_balance,
|
||||
Account.savings_balance) > 10000
|
||||
)
|
||||
|
||||
@@ -340,10 +340,10 @@ Example usage::
|
||||
------------------
|
||||
|
||||
Render a "false" constant expression, rendering as "0" on platforms that don't have a "false" constant::
|
||||
|
||||
|
||||
from sqlalchemy.sql import expression
|
||||
from sqlalchemy.ext.compiler import compiles
|
||||
|
||||
|
||||
class sql_false(expression.ColumnElement):
|
||||
pass
|
||||
|
||||
@@ -358,14 +358,14 @@ Render a "false" constant expression, rendering as "0" on platforms that don't h
|
||||
return "0"
|
||||
|
||||
Example usage::
|
||||
|
||||
|
||||
from sqlalchemy import select, union_all
|
||||
|
||||
exp = union_all(
|
||||
select([users.c.name, sql_false().label("enrolled")]),
|
||||
select([customers.c.name, customers.c.enrolled])
|
||||
)
|
||||
|
||||
|
||||
"""
|
||||
from sqlalchemy import exc
|
||||
|
||||
|
||||
+141
-143
@@ -1,5 +1,5 @@
|
||||
# ext/declarative.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -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.
|
||||
@@ -86,21 +86,11 @@ CREATE statements for all tables::
|
||||
engine = create_engine('sqlite://')
|
||||
Base.metadata.create_all(engine)
|
||||
|
||||
The usual techniques of associating :class:`.MetaData:` with :class:`.Engine`
|
||||
apply, such as assigning to the ``bind`` attribute::
|
||||
|
||||
Base.metadata.bind = create_engine('sqlite://')
|
||||
|
||||
To associate the engine with the :func:`declarative_base` at time
|
||||
of construction, the ``bind`` argument is accepted::
|
||||
|
||||
Base = declarative_base(bind=create_engine('sqlite://'))
|
||||
|
||||
: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 +103,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 +121,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 +138,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 +159,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 +175,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 +184,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 +209,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 +225,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 +243,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 +267,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,15 +282,15 @@ 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``::
|
||||
|
||||
from sqlalchemy.ext.declarative import synonym_for
|
||||
|
||||
|
||||
class MyClass(Base):
|
||||
__table__ = Table('my_table', Base.metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
@@ -320,14 +310,14 @@ 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 example
|
||||
described at :ref:`examples_declarative_reflection` to build a
|
||||
declarative base that sets up mappings only after a special
|
||||
described at :ref:`examples_declarative_reflection` to build a
|
||||
declarative base that sets up mappings only after a special
|
||||
``prepare(engine)`` step is called::
|
||||
|
||||
Base = declarative_base(cls=DeclarativeReflectedBase)
|
||||
@@ -339,14 +329,14 @@ declarative base that sets up mappings only after a special
|
||||
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'))
|
||||
|
||||
Base.prepare(e)
|
||||
|
||||
|
||||
|
||||
Mapper Configuration
|
||||
====================
|
||||
|
||||
@@ -354,7 +344,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
|
||||
@@ -383,7 +373,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):
|
||||
@@ -400,8 +390,8 @@ table::
|
||||
|
||||
Note that above, the ``Engineer.id`` attribute, since it shares the
|
||||
same attribute name as the ``Person.id`` attribute, will in fact
|
||||
represent the ``people.id`` and ``engineers.id`` columns together, and
|
||||
will render inside a query as ``"people.id"``.
|
||||
represent the ``people.id`` and ``engineers.id`` columns together,
|
||||
with the "Engineer.id" column taking precedence if queried directly.
|
||||
To provide the ``Engineer`` class with an attribute that represents
|
||||
only the ``engineers.id`` column, give it a different attribute name::
|
||||
|
||||
@@ -412,12 +402,17 @@ only the ``engineers.id`` column, give it a different attribute name::
|
||||
primary_key=True)
|
||||
primary_language = Column(String(50))
|
||||
|
||||
|
||||
.. 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'
|
||||
@@ -506,29 +501,31 @@ before the class is built::
|
||||
Using the Concrete Helpers
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
New helper classes released in 0.7.3 provides a simpler pattern for concrete inheritance.
|
||||
Helper classes provides a simpler pattern for concrete inheritance.
|
||||
With these objects, the ``__declare_last__`` helper is used to configure the "polymorphic"
|
||||
loader for the mapper after all subclasses have been declared.
|
||||
|
||||
.. versionadded:: 0.7.3
|
||||
|
||||
An abstract base can be declared using the :class:`.AbstractConcreteBase` class::
|
||||
|
||||
from sqlalchemy.ext.declarative import AbstractConcreteBase
|
||||
|
||||
|
||||
class Employee(AbstractConcreteBase, Base):
|
||||
pass
|
||||
|
||||
To have a concrete ``employee`` table, use :class:`.ConcreteBase` instead::
|
||||
|
||||
from sqlalchemy.ext.declarative import ConcreteBase
|
||||
|
||||
|
||||
class Employee(ConcreteBase, Base):
|
||||
__tablename__ = 'employee'
|
||||
employee_id = Column(Integer, primary_key=True)
|
||||
name = Column(String(50))
|
||||
__mapper_args__ = {
|
||||
'polymorphic_identity':'employee',
|
||||
'polymorphic_identity':'employee',
|
||||
'concrete':True}
|
||||
|
||||
|
||||
|
||||
Either ``Employee`` base can be used in the normal fashion::
|
||||
|
||||
@@ -538,7 +535,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):
|
||||
@@ -546,7 +543,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}
|
||||
|
||||
|
||||
@@ -569,7 +566,7 @@ mappings are declared. An example of some commonly mixed-in
|
||||
idioms is below::
|
||||
|
||||
from sqlalchemy.ext.declarative import declared_attr
|
||||
|
||||
|
||||
class MyMixin(object):
|
||||
|
||||
@declared_attr
|
||||
@@ -586,29 +583,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
|
||||
@@ -617,26 +614,26 @@ is achieved using the ``cls`` argument of the :func:`.declarative_base` function
|
||||
@declared_attr
|
||||
def __tablename__(cls):
|
||||
return cls.__name__.lower()
|
||||
|
||||
|
||||
__table_args__ = {'mysql_engine': 'InnoDB'}
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
|
||||
|
||||
Base = declarative_base(cls=Base)
|
||||
|
||||
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):
|
||||
@@ -649,30 +646,29 @@ 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 (renamed from ``sqlalchemy.util.classproperty`` in 0.6.5)
|
||||
is provided so that
|
||||
:func:`~.declared_attr` decorator is provided so that
|
||||
patterns common to many classes can be defined as callables::
|
||||
|
||||
from sqlalchemy.ext.declarative import declared_attr
|
||||
@@ -686,14 +682,17 @@ 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.
|
||||
|
||||
.. versionchanged:: > 0.6.5
|
||||
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::
|
||||
|
||||
@@ -713,7 +712,7 @@ Mixing in Relationships
|
||||
|
||||
Relationships created by :func:`~sqlalchemy.orm.relationship` are provided
|
||||
with declarative mixin classes exclusively using the
|
||||
:func:`.declared_attr` approach, eliminating any ambiguity
|
||||
:class:`.declared_attr` approach, eliminating any ambiguity
|
||||
which could arise when copying a relationship and its possibly column-bound
|
||||
contents. Below is an example which combines a foreign key column and a
|
||||
relationship so that two classes ``Foo`` and ``Bar`` can both be configured to
|
||||
@@ -741,10 +740,10 @@ 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::
|
||||
to get its name::
|
||||
|
||||
class RefTargetMixin(object):
|
||||
@declared_attr
|
||||
@@ -763,8 +762,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 :class:`.declared_attr`
|
||||
requirement so that no reliance on copying is needed::
|
||||
|
||||
class SomethingMixin(object):
|
||||
@@ -781,7 +780,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.
|
||||
|
||||
@@ -816,7 +815,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::
|
||||
@@ -879,7 +878,7 @@ In the case of ``__table_args__`` or ``__mapper_args__``
|
||||
specified with declarative mixins, you may want to combine
|
||||
some parameters from several mixins with those you wish to
|
||||
define on the class iteself. The
|
||||
:func:`.declared_attr` decorator can be used
|
||||
:class:`.declared_attr` decorator can be used
|
||||
here to create user-defined collation routines that pull
|
||||
from multiple collections::
|
||||
|
||||
@@ -906,7 +905,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__``::
|
||||
|
||||
@@ -928,7 +927,7 @@ Special Directives
|
||||
``__declare_last__()``
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``__declare_last__()`` hook, introduced in 0.7.3, 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::
|
||||
@@ -939,29 +938,31 @@ has finished::
|
||||
""
|
||||
# do something with mappings
|
||||
|
||||
.. versionadded:: 0.7.3
|
||||
|
||||
.. _declarative_abstract:
|
||||
|
||||
``__abstract__``
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``__abstract__`` is introduced in 0.7.3 and causes declarative to skip the production
|
||||
``__abstract__`` causes declarative to skip the production
|
||||
of a table or mapper for the class entirely. A class can be added within a hierarchy
|
||||
in the same way as mixin (see :ref:`declarative_mixins`), allowing subclasses to extend
|
||||
just from the special class::
|
||||
|
||||
class SomeAbstractBase(Base):
|
||||
__abstract__ = True
|
||||
|
||||
|
||||
def some_helpful_method(self):
|
||||
""
|
||||
|
||||
|
||||
@declared_attr
|
||||
def __mapper_args__(cls):
|
||||
return {"helpful mapper arguments":True}
|
||||
|
||||
class MyMappedClass(SomeAbstractBase):
|
||||
""
|
||||
|
||||
|
||||
One possible use of ``__abstract__`` is to use a distinct :class:`.MetaData` for different
|
||||
bases::
|
||||
|
||||
@@ -975,13 +976,15 @@ bases::
|
||||
__abstract__ = True
|
||||
metadata = MetaData()
|
||||
|
||||
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.
|
||||
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::
|
||||
|
||||
DefaultBase.metadata.create_all(some_engine)
|
||||
OtherBase.metadata_create_all(some_other_engine)
|
||||
|
||||
.. versionadded:: 0.7.3
|
||||
|
||||
Class Constructor
|
||||
=================
|
||||
|
||||
@@ -1006,7 +1009,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.
|
||||
|
||||
"""
|
||||
|
||||
@@ -1028,7 +1031,7 @@ __all__ = 'declarative_base', 'synonym_for', \
|
||||
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__:
|
||||
@@ -1071,7 +1074,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
|
||||
@@ -1083,19 +1086,19 @@ def _as_declarative(cls, classname, dict_):
|
||||
for name,obj in vars(base).items():
|
||||
if name == '__mapper_args__':
|
||||
if not mapper_args and (
|
||||
not class_mapped or
|
||||
not class_mapped or
|
||||
isinstance(obj, declarative_props)
|
||||
):
|
||||
mapper_args = 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__
|
||||
@@ -1110,7 +1113,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:
|
||||
@@ -1122,7 +1125,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] = \
|
||||
@@ -1151,7 +1154,7 @@ def _as_declarative(cls, classname, dict_):
|
||||
if inherited_table_args and not tablename:
|
||||
table_args = None
|
||||
|
||||
# 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:
|
||||
@@ -1204,7 +1207,7 @@ def _as_declarative(cls, classname, dict_):
|
||||
elif isinstance(c, Column):
|
||||
_undefer_column_name(key, c)
|
||||
cols.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
|
||||
@@ -1291,7 +1294,7 @@ 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)
|
||||
@@ -1310,7 +1313,7 @@ def _as_declarative(cls, classname, dict_):
|
||||
if c not in inherited_mapper._columntoproperty])
|
||||
exclude_properties.difference_update([c.key for c in cols])
|
||||
|
||||
# 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).
|
||||
@@ -1322,25 +1325,21 @@ def _as_declarative(cls, classname, dict_):
|
||||
if k in inherited_mapper._props:
|
||||
p = inherited_mapper._props[k]
|
||||
if isinstance(p, ColumnProperty):
|
||||
# note here we place the superclass column
|
||||
# first. this corresponds to the
|
||||
# append() in mapper._configure_property().
|
||||
# change this ordering when we do [ticket:1892]
|
||||
our_stuff[k] = p.columns + [col]
|
||||
# note here we place the subclass column
|
||||
# first. See [ticket:1892] for background.
|
||||
our_stuff[k] = [col] + p.columns
|
||||
|
||||
|
||||
cls.__mapper__ = mapper_cls(cls,
|
||||
table,
|
||||
properties=our_stuff,
|
||||
cls.__mapper__ = mapper_cls(cls,
|
||||
table,
|
||||
properties=our_stuff,
|
||||
**mapper_args)
|
||||
|
||||
class DeclarativeMeta(type):
|
||||
def __init__(cls, classname, bases, dict_):
|
||||
if '_decl_class_registry' in cls.__dict__:
|
||||
return type.__init__(cls, classname, bases, dict_)
|
||||
else:
|
||||
if '_decl_class_registry' not in cls.__dict__:
|
||||
_as_declarative(cls, classname, cls.__dict__)
|
||||
return type.__init__(cls, classname, bases, dict_)
|
||||
type.__init__(cls, classname, bases, dict_)
|
||||
|
||||
def __setattr__(cls, key, value):
|
||||
if '__mapper__' in cls.__dict__:
|
||||
@@ -1356,7 +1355,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:
|
||||
@@ -1423,7 +1422,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
|
||||
@@ -1493,15 +1492,14 @@ class declared_attr(property):
|
||||
"""Mark a class-level method as representing the definition of
|
||||
a mapped property or special declarative member name.
|
||||
|
||||
.. note::
|
||||
|
||||
@declared_attr is available as
|
||||
``sqlalchemy.util.classproperty`` for SQLAlchemy versions
|
||||
0.6.2, 0.6.3, 0.6.4.
|
||||
.. versionchanged:: 0.6.{2,3,4}
|
||||
``@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
|
||||
@@ -1533,7 +1531,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:
|
||||
@@ -1581,8 +1579,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
|
||||
@@ -1613,13 +1611,13 @@ 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:
|
||||
Defaults to :class:`.DeclarativeMeta`. A metaclass or __metaclass__
|
||||
compatible callable to use as the meta type of the generated
|
||||
@@ -1652,7 +1650,7 @@ def _undefer_column_name(key, column):
|
||||
|
||||
class ConcreteBase(object):
|
||||
"""A helper class for 'concrete' declarative mappings.
|
||||
|
||||
|
||||
:class:`.ConcreteBase` will use the :func:`.polymorphic_union`
|
||||
function automatically, against all tables mapped as a subclass
|
||||
to this class. The function is called via the
|
||||
@@ -1662,7 +1660,7 @@ class ConcreteBase(object):
|
||||
:class:`.ConcreteBase` produces a mapped
|
||||
table for the class itself. Compare to :class:`.AbstractConcreteBase`,
|
||||
which does not.
|
||||
|
||||
|
||||
Example::
|
||||
|
||||
from sqlalchemy.ext.declarative import ConcreteBase
|
||||
@@ -1672,7 +1670,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):
|
||||
@@ -1681,7 +1679,7 @@ class ConcreteBase(object):
|
||||
name = Column(String(50))
|
||||
manager_data = Column(String(40))
|
||||
__mapper_args__ = {
|
||||
'polymorphic_identity':'manager',
|
||||
'polymorphic_identity':'manager',
|
||||
'concrete':True}
|
||||
|
||||
"""
|
||||
@@ -1706,17 +1704,17 @@ class ConcreteBase(object):
|
||||
|
||||
class AbstractConcreteBase(ConcreteBase):
|
||||
"""A helper class for 'concrete' declarative mappings.
|
||||
|
||||
|
||||
:class:`.AbstractConcreteBase` will use the :func:`.polymorphic_union`
|
||||
function automatically, against all tables mapped as a subclass
|
||||
to this class. The function is called via the
|
||||
``__declare_last__()`` function, which is essentially
|
||||
a hook for the :func:`.MapperEvents.after_configured` event.
|
||||
|
||||
|
||||
:class:`.AbstractConcreteBase` does not produce a mapped
|
||||
table for the class itself. Compare to :class:`.ConcreteBase`,
|
||||
which does.
|
||||
|
||||
|
||||
Example::
|
||||
|
||||
from sqlalchemy.ext.declarative import ConcreteBase
|
||||
@@ -1730,7 +1728,7 @@ class AbstractConcreteBase(ConcreteBase):
|
||||
name = Column(String(50))
|
||||
manager_data = Column(String(40))
|
||||
__mapper_args__ = {
|
||||
'polymorphic_identity':'manager',
|
||||
'polymorphic_identity':'manager',
|
||||
'concrete':True}
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# ext/horizontal_shard.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -9,8 +9,8 @@
|
||||
Defines a rudimental 'horizontal sharding' system which allows a Session to
|
||||
distribute queries and persistence operations across multiple databases.
|
||||
|
||||
For a usage example, see the :ref:`examples_sharding` example included in
|
||||
the source distrbution.
|
||||
For a usage example, see the :ref:`examples_sharding` example included in
|
||||
the source distribution.
|
||||
|
||||
"""
|
||||
|
||||
@@ -31,7 +31,7 @@ class ShardedQuery(Query):
|
||||
def set_shard(self, shard_id):
|
||||
"""return a new query, limited to a single shard ID.
|
||||
|
||||
all subsequent operations with the returned query will
|
||||
all subsequent operations with the returned query will
|
||||
be against the single shard regardless of other state.
|
||||
"""
|
||||
|
||||
@@ -45,7 +45,7 @@ class ShardedQuery(Query):
|
||||
result = self._connection_from_session(
|
||||
mapper=self._mapper_zero(),
|
||||
shard_id=shard_id).execute(
|
||||
context.statement,
|
||||
context.statement,
|
||||
self._params)
|
||||
return self.instances(result, context)
|
||||
|
||||
@@ -56,7 +56,7 @@ class ShardedQuery(Query):
|
||||
for shard_id in self.query_chooser(self):
|
||||
partial.extend(iter_for_shard(shard_id))
|
||||
|
||||
# if some kind of in memory 'sorting'
|
||||
# if some kind of in memory 'sorting'
|
||||
# were done, this is where it would happen
|
||||
return iter(partial)
|
||||
|
||||
@@ -73,7 +73,7 @@ class ShardedQuery(Query):
|
||||
return None
|
||||
|
||||
class ShardedSession(Session):
|
||||
def __init__(self, shard_chooser, id_chooser, query_chooser, shards=None,
|
||||
def __init__(self, shard_chooser, id_chooser, query_chooser, shards=None,
|
||||
query_cls=ShardedQuery, **kwargs):
|
||||
"""Construct a ShardedSession.
|
||||
|
||||
@@ -113,8 +113,8 @@ class ShardedSession(Session):
|
||||
if self.transaction is not None:
|
||||
return self.transaction.connection(mapper, shard_id=shard_id)
|
||||
else:
|
||||
return self.get_bind(mapper,
|
||||
shard_id=shard_id,
|
||||
return self.get_bind(mapper,
|
||||
shard_id=shard_id,
|
||||
instance=instance).contextual_connect(**kwargs)
|
||||
|
||||
def get_bind(self, mapper, shard_id=None, instance=None, clause=None, **kw):
|
||||
|
||||
+169
-104
@@ -1,5 +1,5 @@
|
||||
# ext/hybrid.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -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,17 +179,24 @@ 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
|
||||
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``::
|
||||
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. Two variants of we'll illustrate
|
||||
are the "join-dependent" hybrid, and the "correlated subquery" hybrid.
|
||||
|
||||
Join-Dependent Relationship Hybrid
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Consider the following declarative
|
||||
mapping which relates a ``User`` to a ``SavingsAccount``::
|
||||
|
||||
from sqlalchemy import Column, Integer, ForeignKey, Numeric, String
|
||||
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 +207,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,30 +229,88 @@ 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
|
||||
accounts for this user. The in-Python getter/setter methods can treat ``accounts`` as a Python
|
||||
list available on ``self``.
|
||||
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``.
|
||||
|
||||
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::
|
||||
However, at the expression level, 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
|
||||
>>> 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
|
||||
WHERE account.balance > :balance_1
|
||||
|
||||
Note however, that while the instance level accessors need to worry about whether ``self.accounts``
|
||||
is even present, this issue expresses itself differently at the SQL expression level, where we basically
|
||||
Note however, that while the instance level accessors need to worry
|
||||
about whether ``self.accounts`` is even present, this issue expresses
|
||||
itself differently at the SQL expression level, where we basically
|
||||
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
|
||||
|
||||
Correlated Subquery Relationship Hybrid
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
We can, of course, forego being dependent on the enclosing query's usage
|
||||
of joins in favor of the correlated
|
||||
subquery, which can portably be packed into a single colunn expression.
|
||||
A correlated subquery is more portable, but often performs more poorly
|
||||
at the SQL level.
|
||||
Using the same technique illustrated at :ref:`mapper_column_property_sql_expressions`,
|
||||
we can adjust our ``SavingsAccount`` example to aggregate the balances for
|
||||
*all* accounts, and use a correlated subquery for the column expression::
|
||||
|
||||
from sqlalchemy import Column, Integer, ForeignKey, Numeric, String
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.ext.hybrid import hybrid_property
|
||||
from sqlalchemy import select, func
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
class SavingsAccount(Base):
|
||||
__tablename__ = 'account'
|
||||
id = Column(Integer, primary_key=True)
|
||||
user_id = Column(Integer, ForeignKey('user.id'), nullable=False)
|
||||
balance = Column(Numeric(15, 5))
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = 'user'
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String(100), nullable=False)
|
||||
|
||||
accounts = relationship("SavingsAccount", backref="owner")
|
||||
|
||||
@hybrid_property
|
||||
def balance(self):
|
||||
return sum(acc.balance for acc in self.accounts)
|
||||
|
||||
@balance.expression
|
||||
def balance(cls):
|
||||
return select([func.sum(SavingsAccount.balance)]).\\
|
||||
where(SavingsAccount.user_id==cls.id).\\
|
||||
label('total_balance')
|
||||
|
||||
The above recipe will give us the ``balance`` column which renders
|
||||
a correlated SELECT::
|
||||
|
||||
>>> print s.query(User).filter(User.balance > 400)
|
||||
SELECT "user".id AS user_id, "user".name AS user_name
|
||||
FROM "user"
|
||||
WHERE (SELECT sum(account.balance) AS sum_1
|
||||
FROM account
|
||||
WHERE account.user_id = "user".id) > :param_1
|
||||
|
||||
.. _hybrid_custom_comparators:
|
||||
|
||||
Building Custom Comparators
|
||||
@@ -253,7 +318,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 +328,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 +339,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 +375,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 +407,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 +421,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 +434,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 +468,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 +477,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 +496,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 +525,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 +551,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 +594,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 +611,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 +650,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 +712,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.\
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# ext/mutable.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -21,8 +21,8 @@ Establishing Mutability on Scalar Column Values
|
||||
===============================================
|
||||
|
||||
A typical example of a "mutable" structure is a Python dictionary.
|
||||
Following the example introduced in :ref:`types_toplevel`, we
|
||||
begin with a custom type that marshals Python dictionaries into
|
||||
Following the example introduced in :ref:`types_toplevel`, we
|
||||
begin with a custom type that marshals Python dictionaries into
|
||||
JSON strings before being persisted::
|
||||
|
||||
from sqlalchemy.types import TypeDecorator, VARCHAR
|
||||
@@ -43,7 +43,7 @@ JSON strings before being persisted::
|
||||
value = json.loads(value)
|
||||
return value
|
||||
|
||||
The usage of ``json`` is only for the purposes of example. The :mod:`sqlalchemy.ext.mutable`
|
||||
The usage of ``json`` is only for the purposes of example. The :mod:`sqlalchemy.ext.mutable`
|
||||
extension can be used
|
||||
with any type whose target Python type may be mutable, including
|
||||
:class:`.PickleType`, :class:`.postgresql.ARRAY`, etc.
|
||||
@@ -86,7 +86,7 @@ The above dictionary class takes the approach of subclassing the Python
|
||||
built-in ``dict`` to produce a dict
|
||||
subclass which routes all mutation events through ``__setitem__``. There are
|
||||
many variants on this approach, such as subclassing ``UserDict.UserDict``,
|
||||
the newer ``collections.MutableMapping``, etc. The part that's important to this
|
||||
the newer ``collections.MutableMapping``, etc. The part that's important to this
|
||||
example is that the :meth:`.Mutable.changed` method is called whenever an in-place change to the
|
||||
datastructure takes place.
|
||||
|
||||
@@ -95,7 +95,7 @@ convert any values that are not instances of ``MutationDict``, such
|
||||
as the plain dictionaries returned by the ``json`` module, into the
|
||||
appropriate type. Defining this method is optional; we could just as well created our
|
||||
``JSONEncodedDict`` such that it always returns an instance of ``MutationDict``,
|
||||
and additionally ensured that all calling code uses ``MutationDict``
|
||||
and additionally ensured that all calling code uses ``MutationDict``
|
||||
explicitly. When :meth:`.Mutable.coerce` is not overridden, any values
|
||||
applied to a parent object which are not instances of the mutable type
|
||||
will raise a ``ValueError``.
|
||||
@@ -108,14 +108,14 @@ of this type, applying event listening instrumentation to the mapped
|
||||
attribute. Such as, with classical table metadata::
|
||||
|
||||
from sqlalchemy import Table, Column, Integer
|
||||
|
||||
|
||||
my_data = Table('my_data', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('data', MutationDict.as_mutable(JSONEncodedDict))
|
||||
)
|
||||
|
||||
Above, :meth:`~.Mutable.as_mutable` returns an instance of ``JSONEncodedDict``
|
||||
(if the type object was not an instance already), which will intercept any
|
||||
(if the type object was not an instance already), which will intercept any
|
||||
attributes which are mapped against this type. Below we establish a simple
|
||||
mapping against the ``my_data`` table::
|
||||
|
||||
@@ -157,7 +157,7 @@ will flag the attribute as "dirty" on the parent object::
|
||||
|
||||
The ``MutationDict`` can be associated with all future instances
|
||||
of ``JSONEncodedDict`` in one step, using :meth:`~.Mutable.associate_with`. This
|
||||
is similar to :meth:`~.Mutable.as_mutable` except it will intercept
|
||||
is similar to :meth:`~.Mutable.as_mutable` except it will intercept
|
||||
all occurrences of ``MutationDict`` in all mappings unconditionally, without
|
||||
the need to declare it individually::
|
||||
|
||||
@@ -167,8 +167,8 @@ the need to declare it individually::
|
||||
__tablename__ = 'my_data'
|
||||
id = Column(Integer, primary_key=True)
|
||||
data = Column(JSONEncodedDict)
|
||||
|
||||
|
||||
|
||||
|
||||
Supporting Pickling
|
||||
--------------------
|
||||
|
||||
@@ -180,7 +180,7 @@ not picklable, due to the fact that they contain weakrefs and function
|
||||
callbacks. In our case, this is a good thing, since if this dictionary were
|
||||
picklable, it could lead to an excessively large pickle size for our value
|
||||
objects that are pickled by themselves outside of the context of the parent.
|
||||
The developer responsiblity here is only to provide a ``__getstate__`` method
|
||||
The developer responsibility here is only to provide a ``__getstate__`` method
|
||||
that excludes the :meth:`~.MutableBase._parents` collection from the pickle
|
||||
stream::
|
||||
|
||||
@@ -217,12 +217,13 @@ be assigned an object value which represents information "composed" from one
|
||||
or more columns from the underlying mapped table. The usual example is that of
|
||||
a geometric "point", and is introduced in :ref:`mapper_composite`.
|
||||
|
||||
As of SQLAlchemy 0.7, the internals of :func:`.orm.composite` have been
|
||||
greatly simplified and in-place mutation detection is no longer enabled by
|
||||
default; instead, the user-defined value must detect changes on its own and
|
||||
propagate them to all owning parents. The :mod:`sqlalchemy.ext.mutable`
|
||||
extension provides the helper class :class:`.MutableComposite`, which is a
|
||||
slight variant on the :class:`.Mutable` class.
|
||||
.. versionchanged:: 0.7
|
||||
The internals of :func:`.orm.composite` have been
|
||||
greatly simplified and in-place mutation detection is no longer enabled by
|
||||
default; instead, the user-defined value must detect changes on its own and
|
||||
propagate them to all owning parents. The :mod:`sqlalchemy.ext.mutable`
|
||||
extension provides the helper class :class:`.MutableComposite`, which is a
|
||||
slight variant on the :class:`.Mutable` class.
|
||||
|
||||
As is the case with :class:`.Mutable`, the user-defined composite class
|
||||
subclasses :class:`.MutableComposite` as a mixin, and detects and delivers
|
||||
@@ -300,6 +301,31 @@ will flag the attribute as "dirty" on the parent object::
|
||||
>>> assert v1 in sess.dirty
|
||||
True
|
||||
|
||||
Coercing Mutable Composites
|
||||
---------------------------
|
||||
|
||||
The :meth:`.MutableBase.coerce` method is also supported on composite types.
|
||||
In the case of :class:`.MutableComposite`, the :meth:`.MutableBase.coerce`
|
||||
method is only called for attribute set operations, not load operations.
|
||||
Overriding the :meth:`.MutableBase.coerce` method is essentially equivalent
|
||||
to using a :func:`.validates` validation routine for all attributes which
|
||||
make use of the custom composite type::
|
||||
|
||||
class Point(MutableComposite):
|
||||
# other Point methods
|
||||
# ...
|
||||
|
||||
def coerce(cls, key, value):
|
||||
if isinstance(value, tuple):
|
||||
value = Point(*value)
|
||||
elif not isinstance(value, Point):
|
||||
raise ValueError("tuple or Point expected")
|
||||
return value
|
||||
|
||||
.. versionadded:: 0.7.10,0.8.0b2
|
||||
Support for the :meth:`.MutableBase.coerce` method in conjunction with
|
||||
objects of type :class:`.MutableComposite`.
|
||||
|
||||
Supporting Pickling
|
||||
--------------------
|
||||
|
||||
@@ -313,10 +339,10 @@ the minimal form of our ``Point`` class::
|
||||
|
||||
class Point(MutableComposite):
|
||||
# ...
|
||||
|
||||
|
||||
def __getstate__(self):
|
||||
return self.x, self.y
|
||||
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.x, self.y = state
|
||||
|
||||
@@ -327,7 +353,7 @@ pickling process of the parent's object-relational state so that the
|
||||
"""
|
||||
from sqlalchemy.orm.attributes import flag_modified
|
||||
from sqlalchemy import event, types
|
||||
from sqlalchemy.orm import mapper, object_mapper
|
||||
from sqlalchemy.orm import mapper, object_mapper, Mapper
|
||||
from sqlalchemy.util import memoized_property
|
||||
import weakref
|
||||
|
||||
@@ -337,20 +363,38 @@ class MutableBase(object):
|
||||
@memoized_property
|
||||
def _parents(self):
|
||||
"""Dictionary of parent object->attribute name on the parent.
|
||||
|
||||
|
||||
This attribute is a so-called "memoized" property. It initializes
|
||||
itself with a new ``weakref.WeakKeyDictionary`` the first time
|
||||
it is accessed, returning the same object upon subsequent access.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
return weakref.WeakKeyDictionary()
|
||||
|
||||
@classmethod
|
||||
def coerce(cls, key, value):
|
||||
"""Given a value, coerce it into this type.
|
||||
"""Given a value, coerce it into the target type.
|
||||
|
||||
Can be overridden by custom subclasses to coerce incoming
|
||||
data into a particular type.
|
||||
|
||||
By default, raises ``ValueError``.
|
||||
|
||||
This method is called in different scenarios depending on if
|
||||
the parent class is of type :class:`.Mutable` or of type
|
||||
:class:`.MutableComposite`. In the case of the former, it is called
|
||||
for both attribute-set operations as well as during ORM loading
|
||||
operations. For the latter, it is only called during attribute-set
|
||||
operations; the mechanics of the :func:`.composite` construct
|
||||
handle coercion during load operations.
|
||||
|
||||
|
||||
:param key: string name of the ORM-mapped attribute being set.
|
||||
:param value: the incoming value.
|
||||
:return: the method should return the coerced value, or raise
|
||||
``ValueError`` if the coercion cannot be completed.
|
||||
|
||||
By default raises ValueError.
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
@@ -358,7 +402,7 @@ class MutableBase(object):
|
||||
|
||||
@classmethod
|
||||
def _listen_on_attribute(cls, attribute, coerce, parent_cls):
|
||||
"""Establish this type as a mutation listener for the given
|
||||
"""Establish this type as a mutation listener for the given
|
||||
mapped descriptor.
|
||||
|
||||
"""
|
||||
@@ -372,7 +416,7 @@ class MutableBase(object):
|
||||
def load(state, *args):
|
||||
"""Listen for objects loaded or refreshed.
|
||||
|
||||
Wrap the target data member's value with
|
||||
Wrap the target data member's value with
|
||||
``Mutable``.
|
||||
|
||||
"""
|
||||
@@ -388,7 +432,7 @@ class MutableBase(object):
|
||||
data member.
|
||||
|
||||
Establish a weak reference to the parent object
|
||||
on the incoming value, remove it for the one
|
||||
on the incoming value, remove it for the one
|
||||
outgoing.
|
||||
|
||||
"""
|
||||
@@ -435,7 +479,7 @@ class Mutable(MutableBase):
|
||||
|
||||
@classmethod
|
||||
def associate_with_attribute(cls, attribute):
|
||||
"""Establish this type as a mutation listener for the given
|
||||
"""Establish this type as a mutation listener for the given
|
||||
mapped descriptor.
|
||||
|
||||
"""
|
||||
@@ -443,15 +487,15 @@ class Mutable(MutableBase):
|
||||
|
||||
@classmethod
|
||||
def associate_with(cls, sqltype):
|
||||
"""Associate this wrapper with all future mapped columns
|
||||
"""Associate this wrapper with all future mapped columns
|
||||
of the given type.
|
||||
|
||||
This is a convenience method that calls ``associate_with_attribute`` automatically.
|
||||
|
||||
.. warning::
|
||||
|
||||
.. warning::
|
||||
|
||||
The listeners established by this method are *global*
|
||||
to all mappers, and are *not* garbage collected. Only use
|
||||
to all mappers, and are *not* garbage collected. Only use
|
||||
:meth:`.associate_with` for types that are permanent to an application,
|
||||
not with ad-hoc types else this will cause unbounded growth
|
||||
in memory usage.
|
||||
@@ -473,7 +517,7 @@ class Mutable(MutableBase):
|
||||
This establishes listeners that will detect ORM mappings against
|
||||
the given type, adding mutation event trackers to those mappings.
|
||||
|
||||
The type is returned, unconditionally as an instance, so that
|
||||
The type is returned, unconditionally as an instance, so that
|
||||
:meth:`.as_mutable` can be used inline::
|
||||
|
||||
Table('mytable', metadata,
|
||||
@@ -485,15 +529,15 @@ class Mutable(MutableBase):
|
||||
is given, and that only columns which are declared specifically with that
|
||||
type instance receive additional instrumentation.
|
||||
|
||||
To associate a particular mutable type with all occurrences of a
|
||||
To associate a particular mutable type with all occurrences of a
|
||||
particular type, use the :meth:`.Mutable.associate_with` classmethod
|
||||
of the particular :meth:`.Mutable` subclass to establish a global
|
||||
association.
|
||||
|
||||
.. warning::
|
||||
|
||||
.. warning::
|
||||
|
||||
The listeners established by this method are *global*
|
||||
to all mappers, and are *not* garbage collected. Only use
|
||||
to all mappers, and are *not* garbage collected. Only use
|
||||
:meth:`.as_mutable` for types that are permanent to an application,
|
||||
not with ad-hoc types else this will cause unbounded growth
|
||||
in memory usage.
|
||||
@@ -511,28 +555,22 @@ class Mutable(MutableBase):
|
||||
|
||||
return sqltype
|
||||
|
||||
class _MutableCompositeMeta(type):
|
||||
def __init__(cls, classname, bases, dict_):
|
||||
cls._setup_listeners()
|
||||
return type.__init__(cls, classname, bases, dict_)
|
||||
|
||||
class MutableComposite(MutableBase):
|
||||
"""Mixin that defines transparent propagation of change
|
||||
events on a SQLAlchemy "composite" object to its
|
||||
owning parent or parents.
|
||||
|
||||
|
||||
See the example in :ref:`mutable_composites` for usage information.
|
||||
|
||||
.. warning::
|
||||
|
||||
|
||||
.. warning::
|
||||
|
||||
The listeners established by the :class:`.MutableComposite`
|
||||
class are *global* to all mappers, and are *not* garbage collected. Only use
|
||||
class are *global* to all mappers, and are *not* garbage collected. Only use
|
||||
:class:`.MutableComposite` for types that are permanent to an application,
|
||||
not with ad-hoc types else this will cause unbounded growth
|
||||
in memory usage.
|
||||
|
||||
"""
|
||||
__metaclass__ = _MutableCompositeMeta
|
||||
|
||||
def changed(self):
|
||||
"""Subclasses should call this method whenever change events occur."""
|
||||
@@ -541,23 +579,18 @@ class MutableComposite(MutableBase):
|
||||
|
||||
prop = object_mapper(parent).get_property(key)
|
||||
for value, attr_name in zip(
|
||||
self.__composite_values__(),
|
||||
self.__composite_values__(),
|
||||
prop._attribute_keys):
|
||||
setattr(parent, attr_name, value)
|
||||
|
||||
@classmethod
|
||||
def _setup_listeners(cls):
|
||||
"""Associate this wrapper with all future mapped composites
|
||||
of the given type.
|
||||
|
||||
This is a convenience method that calls ``associate_with_attribute`` automatically.
|
||||
|
||||
"""
|
||||
|
||||
def listen_for_type(mapper, class_):
|
||||
for prop in mapper.iterate_properties:
|
||||
if hasattr(prop, 'composite_class') and issubclass(prop.composite_class, cls):
|
||||
cls._listen_on_attribute(getattr(class_, prop.key), False, class_)
|
||||
|
||||
event.listen(mapper, 'mapper_configured', listen_for_type)
|
||||
|
||||
def _setup_composite_listener():
|
||||
def _listen_for_type(mapper, class_):
|
||||
for prop in mapper.iterate_properties:
|
||||
if (hasattr(prop, 'composite_class') and
|
||||
issubclass(prop.composite_class, MutableComposite)):
|
||||
prop.composite_class._listen_on_attribute(
|
||||
getattr(class_, prop.key), False, class_)
|
||||
if not Mapper.dispatch.mapper_configured._contains(Mapper, _listen_for_type):
|
||||
event.listen(Mapper, 'mapper_configured', _listen_for_type)
|
||||
_setup_composite_listener()
|
||||
|
||||
@@ -1,64 +1,77 @@
|
||||
# ext/orderinglist.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""A custom list that manages index/position information for its children.
|
||||
"""A custom list that manages index/position information for contained
|
||||
elements.
|
||||
|
||||
:author: Jason Kirtland
|
||||
|
||||
``orderinglist`` is a helper for mutable ordered relationships. It will intercept
|
||||
list operations performed on a relationship collection and automatically
|
||||
synchronize changes in list position with an attribute on the related objects.
|
||||
(See :ref:`advdatamapping_entitycollections` for more information on the general pattern.)
|
||||
``orderinglist`` is a helper for mutable ordered relationships. It will
|
||||
intercept list operations performed on a :func:`.relationship`-managed
|
||||
collection and
|
||||
automatically synchronize changes in list position onto a target scalar
|
||||
attribute.
|
||||
|
||||
Example: Two tables that store slides in a presentation. Each slide
|
||||
has a number of bullet points, displayed in order by the 'position'
|
||||
column on the bullets table. These bullets can be inserted and re-ordered
|
||||
by your end users, and you need to update the 'position' column of all
|
||||
affected rows when changes are made.
|
||||
Example: A ``slide`` table, where each row refers to zero or more entries
|
||||
in a related ``bullet`` table. The bullets within a slide are
|
||||
displayed in order based on the value of the ``position`` column in the
|
||||
``bullet`` table. As entries are reordered in memory, the value of the
|
||||
``position`` attribute should be updated to reflect the new sort order::
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
slides_table = Table('Slides', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('name', String))
|
||||
Base = declarative_base()
|
||||
|
||||
bullets_table = Table('Bullets', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('slide_id', Integer, ForeignKey('Slides.id')),
|
||||
Column('position', Integer),
|
||||
Column('text', String))
|
||||
class Slide(Base):
|
||||
__tablename__ = 'slide'
|
||||
|
||||
class Slide(object):
|
||||
pass
|
||||
class Bullet(object):
|
||||
pass
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String)
|
||||
|
||||
mapper(Slide, slides_table, properties={
|
||||
'bullets': relationship(Bullet, order_by=[bullets_table.c.position])
|
||||
})
|
||||
mapper(Bullet, bullets_table)
|
||||
bullets = relationship("Bullet", order_by="Bullet.position")
|
||||
|
||||
The standard relationship mapping will produce a list-like attribute on each Slide
|
||||
containing all related Bullets, but coping with changes in ordering is totally
|
||||
your responsibility. If you insert a Bullet into that list, there is no
|
||||
magic- it won't have a position attribute unless you assign it it one, and
|
||||
you'll need to manually renumber all the subsequent Bullets in the list to
|
||||
accommodate the insert.
|
||||
class Bullet(Base):
|
||||
__tablename__ = 'bullet'
|
||||
id = Column(Integer, primary_key=True)
|
||||
slide_id = Column(Integer, ForeignKey('slide.id'))
|
||||
position = Column(Integer)
|
||||
text = Column(String)
|
||||
|
||||
An ``orderinglist`` can automate this and manage the 'position' attribute on all
|
||||
related bullets for you.
|
||||
The standard relationship mapping will produce a list-like attribute on each
|
||||
``Slide`` containing all related ``Bullet`` objects,
|
||||
but coping with changes in ordering is not handled automatically.
|
||||
When appending a ``Bullet`` into ``Slide.bullets``, the ``Bullet.position``
|
||||
attribute will remain unset until manually assigned. When the ``Bullet``
|
||||
is inserted into the middle of the list, the following ``Bullet`` objects
|
||||
will also need to be renumbered.
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
The :class:`.OrderingList` object automates this task, managing the
|
||||
``position`` attribute on all ``Bullet`` objects in the collection. It is
|
||||
constructed using the :func:`.ordering_list` factory::
|
||||
|
||||
mapper(Slide, slides_table, properties={
|
||||
'bullets': relationship(Bullet,
|
||||
collection_class=ordering_list('position'),
|
||||
order_by=[bullets_table.c.position])
|
||||
})
|
||||
mapper(Bullet, bullets_table)
|
||||
from sqlalchemy.ext.orderinglist import ordering_list
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
class Slide(Base):
|
||||
__tablename__ = 'slide'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String)
|
||||
|
||||
bullets = relationship("Bullet", order_by="Bullet.position",
|
||||
collection_class=ordering_list('position'))
|
||||
|
||||
class Bullet(Base):
|
||||
__tablename__ = 'bullet'
|
||||
id = Column(Integer, primary_key=True)
|
||||
slide_id = Column(Integer, ForeignKey('slide.id'))
|
||||
position = Column(Integer)
|
||||
text = Column(String)
|
||||
|
||||
With the above mapping the ``Bullet.position`` attribute is managed::
|
||||
|
||||
s = Slide()
|
||||
s.bullets.append(Bullet())
|
||||
@@ -69,73 +82,87 @@ related bullets for you.
|
||||
s.bullets[2].position
|
||||
>>> 2
|
||||
|
||||
Use the ``ordering_list`` function to set up the ``collection_class`` on relationships
|
||||
(as in the mapper example above). This implementation depends on the list
|
||||
starting in the proper order, so be SURE to put an order_by on your relationship.
|
||||
The :class:`.OrderingList` construct only works with **changes** to a collection,
|
||||
and not the initial load from the database, and requires that the list be
|
||||
sorted when loaded. Therefore, be sure to
|
||||
specify ``order_by`` on the :func:`.relationship` against the target ordering
|
||||
attribute, so that the ordering is correct when first loaded.
|
||||
|
||||
.. warning::
|
||||
.. warning::
|
||||
|
||||
``ordering_list`` only provides limited functionality when a primary
|
||||
key column or unique column is the target of the sort. Since changing the order of
|
||||
entries often means that two rows must trade values, this is not possible when
|
||||
the value is constrained by a primary key or unique constraint, since one of the rows
|
||||
would temporarily have to point to a third available value so that the other row
|
||||
could take its old value. ``ordering_list`` doesn't do any of this for you,
|
||||
:class:`.OrderingList` only provides limited functionality when a primary
|
||||
key column or unique column is the target of the sort. Since changing the
|
||||
order of entries often means that two rows must trade values, this is not
|
||||
possible when the value is constrained by a primary key or unique
|
||||
constraint, since one of the rows would temporarily have to point to a
|
||||
third available value so that the other row could take its old
|
||||
value. :class:`.OrderingList` doesn't do any of this for you,
|
||||
nor does SQLAlchemy itself.
|
||||
|
||||
``ordering_list`` takes the name of the related object's ordering attribute as
|
||||
:func:`.ordering_list` takes the name of the related object's ordering attribute as
|
||||
an argument. By default, the zero-based integer index of the object's
|
||||
position in the ``ordering_list`` is synchronized with the ordering attribute:
|
||||
position in the :func:`.ordering_list` is synchronized with the ordering attribute:
|
||||
index 0 will get position 0, index 1 position 1, etc. To start numbering at 1
|
||||
or some other integer, provide ``count_from=1``.
|
||||
|
||||
Ordering values are not limited to incrementing integers. Almost any scheme
|
||||
can implemented by supplying a custom ``ordering_func`` that maps a Python list
|
||||
index to any value you require.
|
||||
|
||||
|
||||
|
||||
|
||||
"""
|
||||
from sqlalchemy.orm.collections import collection
|
||||
from sqlalchemy import util
|
||||
|
||||
__all__ = [ 'ordering_list' ]
|
||||
__all__ = ['ordering_list']
|
||||
|
||||
|
||||
def ordering_list(attr, count_from=None, **kw):
|
||||
"""Prepares an OrderingList factory for use in mapper definitions.
|
||||
"""Prepares an :class:`OrderingList` factory for use in mapper definitions.
|
||||
|
||||
Returns an object suitable for use as an argument to a Mapper relationship's
|
||||
``collection_class`` option. Arguments are:
|
||||
Returns an object suitable for use as an argument to a Mapper
|
||||
relationship's ``collection_class`` option. e.g.::
|
||||
|
||||
attr
|
||||
from sqlalchemy.ext.orderinglist import ordering_list
|
||||
|
||||
class Slide(Base):
|
||||
__tablename__ = 'slide'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String)
|
||||
|
||||
bullets = relationship("Bullet", order_by="Bullet.position",
|
||||
collection_class=ordering_list('position'))
|
||||
|
||||
:param attr:
|
||||
Name of the mapped attribute to use for storage and retrieval of
|
||||
ordering information
|
||||
|
||||
count_from (optional)
|
||||
:param count_from:
|
||||
Set up an integer-based ordering, starting at ``count_from``. For
|
||||
example, ``ordering_list('pos', count_from=1)`` would create a 1-based
|
||||
list in SQL, storing the value in the 'pos' column. Ignored if
|
||||
``ordering_func`` is supplied.
|
||||
|
||||
Passes along any keyword arguments to ``OrderingList`` constructor.
|
||||
Additional arguments are passed to the :class:`.OrderingList` constructor.
|
||||
|
||||
"""
|
||||
|
||||
kw = _unsugar_count_from(count_from=count_from, **kw)
|
||||
return lambda: OrderingList(attr, **kw)
|
||||
|
||||
|
||||
# Ordering utility functions
|
||||
|
||||
|
||||
def count_from_0(index, collection):
|
||||
"""Numbering function: consecutive integers starting at 0."""
|
||||
|
||||
return index
|
||||
|
||||
|
||||
def count_from_1(index, collection):
|
||||
"""Numbering function: consecutive integers starting at 1."""
|
||||
|
||||
return index + 1
|
||||
|
||||
|
||||
def count_from_n_factory(start):
|
||||
"""Numbering function: consecutive integers starting at arbitrary start."""
|
||||
|
||||
@@ -147,8 +174,9 @@ def count_from_n_factory(start):
|
||||
pass
|
||||
return f
|
||||
|
||||
|
||||
def _unsugar_count_from(**kw):
|
||||
"""Builds counting functions from keywrod arguments.
|
||||
"""Builds counting functions from keyword arguments.
|
||||
|
||||
Keyword argument filter, prepares a simple ``ordering_func`` from a
|
||||
``count_from`` argument, otherwise passes ``ordering_func`` on unchanged.
|
||||
@@ -164,12 +192,13 @@ def _unsugar_count_from(**kw):
|
||||
kw['ordering_func'] = count_from_n_factory(count_from)
|
||||
return kw
|
||||
|
||||
|
||||
class OrderingList(list):
|
||||
"""A custom list that manages position information for its children.
|
||||
|
||||
See the module and __init__ documentation for more details. The
|
||||
``ordering_list`` factory function is used to configure ``OrderingList``
|
||||
collections in ``mapper`` relationship definitions.
|
||||
The :class:`.OrderingList` object is normally set up using the
|
||||
:func:`.ordering_list` factory function, used in conjunction with
|
||||
the :func:`.relationship` function.
|
||||
|
||||
"""
|
||||
|
||||
@@ -184,13 +213,14 @@ class OrderingList(list):
|
||||
This implementation relies on the list starting in the proper order,
|
||||
so be **sure** to put an ``order_by`` on your relationship.
|
||||
|
||||
:param ordering_attr:
|
||||
:param ordering_attr:
|
||||
Name of the attribute that stores the object's order in the
|
||||
relationship.
|
||||
|
||||
:param ordering_func: Optional. A function that maps the position in the Python list to a
|
||||
value to store in the ``ordering_attr``. Values returned are
|
||||
usually (but need not be!) integers.
|
||||
:param ordering_func: Optional. A function that maps the position in
|
||||
the Python list to a value to store in the
|
||||
``ordering_attr``. Values returned are usually (but need not be!)
|
||||
integers.
|
||||
|
||||
An ``ordering_func`` is called with two positional parameters: the
|
||||
index of the element in the list, and the list itself.
|
||||
@@ -201,7 +231,7 @@ class OrderingList(list):
|
||||
like stepped numbering, alphabetical and Fibonacci numbering, see
|
||||
the unit tests.
|
||||
|
||||
:param reorder_on_append:
|
||||
:param reorder_on_append:
|
||||
Default False. When appending an object with an existing (non-None)
|
||||
ordering value, that value will be left untouched unless
|
||||
``reorder_on_append`` is true. This is an optimization to avoid a
|
||||
@@ -215,7 +245,7 @@ class OrderingList(list):
|
||||
making changes, any of whom happen to load this collection even in
|
||||
passing, all of the sessions would try to "clean up" the numbering
|
||||
in their commits, possibly causing all but one to fail with a
|
||||
concurrent modification error. Spooky action at a distance.
|
||||
concurrent modification error.
|
||||
|
||||
Recommend leaving this with the default of False, and just call
|
||||
``reorder()`` if you're doing ``append()`` operations with
|
||||
@@ -314,9 +344,24 @@ class OrderingList(list):
|
||||
self._reorder()
|
||||
# end Py2K
|
||||
|
||||
def __reduce__(self):
|
||||
return _reconstitute, (self.__class__, self.__dict__, list(self))
|
||||
|
||||
for func_name, func in locals().items():
|
||||
if (util.callable(func) and func.func_name == func_name and
|
||||
not func.__doc__ and hasattr(list, func_name)):
|
||||
func.__doc__ = getattr(list, func_name).__doc__
|
||||
del func_name, func
|
||||
|
||||
|
||||
def _reconstitute(cls, dict_, items):
|
||||
""" Reconstitute an :class:`.OrderingList`.
|
||||
|
||||
This is the adjoint to :meth:`.OrderingList.__reduce__`. It is used for
|
||||
unpickling :class:`.OrderingList` objects.
|
||||
|
||||
"""
|
||||
obj = cls.__new__(cls)
|
||||
obj.__dict__.update(dict_)
|
||||
list.extend(obj, items)
|
||||
return obj
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# ext/serializer.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Serializer/Deserializer objects for usage with SQLAlchemy query structures,
|
||||
"""Serializer/Deserializer objects for usage with SQLAlchemy query structures,
|
||||
allowing "contextual" deserialization.
|
||||
|
||||
Any SQLAlchemy query structure, either based on sqlalchemy.sql.*
|
||||
@@ -31,19 +31,19 @@ Usage is nearly the same as that of the standard Python pickle module::
|
||||
|
||||
print query2.all()
|
||||
|
||||
Similar restrictions as when using raw pickle apply; mapped classes must be
|
||||
Similar restrictions as when using raw pickle apply; mapped classes must be
|
||||
themselves be pickleable, meaning they are importable from a module-level
|
||||
namespace.
|
||||
|
||||
The serializer module is only appropriate for query structures. It is not
|
||||
needed for:
|
||||
|
||||
* instances of user-defined classes. These contain no references to engines,
|
||||
* instances of user-defined classes. These contain no references to engines,
|
||||
sessions or expression constructs in the typical case and can be serialized directly.
|
||||
|
||||
* Table metadata that is to be loaded entirely from the serialized structure (i.e. is
|
||||
not already declared in the application). Regular pickle.loads()/dumps() can
|
||||
be used to fully dump any ``MetaData`` object, typically one which was reflected
|
||||
not already declared in the application). Regular pickle.loads()/dumps() can
|
||||
be used to fully dump any ``MetaData`` object, typically one which was reflected
|
||||
from an existing database at some previous point in time. The serializer module
|
||||
is specifically for the opposite case, where the Table metadata is already present
|
||||
in memory.
|
||||
|
||||
@@ -1,22 +1,17 @@
|
||||
# ext/sqlsoup.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
|
||||
.. note::
|
||||
|
||||
SQLSoup is now its own project. Documentation
|
||||
and project status are available at:
|
||||
|
||||
http://pypi.python.org/pypi/sqlsoup
|
||||
|
||||
http://readthedocs.org/docs/sqlsoup
|
||||
|
||||
SQLSoup will no longer be included with SQLAlchemy as of
|
||||
version 0.8.
|
||||
.. versionchanged:: 0.8
|
||||
SQLSoup is now its own project. Documentation
|
||||
and project status are available at:
|
||||
http://pypi.python.org/pypi/sqlsoup and
|
||||
http://readthedocs.org/docs/sqlsoup\ .
|
||||
SQLSoup will no longer be included with SQLAlchemy.
|
||||
|
||||
|
||||
Introduction
|
||||
@@ -62,7 +57,7 @@ Loading objects is as easy as this::
|
||||
>>> users
|
||||
[
|
||||
MappedUsers(name=u'Joe Student',email=u'student@example.edu',
|
||||
password=u'student',classname=None,admin=0),
|
||||
password=u'student',classname=None,admin=0),
|
||||
MappedUsers(name=u'Bhargan Basepair',email=u'basepair@example.edu',
|
||||
password=u'basepair',classname=None,admin=1)
|
||||
]
|
||||
@@ -72,7 +67,7 @@ Of course, letting the database do the sort is better::
|
||||
>>> db.users.order_by(db.users.name).all()
|
||||
[
|
||||
MappedUsers(name=u'Bhargan Basepair',email=u'basepair@example.edu',
|
||||
password=u'basepair',classname=None,admin=1),
|
||||
password=u'basepair',classname=None,admin=1),
|
||||
MappedUsers(name=u'Joe Student',email=u'student@example.edu',
|
||||
password=u'student',classname=None,admin=0)
|
||||
]
|
||||
@@ -91,7 +86,7 @@ we're at it::
|
||||
>>> db.users.filter(where).order_by(desc(db.users.name)).all()
|
||||
[
|
||||
MappedUsers(name=u'Joe Student',email=u'student@example.edu',
|
||||
password=u'student',classname=None,admin=0),
|
||||
password=u'student',classname=None,admin=0),
|
||||
MappedUsers(name=u'Bhargan Basepair',email=u'basepair@example.edu',
|
||||
password=u'basepair',classname=None,admin=1)
|
||||
]
|
||||
@@ -217,15 +212,15 @@ with `with_labels`, to disambiguate columns with their table name
|
||||
(.c is short for .columns)::
|
||||
|
||||
>>> db.with_labels(join1).c.keys()
|
||||
[u'users_name', u'users_email', u'users_password',
|
||||
u'users_classname', u'users_admin', u'loans_book_id',
|
||||
[u'users_name', u'users_email', u'users_password',
|
||||
u'users_classname', u'users_admin', u'loans_book_id',
|
||||
u'loans_user_name', u'loans_loan_date']
|
||||
|
||||
You can also join directly to a labeled object::
|
||||
|
||||
>>> labeled_loans = db.with_labels(db.loans)
|
||||
>>> db.join(db.users, labeled_loans, isouter=True).c.keys()
|
||||
[u'name', u'email', u'password', u'classname',
|
||||
[u'name', u'email', u'password', u'classname',
|
||||
u'admin', u'loans_book_id', u'loans_user_name', u'loans_loan_date']
|
||||
|
||||
|
||||
@@ -256,8 +251,8 @@ accepts in normal mapper definition:
|
||||
Advanced Use
|
||||
============
|
||||
|
||||
Sessions, Transations and Application Integration
|
||||
-------------------------------------------------
|
||||
Sessions, Transactions and Application Integration
|
||||
---------------------------------------------------
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -472,8 +467,8 @@ def _class_for_table(session, engine, selectable, base_cls, mapper_kwargs):
|
||||
selectable = expression._clause_element_as_expr(selectable)
|
||||
mapname = 'Mapped' + _selectable_name(selectable)
|
||||
# Py2K
|
||||
if isinstance(mapname, unicode):
|
||||
engine_encoding = engine.dialect.encoding
|
||||
if isinstance(mapname, unicode):
|
||||
engine_encoding = engine.dialect.encoding
|
||||
mapname = mapname.encode(engine_encoding)
|
||||
# end Py2K
|
||||
|
||||
@@ -492,7 +487,7 @@ def _class_for_table(session, engine, selectable, base_cls, mapper_kwargs):
|
||||
raise TypeError('unable to compare with %s' % o.__class__)
|
||||
return t1, t2
|
||||
|
||||
# python2/python3 compatible system of
|
||||
# python2/python3 compatible system of
|
||||
# __cmp__ - __lt__ + __eq__
|
||||
|
||||
def __lt__(self, o):
|
||||
@@ -529,15 +524,15 @@ class SqlSoup(object):
|
||||
def __init__(self, engine_or_metadata, base=object, session=None):
|
||||
"""Initialize a new :class:`.SqlSoup`.
|
||||
|
||||
:param engine_or_metadata: a string database URL, :class:`.Engine`
|
||||
:param engine_or_metadata: a string database URL, :class:`.Engine`
|
||||
or :class:`.MetaData` object to associate with. If the
|
||||
argument is a :class:`.MetaData`, it should be *bound*
|
||||
to an :class:`.Engine`.
|
||||
:param base: a class which will serve as the default class for
|
||||
:param base: a class which will serve as the default class for
|
||||
returned mapped classes. Defaults to ``object``.
|
||||
:param session: a :class:`.ScopedSession` or :class:`.Session` with
|
||||
which to associate ORM operations for this :class:`.SqlSoup` instance.
|
||||
If ``None``, a :class:`.ScopedSession` that's local to this
|
||||
If ``None``, a :class:`.ScopedSession` that's local to this
|
||||
module is used.
|
||||
|
||||
"""
|
||||
@@ -550,7 +545,7 @@ class SqlSoup(object):
|
||||
elif isinstance(engine_or_metadata, (basestring, Engine)):
|
||||
self._metadata = MetaData(engine_or_metadata)
|
||||
else:
|
||||
raise ArgumentError("invalid engine or metadata argument %r" %
|
||||
raise ArgumentError("invalid engine or metadata argument %r" %
|
||||
engine_or_metadata)
|
||||
|
||||
self._cache = {}
|
||||
@@ -572,7 +567,7 @@ class SqlSoup(object):
|
||||
"""Execute a SQL statement.
|
||||
|
||||
The statement may be a string SQL string,
|
||||
an :func:`.expression.select` construct, or an :func:`.expression.text`
|
||||
an :func:`.expression.select` construct, or an :func:`.expression.text`
|
||||
construct.
|
||||
|
||||
"""
|
||||
@@ -599,7 +594,7 @@ class SqlSoup(object):
|
||||
self.session.flush()
|
||||
|
||||
def rollback(self):
|
||||
"""Rollback the current transction.
|
||||
"""Rollback the current transaction.
|
||||
|
||||
See :meth:`.Session.rollback`.
|
||||
|
||||
@@ -635,14 +630,14 @@ class SqlSoup(object):
|
||||
"""
|
||||
self.session.expunge_all()
|
||||
|
||||
def map_to(self, attrname, tablename=None, selectable=None,
|
||||
def map_to(self, attrname, tablename=None, selectable=None,
|
||||
schema=None, base=None, mapper_args=util.immutabledict()):
|
||||
"""Configure a mapping to the given attrname.
|
||||
|
||||
This is the "master" method that can be used to create any
|
||||
This is the "master" method that can be used to create any
|
||||
configuration.
|
||||
|
||||
(new in 0.6.6)
|
||||
.. versionadded:: 0.6.6
|
||||
|
||||
:param attrname: String attribute name which will be
|
||||
established as an attribute on this :class:.`.SqlSoup`
|
||||
@@ -682,10 +677,10 @@ class SqlSoup(object):
|
||||
raise ArgumentError("'tablename' and 'selectable' "
|
||||
"arguments are mutually exclusive")
|
||||
|
||||
selectable = Table(tablename,
|
||||
self._metadata,
|
||||
autoload=True,
|
||||
autoload_with=self.bind,
|
||||
selectable = Table(tablename,
|
||||
self._metadata,
|
||||
autoload=True,
|
||||
autoload_with=self.bind,
|
||||
schema=schema or self.schema)
|
||||
elif schema:
|
||||
raise ArgumentError("'tablename' argument is required when "
|
||||
@@ -723,8 +718,9 @@ class SqlSoup(object):
|
||||
def map(self, selectable, base=None, **mapper_args):
|
||||
"""Map a selectable directly.
|
||||
|
||||
The class and its mapping are not cached and will
|
||||
be discarded once dereferenced (as of 0.6.6).
|
||||
.. versionchanged:: 0.6.6
|
||||
The class and its mapping are not cached and will
|
||||
be discarded once dereferenced.
|
||||
|
||||
:param selectable: an :func:`.expression.select` construct.
|
||||
:param base: a Python class which will be used as the
|
||||
@@ -746,11 +742,12 @@ class SqlSoup(object):
|
||||
)
|
||||
|
||||
def with_labels(self, selectable, base=None, **mapper_args):
|
||||
"""Map a selectable directly, wrapping the
|
||||
"""Map a selectable directly, wrapping the
|
||||
selectable in a subquery with labels.
|
||||
|
||||
The class and its mapping are not cached and will
|
||||
be discarded once dereferenced (as of 0.6.6).
|
||||
.. versionchanged:: 0.6.6
|
||||
The class and its mapping are not cached and will
|
||||
be discarded once dereferenced.
|
||||
|
||||
:param selectable: an :func:`.expression.select` construct.
|
||||
:param base: a Python class which will be used as the
|
||||
@@ -769,12 +766,13 @@ class SqlSoup(object):
|
||||
select(use_labels=True).
|
||||
alias('foo'), base=base, **mapper_args)
|
||||
|
||||
def join(self, left, right, onclause=None, isouter=False,
|
||||
def join(self, left, right, onclause=None, isouter=False,
|
||||
base=None, **mapper_args):
|
||||
"""Create an :func:`.expression.join` and map to it.
|
||||
|
||||
The class and its mapping are not cached and will
|
||||
be discarded once dereferenced (as of 0.6.6).
|
||||
.. versionchanged:: 0.6.6
|
||||
The class and its mapping are not cached and will
|
||||
be discarded once dereferenced.
|
||||
|
||||
:param left: a mapped class or table object.
|
||||
:param right: a mapped class or table object.
|
||||
@@ -794,7 +792,7 @@ class SqlSoup(object):
|
||||
return self.map(j, base=base, **mapper_args)
|
||||
|
||||
def entity(self, attr, schema=None):
|
||||
"""Return the named entity from this :class:`.SqlSoup`, or
|
||||
"""Return the named entity from this :class:`.SqlSoup`, or
|
||||
create if not present.
|
||||
|
||||
For more generalized mapping, see :meth:`.map_to`.
|
||||
|
||||
Reference in New Issue
Block a user