Update SQLAlchemy

This commit is contained in:
Ruud
2013-06-14 11:00:06 +02:00
parent 267ecfacab
commit 4aa6700ceb
124 changed files with 6500 additions and 5207 deletions
+1 -1
View File
@@ -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
+31 -31
View File
@@ -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`,
+25 -25
View File
@@ -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
View File
@@ -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}
"""
+9 -9
View File
@@ -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
View File
@@ -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.\
+98 -65
View File
@@ -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()
+123 -78
View File
@@ -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
+6 -6
View File
@@ -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.
+43 -45
View File
@@ -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`.