summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2010-08-08 17:50:44 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2010-08-08 17:50:44 -0400
commitbb5a85feca1a6ed1aa7d18a04c0dfff3afa306c1 (patch)
tree12d6f26d360310bfa8b718e0876eff8eedd42c11 /lib
parent0b9afe1412898ba31282e75d90ca4d728613ca2b (diff)
parentbb3be98d3bee4b2bcef791be022ddb2510b9cf9c (diff)
downloadsqlalchemy-bb5a85feca1a6ed1aa7d18a04c0dfff3afa306c1.tar.gz
merge tip
Diffstat (limited to 'lib')
-rw-r--r--lib/sqlalchemy/dialects/sqlite/base.py2
-rw-r--r--lib/sqlalchemy/engine/base.py38
-rw-r--r--lib/sqlalchemy/exc.py4
-rwxr-xr-xlib/sqlalchemy/ext/declarative.py272
-rw-r--r--lib/sqlalchemy/orm/__init__.py390
-rw-r--r--lib/sqlalchemy/orm/attributes.py4
-rw-r--r--lib/sqlalchemy/orm/collections.py2
-rw-r--r--lib/sqlalchemy/orm/dynamic.py39
-rw-r--r--lib/sqlalchemy/orm/interfaces.py2
-rw-r--r--lib/sqlalchemy/orm/mapper.py86
-rw-r--r--lib/sqlalchemy/orm/properties.py112
-rw-r--r--lib/sqlalchemy/orm/query.py6
-rw-r--r--lib/sqlalchemy/orm/session.py5
-rw-r--r--lib/sqlalchemy/orm/strategies.py14
-rw-r--r--lib/sqlalchemy/orm/util.py55
-rw-r--r--lib/sqlalchemy/schema.py53
-rw-r--r--lib/sqlalchemy/sql/expression.py36
17 files changed, 659 insertions, 461 deletions
diff --git a/lib/sqlalchemy/dialects/sqlite/base.py b/lib/sqlalchemy/dialects/sqlite/base.py
index 2d9e56baf..b84b18e68 100644
--- a/lib/sqlalchemy/dialects/sqlite/base.py
+++ b/lib/sqlalchemy/dialects/sqlite/base.py
@@ -600,5 +600,5 @@ def _pragma_cursor(cursor):
"""work around SQLite issue whereby cursor.description is blank when PRAGMA returns no rows."""
if cursor.closed:
- cursor._fetchone_impl = lambda: None
+ cursor.fetchone = lambda: None
return cursor
diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py
index 8533e19fc..4260e923a 100644
--- a/lib/sqlalchemy/engine/base.py
+++ b/lib/sqlalchemy/engine/base.py
@@ -908,7 +908,7 @@ class Connection(Connectable):
self.__connection = self.engine.raw_connection()
self.__invalid = False
return self.__connection
- raise exc.InvalidRequestError("This Connection is closed")
+ raise exc.ResourceClosedError("This Connection is closed")
@property
def info(self):
@@ -956,7 +956,7 @@ class Connection(Connectable):
"""
if self.closed:
- raise exc.InvalidRequestError("This Connection is closed")
+ raise exc.ResourceClosedError("This Connection is closed")
if self.__connection.is_valid:
self.__connection.invalidate(exception)
@@ -2359,7 +2359,9 @@ class ResultProxy(object):
if _autoclose_connection and \
self.connection.should_close_with_result:
self.connection.close()
-
+ # allow consistent errors
+ self.cursor = None
+
def __iter__(self):
while True:
row = self.fetchone()
@@ -2441,14 +2443,32 @@ class ResultProxy(object):
return self.dialect.supports_sane_multi_rowcount
def _fetchone_impl(self):
- return self.cursor.fetchone()
+ try:
+ return self.cursor.fetchone()
+ except AttributeError:
+ self._non_result()
def _fetchmany_impl(self, size=None):
- return self.cursor.fetchmany(size)
+ try:
+ return self.cursor.fetchmany(size)
+ except AttributeError:
+ self._non_result()
def _fetchall_impl(self):
- return self.cursor.fetchall()
-
+ try:
+ return self.cursor.fetchall()
+ except AttributeError:
+ self._non_result()
+
+ def _non_result(self):
+ if self._metadata is None:
+ raise exc.ResourceClosedError(
+ "This result object does not return rows. "
+ "It has been closed automatically.",
+ )
+ else:
+ raise exc.ResourceClosedError("This result object is closed.")
+
def process_rows(self, rows):
process_row = self._process_row
metadata = self._metadata
@@ -2505,7 +2525,6 @@ class ResultProxy(object):
Else the cursor is automatically closed and None is returned.
"""
-
try:
row = self._fetchone_impl()
if row is not None:
@@ -2525,6 +2544,9 @@ class ResultProxy(object):
Returns None if no row is present.
"""
+ if self._metadata is None:
+ self._non_result()
+
try:
row = self._fetchone_impl()
except Exception, e:
diff --git a/lib/sqlalchemy/exc.py b/lib/sqlalchemy/exc.py
index 31826f44f..1c412824c 100644
--- a/lib/sqlalchemy/exc.py
+++ b/lib/sqlalchemy/exc.py
@@ -60,6 +60,10 @@ class InvalidRequestError(SQLAlchemyError):
"""
+class ResourceClosedError(InvalidRequestError):
+ """An operation was requested from a connection, cursor, or other
+ object that's in a closed state."""
+
class NoSuchColumnError(KeyError, InvalidRequestError):
"""A nonexistent column is requested from a ``RowProxy``."""
diff --git a/lib/sqlalchemy/ext/declarative.py b/lib/sqlalchemy/ext/declarative.py
index 3370a764c..2310f01ce 100755
--- a/lib/sqlalchemy/ext/declarative.py
+++ b/lib/sqlalchemy/ext/declarative.py
@@ -2,13 +2,14 @@
Synopsis
========
-SQLAlchemy object-relational configuration involves the use of
-:class:`~sqlalchemy.schema.Table`, :func:`~sqlalchemy.orm.mapper`, and
-class objects to define the three areas of configuration.
-:mod:`~sqlalchemy.ext.declarative` allows all three types of
-configuration to be expressed declaratively on an individual
-mapped class. Regular SQLAlchemy schema elements and ORM constructs
-are used in most cases.
+SQLAlchemy object-relational configuration involves the
+combination of :class:`.Table`, :func:`.mapper`, and class
+objects to define a mapped class.
+:mod:`~sqlalchemy.ext.declarative` allows all three to be
+expressed at once within the class declaration. As much as
+possible, regular SQLAlchemy schema and ORM constructs are
+used directly, so that configuration between "classical" ORM
+usage and declarative remain highly similar.
As a simple example::
@@ -23,72 +24,74 @@ As a simple example::
Above, the :func:`declarative_base` callable returns a new base class from
which all mapped classes should inherit. When the class definition is
-completed, a new :class:`~sqlalchemy.schema.Table` and
-:class:`~sqlalchemy.orm.mapper` will have been generated, accessible via the
-``__table__`` and ``__mapper__`` attributes on the ``SomeClass`` class.
+completed, a new :class:`.Table` and
+:func:`.mapper` will have been generated.
+
+The resulting table and mapper are accessible via
+``__table__`` and ``__mapper__`` attributes on the
+``SomeClass`` class::
+
+ # access the mapped Table
+ SomeClass.__table__
+
+ # access the Mapper
+ SomeClass.__mapper__
Defining Attributes
===================
-In the above example, the :class:`~sqlalchemy.schema.Column` objects are
+In the previous example, the :class:`.Column` objects are
automatically named with the name of the attribute to which they are
assigned.
-They can also be explicitly named, and that name does not have to be
-the same as name assigned on the class.
-The column will be assigned to the :class:`~sqlalchemy.schema.Table` using the
-given name, and mapped to the class using the attribute name::
+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
+"id" attribute of `SomeClass`, but in SQL will be represented as "some_table_id"::
class SomeClass(Base):
__tablename__ = 'some_table'
id = Column("some_table_id", Integer, primary_key=True)
- name = Column("name", String(50))
Attributes may be added to the class after its construction, and they will be
-added to the underlying :class:`~sqlalchemy.schema.Table` and
-:func:`~sqlalchemy.orm.mapper()` definitions as appropriate::
+added to the underlying :class:`.Table` and
+:func:`.mapper()` definitions as appropriate::
SomeClass.data = Column('data', Unicode)
SomeClass.related = relationship(RelatedInfo)
-Classes which are mapped explicitly using
-:func:`~sqlalchemy.orm.mapper()` can interact freely with declarative
-classes.
+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
share the same underlying :class:`~sqlalchemy.schema.MetaData` object,
so that string-configured :class:`~sqlalchemy.schema.ForeignKey`
references can be resolved without issue.
-Association of Metadata and Engine
-==================================
+Accessing the MetaData
+=======================
The :func:`declarative_base` base class contains a
-:class:`~sqlalchemy.schema.MetaData` object where newly
-defined :class:`~sqlalchemy.schema.Table` objects are collected. This
-is accessed via the :class:`~sqlalchemy.schema.MetaData` class level
-accessor, so to create tables we can say::
+:class:`.MetaData` object where newly defined
+:class:`.Table` objects are collected. This object is
+intended to be accessed directly for
+:class:`.MetaData`-specific operations. Such as, to issue
+CREATE statements for all tables::
engine = create_engine('sqlite://')
Base.metadata.create_all(engine)
-The :class:`~sqlalchemy.engine.base.Engine` created above may also be
-directly associated with the declarative base class using the ``bind``
-keyword argument, where it will be associated with the underlying
-:class:`~sqlalchemy.schema.MetaData` object and allow SQL operations
-involving that metadata and its tables to make use of that engine
-automatically::
+The usual techniques of associating :class:`.MetaData:` with :class:`.Engine`
+apply, such as assigning to the ``bind`` attribute::
- Base = declarative_base(bind=create_engine('sqlite://'))
+ Base.metadata.bind = create_engine('sqlite://')
-Alternatively, by way of the normal
-:class:`~sqlalchemy.schema.MetaData` behavior, the ``bind`` attribute
-of the class level accessor can be assigned at any time as follows::
+To associate the engine with the :func:`declarative_base` at time
+of construction, the ``bind`` argument is accepted::
- Base.metadata.bind = create_engine('sqlite://')
+ Base = declarative_base(bind=create_engine('sqlite://'))
-The :func:`declarative_base` can also receive a pre-created
-:class:`~sqlalchemy.schema.MetaData` object, which allows a
+:func:`declarative_base` can also receive a pre-existing
+:class:`.MetaData` object, which allows a
declarative setup to be associated with an already
existing traditional collection of :class:`~sqlalchemy.schema.Table`
objects::
@@ -157,12 +160,13 @@ class after the fact::
Configuring Many-to-Many Relationships
======================================
-There's nothing special about many-to-many with declarative. The
-``secondary`` argument to :func:`~sqlalchemy.orm.relationship` still
-requires a :class:`~sqlalchemy.schema.Table` object, not a declarative
-class. The :class:`~sqlalchemy.schema.Table` should share the same
-:class:`~sqlalchemy.schema.MetaData` object used by the declarative
-base::
+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
+traditional way. The :class:`.Table` usually shares
+the :class:`.MetaData` object used by the declarative base::
keywords = Table(
'keywords', Base.metadata,
@@ -175,10 +179,11 @@ base::
id = Column(Integer, primary_key=True)
keywords = relationship("Keyword", secondary=keywords)
-You should generally **not** map a class and also specify its table in
-a many-to-many relationship, since the ORM may issue duplicate INSERT and
-DELETE statements.
-
+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
+DELETE statements against the underlying table.
.. _declarative_synonyms:
@@ -187,18 +192,25 @@ Defining Synonyms
Synonyms are introduced in :ref:`synonyms`. To define a getter/setter
which proxies to an underlying attribute, use
-:func:`~sqlalchemy.orm.synonym` with the ``descriptor`` argument::
+:func:`~.synonym` with the ``descriptor`` argument. Here we present
+using Python 2.6 style properties::
class MyClass(Base):
__tablename__ = 'sometable'
+ id = Column(Integer, primary_key=True)
+
_attr = Column('attr', String)
- def _get_attr(self):
- return self._some_attr
- def _set_attr(self, attr):
- self._some_attr = attr
- attr = synonym('_attr', descriptor=property(_get_attr, _set_attr))
+ @property
+ def attr(self):
+ return self._attr
+
+ @attr.setter
+ def attr(self, attr):
+ self._attr = attr
+
+ attr = synonym('_attr', descriptor=attr)
The above synonym is then usable as an instance attribute as well as a
class-level expression construct::
@@ -212,16 +224,17 @@ conjunction with ``@property``::
class MyClass(Base):
__tablename__ = 'sometable'
-
+
+ id = Column(Integer, primary_key=True)
_attr = Column('attr', String)
@synonym_for('_attr')
@property
def attr(self):
- return self._some_attr
+ return self._attr
Similarly, :func:`comparable_using` is a front end for the
-:func:`~sqlalchemy.orm.comparable_property` ORM function::
+:func:`~.comparable_property` ORM function::
class MyClass(Base):
__tablename__ = 'sometable'
@@ -233,6 +246,61 @@ Similarly, :func:`comparable_using` is a front end for the
def uc_name(self):
return self.name.upper()
+.. _declarative_sql_expressions:
+
+Defining SQL Expressions
+========================
+
+The usage of :func:`.column_property` with Declarative is
+pretty much the same as that described in
+:ref:`mapper_sql_expressions`. Local columns within the same
+class declaration can be referenced directly::
+
+ class User(Base):
+ __tablename__ = 'user'
+ id = Column(Integer, primary_key=True)
+ firstname = Column(String)
+ lastname = Column(String)
+ fullname = column_property(
+ firstname + " " + lastname
+ )
+
+Correlated subqueries reference the :class:`Column` objects they
+need either from the local class definition or from remote
+classes::
+
+ from sqlalchemy.sql import func
+
+ class Address(Base):
+ __tablename__ = 'address'
+
+ id = Column('id', Integer, primary_key=True)
+ user_id = Column(Integer, ForeignKey('user.id'))
+
+ class User(Base):
+ __tablename__ = 'user'
+
+ id = Column(Integer, primary_key=True)
+ name = Column(String)
+
+ address_count = column_property(
+ select([func.count(Address.id)]).\\
+ where(Address.user_id==id)
+ )
+
+In the case that the ``address_count`` attribute above doesn't have access to
+``Address`` when ``User`` is defined, the ``address_count`` attribute should
+be added to ``User`` when both ``User`` and ``Address`` are available (i.e.
+there is no string based "late compilation" feature like there is with
+:func:`.relationship` at this time). Note we reference the ``id`` column
+attribute of ``User`` with its class when we are no longer in the declaration
+of the ``User`` class::
+
+ User.address_count = column_property(
+ select([func.count(Address.id)]).\\
+ where(Address.user_id==User.id)
+ )
+
Table Configuration
===================
@@ -263,6 +331,9 @@ arguments to be specified as well (usually constraints)::
Note that the keyword parameters dictionary is required in the tuple
form even if empty.
+Using a Hybrid Approach with __table__
+=======================================
+
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
@@ -275,23 +346,48 @@ to a table::
Column('name', String(50))
)
+``__table__`` provides a more focused point of control for establishing
+table metadata, while still getting most of the benefits of using declarative.
+An application that uses reflection might want to load table metadata elsewhere
+and simply pass it to declarative classes::
+
+ from sqlalchemy.ext.declarative import declarative_base
+
+ Base = declarative_base()
+ Base.metadata.reflect(some_engine)
+
+ class User(Base):
+ __table__ = metadata['user']
+
+ class Address(Base):
+ __table__ = metadata['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.
+
Mapper Configuration
====================
-Configuration of mappers is done with the
-:func:`~sqlalchemy.orm.mapper` function and all the possible mapper
-configuration parameters can be found in the documentation for that
-function.
-
-:func:`~sqlalchemy.orm.mapper` is still used by declaratively mapped
-classes and keyword parameters to the function can be passed by
-placing them in the ``__mapper_args__`` class variable::
+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
+class declaration::
+ from datetime import datetime
+
class Widget(Base):
__tablename__ = 'widgets'
+
id = Column(Integer, primary_key=True)
+ timestamp = Column(DateTime, nullable=False)
- __mapper_args__ = {'extension': MyWidgetExtension()}
+ __mapper_args__ = {
+ 'version_id_col': timestamp,
+ 'version_id_generator': lambda v:datetime.now()
+ }
Inheritance Configuration
=========================
@@ -531,12 +627,12 @@ Mixing in Relationships
~~~~~~~~~~~~~~~~~~~~~~~
Relationships created by :func:`~sqlalchemy.orm.relationship` are provided
-exclusively using the :func:`~sqlalchemy.util.classproperty` 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 reference a common
-target class via many-to-one::
+with declarative mixin classes exclusively using the
+:func:`~sqlalchemy.util.classproperty` 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
+reference a common target class via many-to-one::
class RefTargetMixin(object):
@classproperty
@@ -586,9 +682,9 @@ 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 have the
-:func:`~sqlalchemy.util.classproperty` requirement so that no reliance on
-copying is needed::
+etc. ultimately involve references to columns, and therefore, when
+used with declarative mixins, have the :func:`~sqlalchemy.util.classproperty`
+requirement so that no reliance on copying is needed::
class SomethingMixin(object):
@@ -607,7 +703,8 @@ Controlling table inheritance with mixins
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``__tablename__`` attribute in conjunction with the hierarchy of
-the classes involved controls what type of table inheritance, if any,
+classes involved in a declarative mixin scenario controls what type of
+table inheritance, if any,
is configured by the declarative extension.
If the ``__tablename__`` is computed by a mixin, you may need to
@@ -700,12 +797,13 @@ classes::
Combining Table/Mapper Arguments from Multiple Mixins
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-In the case of ``__table_args__`` or ``__mapper_args__``, you may want
-to combine some parameters from several mixins with those you wish to
-define on the class iteself. The
-:func:`~sqlalchemy.util.classproperty` decorator can be used here
-to create user-defined collation routines that pull from multiple
-collections::
+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:`~sqlalchemy.util.classproperty` decorator can be used
+here to create user-defined collation routines that pull
+from multiple collections::
from sqlalchemy.util import classproperty
@@ -866,8 +964,12 @@ def _as_declarative(cls, classname, dict_):
cls._decl_class_registry[classname] = cls
our_stuff = util.OrderedDict()
+
for k in dict_:
value = dict_[k]
+ if isinstance(value, util.classproperty):
+ value = getattr(cls, k)
+
if (isinstance(value, tuple) and len(value) == 1 and
isinstance(value[0], (Column, MapperProperty))):
util.warn("Ignoring declarative-like tuple value of attribute "
@@ -933,7 +1035,7 @@ def _as_declarative(cls, classname, dict_):
if not table.c.contains_column(c):
raise exceptions.ArgumentError(
"Can't add additional column %r when "
- "specifying __table__" % key
+ "specifying __table__" % c.key
)
if 'inherits' not in mapper_args:
diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py
index 77281ee56..2004dccd1 100644
--- a/lib/sqlalchemy/orm/__init__.py
+++ b/lib/sqlalchemy/orm/__init__.py
@@ -1,5 +1,6 @@
# sqlalchemy/orm/__init__.py
-# Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Michael Bayer mike_mp@zzzcomputing.com
+# Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Michael Bayer
+# mike_mp@zzzcomputing.com
#
# This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -51,11 +52,11 @@ from sqlalchemy.orm import strategies
from sqlalchemy.orm.query import AliasOption, Query
from sqlalchemy.sql import util as sql_util
from sqlalchemy.orm.session import Session as _Session
-from sqlalchemy.orm.session import object_session, sessionmaker, make_transient
+from sqlalchemy.orm.session import object_session, sessionmaker, \
+ make_transient
from sqlalchemy.orm.scoping import ScopedSession
from sqlalchemy import util as sa_util
-
__all__ = (
'EXT_CONTINUE',
'EXT_STOP',
@@ -174,10 +175,11 @@ def relationship(argument, secondary=None, **kwargs):
"""Provide a relationship of a primary Mapper to a secondary Mapper.
.. note:: This function is known as :func:`relation` in all versions
- of SQLAlchemy prior to version 0.6beta2, including the 0.5 and 0.4 series.
- :func:`~sqlalchemy.orm.relationship()` is only available starting with
- SQLAlchemy 0.6beta2. The :func:`relation` name will remain available for
- the foreseeable future in order to enable cross-compatibility.
+ of SQLAlchemy prior to version 0.6beta2, including the 0.5 and 0.4
+ series. :func:`~sqlalchemy.orm.relationship()` is only available
+ starting with SQLAlchemy 0.6beta2. The :func:`relation` name will
+ remain available for the foreseeable future in order to enable
+ cross-compatibility.
This corresponds to a parent-child or associative table relationship. The
constructed class is an instance of :class:`RelationshipProperty`.
@@ -223,7 +225,7 @@ def relationship(argument, secondary=None, **kwargs):
Available cascades are:
- * ``save-update`` - cascade the :meth:`~sqlalchemy.orm.session.Session.add`
+ * ``save-update`` - cascade the :meth:`.Session.add`
operation. This cascade applies both to future and
past calls to :meth:`~sqlalchemy.orm.session.Session.add`,
meaning new items added to a collection or scalar relationship
@@ -234,10 +236,10 @@ def relationship(argument, secondary=None, **kwargs):
* ``merge`` - cascade the :meth:`~sqlalchemy.orm.session.Session.merge`
operation
- * ``expunge`` - cascade the :meth:`~sqlalchemy.orm.session.Session.expunge`
+ * ``expunge`` - cascade the :meth:`.Session.expunge`
operation
- * ``delete`` - cascade the :meth:`~sqlalchemy.orm.session.Session.delete`
+ * ``delete`` - cascade the :meth:`.Session.delete`
operation
* ``delete-orphan`` - if an item of the child's type with no
@@ -245,7 +247,7 @@ def relationship(argument, secondary=None, **kwargs):
option prevents a pending item of the child's class from being
persisted without a parent present.
- * ``refresh-expire`` - cascade the :meth:`~sqlalchemy.orm.session.Session.expire`
+ * ``refresh-expire`` - cascade the :meth:`.Session.expire`
and :meth:`~sqlalchemy.orm.session.Session.refresh` operations
* ``all`` - shorthand for "save-update,merge, refresh-expire,
@@ -501,7 +503,8 @@ def dynamic_loader(argument, secondary=None, primaryjoin=None,
return RelationshipProperty(
argument, secondary=secondary, primaryjoin=primaryjoin,
- secondaryjoin=secondaryjoin, foreign_keys=foreign_keys, backref=backref,
+ secondaryjoin=secondaryjoin, foreign_keys=foreign_keys,
+ backref=backref,
post_update=post_update, cascade=cascade, remote_side=remote_side,
enable_typechecks=enable_typechecks, passive_deletes=passive_deletes,
order_by=order_by, comparator_factory=comparator_factory,doc=doc,
@@ -522,9 +525,9 @@ def column_property(*args, **kwargs):
:param \*cols:
list of Column objects to be mapped.
- :param comparator_factory:
- a class which extends :class:`sqlalchemy.orm.properties.ColumnProperty.Comparator`
- which provides custom SQL clause generation for comparison operations.
+ :param comparator_factory: a class which extends
+ :class:`.ColumnProperty.Comparator` which provides custom SQL clause
+ generation for comparison operations.
:param group:
a group name for this property when marked as deferred.
@@ -571,9 +574,9 @@ def composite(class_, *cols, **kwargs):
load immediately, and is instead loaded when the attribute is first
accessed on an instance. See also :func:`~sqlalchemy.orm.deferred`.
- :param comparator_factory:
- a class which extends :class:`sqlalchemy.orm.properties.CompositeProperty.Comparator`
- which provides custom SQL clause generation for comparison operations.
+ :param comparator_factory: a class which extends
+ :class:`.CompositeProperty.Comparator` which provides custom SQL clause
+ generation for comparison operations.
:param doc:
optional string that will be applied as the doc on the
@@ -616,140 +619,150 @@ def mapper(class_, local_table=None, *args, **params):
:param class\_: The class to be mapped.
- :param local_table: The table to which the class is mapped, or None if this mapper
- inherits from another mapper using concrete table inheritance.
-
- :param always_refresh: If True, all query operations for this mapped class will overwrite all
- data within object instances that already exist within the session,
- erasing any in-memory changes with whatever information was loaded
- from the database. Usage of this flag is highly discouraged; as an
- alternative, see the method `populate_existing()` on
- :class:`~sqlalchemy.orm.query.Query`.
-
- :param allow_null_pks: This flag is deprecated - this is stated as allow_partial_pks
- which defaults to True.
-
- :param allow_partial_pks: Defaults to True. Indicates that a composite primary key with
- some NULL values should be considered as possibly existing
- within the database. This affects whether a mapper will assign
- an incoming row to an existing identity, as well as if
- session.merge() will check the database first for a particular
- primary key value. A "partial primary key" can occur if one
- has mapped to an OUTER JOIN, for example.
-
- :param batch: Indicates that save operations of multiple entities can be batched
- together for efficiency. setting to False indicates that an instance
- will be fully saved before saving the next instance, which includes
- inserting/updating all table rows corresponding to the entity as well
- as calling all :class:`MapperExtension` methods corresponding to the save
- operation.
-
- :param column_prefix: A string which will be prepended to the `key` name of all Columns when
- creating column-based properties from the given Table. Does not
- affect explicitly specified column-based properties
-
- :param concrete: If True, indicates this mapper should use concrete table inheritance
- with its parent mapper.
-
- :param exclude_properties: A list of properties not to map. Columns present in the mapped table
- and present in this list will not be automatically converted into
- properties. Note that neither this option nor include_properties will
- allow an end-run around Python inheritance. If mapped class ``B``
- inherits from mapped class ``A``, no combination of includes or
- excludes will allow ``B`` to have fewer properties than its
- superclass, ``A``.
-
-
- :param extension: A :class:`~sqlalchemy.orm.interfaces.MapperExtension` instance or list of
- :class:`~sqlalchemy.orm.interfaces.MapperExtension` instances which will be applied to all
- operations by this :class:`~sqlalchemy.orm.mapper.Mapper`.
-
- :param include_properties: An inclusive list of properties to map. Columns present in the mapped
- table but not present in this list will not be automatically converted
- into properties.
+ :param local_table: The table to which the class is mapped, or None if
+ this mapper inherits from another mapper using concrete table
+ inheritance.
+
+ :param always_refresh: If True, all query operations for this mapped
+ class will overwrite all data within object instances that already
+ exist within the session, erasing any in-memory changes with
+ whatever information was loaded from the database. Usage of this
+ flag is highly discouraged; as an alternative, see the method
+ `populate_existing()` on :class:`~sqlalchemy.orm.query.Query`.
+
+ :param allow_null_pks: This flag is deprecated - this is stated as
+ allow_partial_pks which defaults to True.
+
+ :param allow_partial_pks: Defaults to True. Indicates that a
+ composite primary key with some NULL values should be considered as
+ possibly existing within the database. This affects whether a
+ mapper will assign an incoming row to an existing identity, as well
+ as if session.merge() will check the database first for a
+ particular primary key value. A "partial primary key" can occur if
+ one has mapped to an OUTER JOIN, for example.
+
+ :param batch: Indicates that save operations of multiple entities
+ can be batched together for efficiency. setting to False indicates
+ that an instance will be fully saved before saving the next
+ instance, which includes inserting/updating all table rows
+ corresponding to the entity as well as calling all
+ :class:`MapperExtension` methods corresponding to the save
+ operation.
+
+ :param column_prefix: A string which will be prepended to the `key`
+ name of all Columns when creating column-based properties from the
+ given Table. Does not affect explicitly specified column-based
+ properties
+
+ :param concrete: If True, indicates this mapper should use concrete
+ table inheritance with its parent mapper.
+
+ :param exclude_properties: A list of properties not to map. Columns
+ present in the mapped table and present in this list will not be
+ automatically converted into properties. Note that neither this
+ option nor include_properties will allow an end-run around Python
+ inheritance. If mapped class ``B`` inherits from mapped class
+ ``A``, no combination of includes or excludes will allow ``B`` to
+ have fewer properties than its superclass, ``A``.
+
+ :param extension: A :class:`.MapperExtension` instance or
+ list of :class:`~sqlalchemy.orm.interfaces.MapperExtension`
+ instances which will be applied to all operations by this
+ :class:`~sqlalchemy.orm.mapper.Mapper`.
+
+ :param include_properties: An inclusive list of properties to map.
+ Columns present in the mapped table but not present in this list
+ will not be automatically converted into properties.
:param inherits: Another :class:`~sqlalchemy.orm.Mapper` for which
this :class:`~sqlalchemy.orm.Mapper` will have an inheritance
relationship with.
-
- :param inherit_condition: For joined table inheritance, a SQL expression (constructed
- :class:`~sqlalchemy.expression.sql.ClauseElement`) which will define how the two tables are joined;
- defaults to a natural join between the two tables.
-
- :param inherit_foreign_keys: When inherit_condition is used and the condition contains no
- ForeignKey columns, specify the "foreign" columns of the join
- condition in this list. else leave as None.
-
- :param non_primary: Construct a :class:`Mapper` that will define only the selection of
- instances, not their persistence. Any number of non_primary mappers
- may be created for a particular class.
-
- :param order_by: A single :class:`Column` or list of :class:`Column` objects for which
- selection operations should use as the default ordering for entities.
- Defaults to the OID/ROWID of the table if any, or the first primary
- key column of the table.
-
- :param passive_updates: Indicates UPDATE behavior of foreign keys when a primary key changes
- on a joined-table inheritance or other joined table mapping.
-
+ :param inherit_condition: For joined table inheritance, a SQL
+ expression (constructed
+ :class:`~sqlalchemy.expression.sql.ClauseElement`) which will
+ define how the two tables are joined; defaults to a natural join
+ between the two tables.
+
+ :param inherit_foreign_keys: When inherit_condition is used and the
+ condition contains no ForeignKey columns, specify the "foreign"
+ columns of the join condition in this list. else leave as None.
+
+ :param non_primary: Construct a :class:`Mapper` that will define only
+ the selection of instances, not their persistence. Any number of
+ non_primary mappers may be created for a particular class.
+
+ :param order_by: A single :class:`Column` or list of :class:`Column`
+ objects for which selection operations should use as the default
+ ordering for entities. Defaults to the OID/ROWID of the table if
+ any, or the first primary key column of the table.
+
+ :param passive_updates: Indicates UPDATE behavior of foreign keys
+ when a primary key changes on a joined-table inheritance or other
+ joined table mapping.
+
When True, it is assumed that ON UPDATE CASCADE is configured on
- the foreign key in the database, and that the database will
- handle propagation of an UPDATE from a source column to
- dependent rows. Note that with databases which enforce
- referential integrity (i.e. PostgreSQL, MySQL with InnoDB tables),
- ON UPDATE CASCADE is required for this operation. The
- relationship() will update the value of the attribute on related
- items which are locally present in the session during a flush.
-
+ the foreign key in the database, and that the database will handle
+ propagation of an UPDATE from a source column to dependent rows.
+ Note that with databases which enforce referential integrity (i.e.
+ PostgreSQL, MySQL with InnoDB tables), ON UPDATE CASCADE is
+ required for this operation. The relationship() will update the
+ value of the attribute on related items which are locally present
+ in the session during a flush.
+
When False, it is assumed that the database does not enforce
- referential integrity and will not be issuing its own CASCADE
- operation for an update. The relationship() will issue the
- appropriate UPDATE statements to the database in response to the
- change of a referenced key, and items locally present in the
- session during a flush will also be refreshed.
-
+ referential integrity and will not be issuing its own CASCADE
+ operation for an update. The relationship() will issue the
+ appropriate UPDATE statements to the database in response to the
+ change of a referenced key, and items locally present in the
+ session during a flush will also be refreshed.
+
This flag should probably be set to False if primary key changes
- are expected and the database in use doesn't support CASCADE
- (i.e. SQLite, MySQL MyISAM tables).
-
+ are expected and the database in use doesn't support CASCADE (i.e.
+ SQLite, MySQL MyISAM tables).
+
Also see the passive_updates flag on :func:`relationship()`.
-
+
A future SQLAlchemy release will provide a "detect" feature for
- this flag.
-
- :param polymorphic_on: Used with mappers in an inheritance relationship, a ``Column`` which
- will identify the class/mapper combination to be used with a
- particular row. Requires the ``polymorphic_identity`` value to be set
- for all mappers in the inheritance hierarchy. The column specified by
- ``polymorphic_on`` is usually a column that resides directly within
- the base mapper's mapped table; alternatively, it may be a column that
- is only present within the <selectable> portion of the
- ``with_polymorphic`` argument.
-
- :param polymorphic_identity: A value which will be stored in the Column denoted by polymorphic_on,
- corresponding to the *class identity* of this mapper.
-
- :param properties: A dictionary mapping the string names of object attributes to
- ``MapperProperty`` instances, which define the persistence behavior of
- that attribute. Note that the columns in the mapped table are
- automatically converted into ``ColumnProperty`` instances based on the
- `key` property of each ``Column`` (although they can be overridden
- using this dictionary).
-
- :param primary_key: A list of ``Column`` objects which define the *primary key* to be used
- against this mapper's selectable unit. This is normally simply the
- primary key of the `local_table`, but can be overridden here.
-
- :param version_id_col: A ``Column`` which must have an integer type that will be used to keep
- a running *version id* of mapped entities in the database. this is
- used during save operations to ensure that no other thread or process
- has updated the instance during the lifetime of the entity, else a
- :class:`StaleDataError` exception is thrown.
-
- :param version_id_generator: A callable which defines the algorithm used to generate new version
- ids. Defaults to an integer generator. Can be replaced with one that
- generates timestamps, uuids, etc. e.g.::
+ this flag.
+
+ :param polymorphic_on: Used with mappers in an inheritance
+ relationship, a ``Column`` which will identify the class/mapper
+ combination to be used with a particular row. Requires the
+ ``polymorphic_identity`` value to be set for all mappers in the
+ inheritance hierarchy. The column specified by ``polymorphic_on``
+ is usually a column that resides directly within the base mapper's
+ mapped table; alternatively, it may be a column that is only
+ present within the <selectable> portion of the ``with_polymorphic``
+ argument.
+
+ :param polymorphic_identity: A value which will be stored in the
+ Column denoted by polymorphic_on, corresponding to the *class
+ identity* of this mapper.
+
+ :param properties: A dictionary mapping the string names of object
+ attributes to ``MapperProperty`` instances, which define the
+ persistence behavior of that attribute. Note that the columns in
+ the mapped table are automatically converted into
+ ``ColumnProperty`` instances based on the `key` property of each
+ ``Column`` (although they can be overridden using this dictionary).
+
+ :param primary_key: A list of ``Column`` objects which define the
+ *primary key* to be used against this mapper's selectable unit.
+ This is normally simply the primary key of the `local_table`, but
+ can be overridden here.
+
+ :param version_id_col: A ``Column`` which must have an integer type
+ that will be used to keep a running *version id* of mapped entities
+ in the database. this is used during save operations to ensure that
+ no other thread or process has updated the instance during the
+ lifetime of the entity, else a :class:`StaleDataError` exception is
+ thrown.
+
+ :param version_id_generator: A callable which defines the algorithm
+ used to generate new version ids. Defaults to an integer
+ generator. Can be replaced with one that generates timestamps,
+ uuids, etc. e.g.::
import uuid
@@ -761,29 +774,31 @@ def mapper(class_, local_table=None, *args, **params):
The callable receives the current version identifier as its
single argument.
- :param with_polymorphic: A tuple in the form ``(<classes>, <selectable>)`` indicating the
- default style of "polymorphic" loading, that is, which tables are
- queried at once. <classes> is any single or list of mappers and/or
- classes indicating the inherited classes that should be loaded at
- once. The special value ``'*'`` may be used to indicate all descending
- classes should be loaded immediately. The second tuple argument
- <selectable> indicates a selectable that will be used to query for
- multiple classes. Normally, it is left as None, in which case this
- mapper will form an outer join from the base mapper's table to that of
- all desired sub-mappers. When specified, it provides the selectable
- to be used for polymorphic loading. When with_polymorphic includes
- mappers which load from a "concrete" inheriting table, the
- <selectable> argument is required, since it usually requires more
- complex UNION queries.
+ :param with_polymorphic: A tuple in the form ``(<classes>,
+ <selectable>)`` indicating the default style of "polymorphic"
+ loading, that is, which tables are queried at once. <classes> is
+ any single or list of mappers and/or classes indicating the
+ inherited classes that should be loaded at once. The special value
+ ``'*'`` may be used to indicate all descending classes should be
+ loaded immediately. The second tuple argument <selectable>
+ indicates a selectable that will be used to query for multiple
+ classes. Normally, it is left as None, in which case this mapper
+ will form an outer join from the base mapper's table to that of
+ all desired sub-mappers. When specified, it provides the
+ selectable to be used for polymorphic loading. When
+ with_polymorphic includes mappers which load from a "concrete"
+ inheriting table, the <selectable> argument is required, since it
+ usually requires more complex UNION queries.
-
"""
return Mapper(class_, local_table, *args, **params)
-def synonym(name, map_column=False, descriptor=None, comparator_factory=None, doc=None):
+def synonym(name, map_column=False, descriptor=None,
+ comparator_factory=None, doc=None):
"""Set up `name` as a synonym to another mapped property.
- Used with the ``properties`` dictionary sent to :func:`~sqlalchemy.orm.mapper`.
+ Used with the ``properties`` dictionary sent to
+ :func:`~sqlalchemy.orm.mapper`.
Any existing attributes on the class which map the key name sent
to the ``properties`` dictionary will be used by the synonym to provide
@@ -848,12 +863,13 @@ def comparable_property(comparator_factory, descriptor=None):
mapper(MyClass, mytable, properties={
'myprop': comparable_property(MyComparator)})
- Used with the ``properties`` dictionary sent to :func:`~sqlalchemy.orm.mapper`.
+ Used with the ``properties`` dictionary sent to
+ :func:`~sqlalchemy.orm.mapper`.
Note that :func:`comparable_property` is usually not needed for basic
- needs. The recipe at :mod:`.derived_attributes` offers a simpler pure-Python
- method of achieving a similar result using class-bound attributes with
- SQLAlchemy expression constructs.
+ needs. The recipe at :mod:`.derived_attributes` offers a simpler
+ pure-Python method of achieving a similar result using class-bound
+ attributes with SQLAlchemy expression constructs.
:param comparator_factory:
A PropComparator subclass or factory that defines operator behavior
@@ -913,9 +929,9 @@ def joinedload(*keys, **kw):
name into an joined eager load.
.. note:: This function is known as :func:`eagerload` in all versions
- of SQLAlchemy prior to version 0.6beta3, including the 0.5 and 0.4 series.
- :func:`eagerload` will remain available for
- the foreseeable future in order to enable cross-compatibility.
+ of SQLAlchemy prior to version 0.6beta3, including the 0.5 and 0.4
+ series. :func:`eagerload` will remain available for the foreseeable
+ future in order to enable cross-compatibility.
Used with :meth:`~sqlalchemy.orm.query.Query.options`.
@@ -937,10 +953,11 @@ def joinedload(*keys, **kw):
query(Order).options(joinedload(Order.user, innerjoin=True))
- Note that the join created by :func:`joinedload` is aliased such that
- no other aspects of the query will affect what it loads. To use joined eager
- loading with a join that is constructed manually using :meth:`~sqlalchemy.orm.query.Query.join`
- or :func:`~sqlalchemy.orm.join`, see :func:`contains_eager`.
+ Note that the join created by :func:`joinedload` is aliased such that no
+ other aspects of the query will affect what it loads. To use joined eager
+ loading with a join that is constructed manually using
+ :meth:`~sqlalchemy.orm.query.Query.join` or :func:`~sqlalchemy.orm.join`,
+ see :func:`contains_eager`.
See also: :func:`subqueryload`, :func:`lazyload`
@@ -960,9 +977,9 @@ def joinedload_all(*keys, **kw):
given dot-separated path into an joined eager load.
.. note:: This function is known as :func:`eagerload_all` in all versions
- of SQLAlchemy prior to version 0.6beta3, including the 0.5 and 0.4 series.
- :func:`eagerload_all` will remain available for
- the foreseeable future in order to enable cross-compatibility.
+ of SQLAlchemy prior to version 0.6beta3, including the 0.5 and 0.4
+ series. :func:`eagerload_all` will remain available for the
+ foreseeable future in order to enable cross-compatibility.
Used with :meth:`~sqlalchemy.orm.query.Query.options`.
@@ -977,8 +994,9 @@ def joinedload_all(*keys, **kw):
query.options(joinedload_all(User.orders, Order.items, Item.keywords))
- The keyword arguments accept a flag `innerjoin=True|False` which will
- override the value of the `innerjoin` flag specified on the relationship().
+ The keyword arguments accept a flag `innerjoin=True|False` which will
+ override the value of the `innerjoin` flag specified on the
+ relationship().
See also: :func:`subqueryload_all`, :func:`lazyload`
@@ -1043,7 +1061,8 @@ def subqueryload_all(*keys):
Individual descriptors are accepted as arguments as well::
- query.options(subqueryload_all(User.orders, Order.items, Item.keywords))
+ query.options(subqueryload_all(User.orders, Order.items,
+ Item.keywords))
See also: :func:`joinedload_all`, :func:`lazyload`
@@ -1102,10 +1121,10 @@ def contains_eager(*keys, **kwargs):
``User`` entity, and the returned ``Order`` objects would have the
``Order.user`` attribute pre-populated.
- :func:`contains_eager` also accepts an `alias` argument, which
- is the string name of an alias, an :func:`~sqlalchemy.sql.expression.alias`
- construct, or an :func:`~sqlalchemy.orm.aliased` construct. Use this
- when the eagerly-loaded rows are to come from an aliased table::
+ :func:`contains_eager` also accepts an `alias` argument, which is the
+ string name of an alias, an :func:`~sqlalchemy.sql.expression.alias`
+ construct, or an :func:`~sqlalchemy.orm.aliased` construct. Use this when
+ the eagerly-loaded rows are to come from an aliased table::
user_alias = aliased(User)
sess.query(Order).\\
@@ -1118,12 +1137,11 @@ def contains_eager(*keys, **kwargs):
"""
alias = kwargs.pop('alias', None)
if kwargs:
- raise exceptions.ArgumentError("Invalid kwargs for contains_eager: %r" % kwargs.keys())
-
- return (
- strategies.EagerLazyOption(keys, lazy='joined', propagate_to_loaders=False),
- strategies.LoadEagerFromAliasOption(keys, alias=alias)
- )
+ raise exceptions.ArgumentError('Invalid kwargs for contains_eag'
+ 'er: %r' % kwargs.keys())
+ return strategies.EagerLazyOption(keys, lazy='joined',
+ propagate_to_loaders=False), \
+ strategies.LoadEagerFromAliasOption(keys, alias=alias)
@sa_util.accepts_a_list_as_starargs(list_deprecation='deprecated')
def defer(*keys):
diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py
index 2701fc879..a09463a4f 100644
--- a/lib/sqlalchemy/orm/attributes.py
+++ b/lib/sqlalchemy/orm/attributes.py
@@ -484,6 +484,7 @@ class ScalarAttributeImpl(AttributeImpl):
accepts_scalar_loader = True
uses_objects = False
+ supports_population = True
def delete(self, state, dict_):
@@ -538,6 +539,7 @@ class MutableScalarAttributeImpl(ScalarAttributeImpl):
"""
uses_objects = False
+ supports_population = True
def __init__(self, class_, key, callable_, events,
class_manager, copy_function=None,
@@ -604,6 +606,7 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl):
accepts_scalar_loader = False
uses_objects = True
+ supports_population = True
def __init__(self, class_, key, callable_, events,
trackparent=False, extension=None, copy_function=None,
@@ -694,6 +697,7 @@ class CollectionAttributeImpl(AttributeImpl):
"""
accepts_scalar_loader = False
uses_objects = True
+ supports_population = True
def __init__(self, class_, key, callable_, events,
typecallable=None, trackparent=False, extension=None,
diff --git a/lib/sqlalchemy/orm/collections.py b/lib/sqlalchemy/orm/collections.py
index 0ea17cd8b..b5c4353b3 100644
--- a/lib/sqlalchemy/orm/collections.py
+++ b/lib/sqlalchemy/orm/collections.py
@@ -129,7 +129,7 @@ def column_mapped_collection(mapping_spec):
from sqlalchemy.orm.util import _state_mapper
from sqlalchemy.orm.attributes import instance_state
- cols = [expression._no_literals(q) for q in util.to_list(mapping_spec)]
+ cols = [expression._only_column_elements(q) for q in util.to_list(mapping_spec)]
if len(cols) == 1:
def keyfunc(value):
state = instance_state(value)
diff --git a/lib/sqlalchemy/orm/dynamic.py b/lib/sqlalchemy/orm/dynamic.py
index d0aa2c20b..d55838011 100644
--- a/lib/sqlalchemy/orm/dynamic.py
+++ b/lib/sqlalchemy/orm/dynamic.py
@@ -43,10 +43,12 @@ log.class_logger(DynaLoader)
class DynamicAttributeImpl(attributes.AttributeImpl):
uses_objects = True
accepts_scalar_loader = False
-
+ supports_population = False
+
def __init__(self, class_, key, typecallable,
target_mapper, order_by, query_class=None, **kwargs):
- super(DynamicAttributeImpl, self).__init__(class_, key, typecallable, **kwargs)
+ super(DynamicAttributeImpl, self).\
+ __init__(class_, key, typecallable, **kwargs)
self.target_mapper = target_mapper
self.order_by = order_by
if not query_class:
@@ -58,15 +60,18 @@ class DynamicAttributeImpl(attributes.AttributeImpl):
def get(self, state, dict_, passive=False):
if passive:
- return self._get_collection_history(state, passive=True).added_items
+ return self._get_collection_history(state,
+ passive=True).added_items
else:
return self.query_class(self, state)
def get_collection(self, state, dict_, user_data=None, passive=True):
if passive:
- return self._get_collection_history(state, passive=passive).added_items
+ return self._get_collection_history(state,
+ passive=passive).added_items
else:
- history = self._get_collection_history(state, passive=passive)
+ history = self._get_collection_history(state,
+ passive=passive)
return history.added_items + history.unchanged_items
def fire_append_event(self, state, dict_, value, initiator):
@@ -105,30 +110,36 @@ class DynamicAttributeImpl(attributes.AttributeImpl):
dict_[self.key] = True
return state.committed_state[self.key]
- def set(self, state, dict_, value, initiator, passive=attributes.PASSIVE_OFF):
+ def set(self, state, dict_, value, initiator,
+ passive=attributes.PASSIVE_OFF):
if initiator is self:
return
self._set_iterable(state, dict_, value)
def _set_iterable(self, state, dict_, iterable, adapter=None):
-
collection_history = self._modified_event(state, dict_)
new_values = list(iterable)
-
if state.has_identity:
old_collection = list(self.get(state, dict_))
else:
old_collection = []
-
- collections.bulk_replace(new_values, DynCollectionAdapter(self, state, old_collection), DynCollectionAdapter(self, state, new_values))
+ collections.bulk_replace(new_values, DynCollectionAdapter(self,
+ state, old_collection),
+ DynCollectionAdapter(self, state,
+ new_values))
def delete(self, *args, **kwargs):
raise NotImplementedError()
+ def set_committed_value(self, state, dict_, value):
+ raise NotImplementedError("Dynamic attributes don't support "
+ "collection population.")
+
def get_history(self, state, dict_, passive=False):
c = self._get_collection_history(state, passive)
- return attributes.History(c.added_items, c.unchanged_items, c.deleted_items)
+ return attributes.History(c.added_items, c.unchanged_items,
+ c.deleted_items)
def _get_collection_history(self, state, passive=False):
if self.key in state.committed_state:
@@ -193,7 +204,8 @@ class AppenderMixin(object):
def __session(self):
sess = object_session(self.instance)
- if sess is not None and self.autoflush and sess.autoflush and self.instance in sess:
+ if sess is not None and self.autoflush and sess.autoflush \
+ and self.instance in sess:
sess.flush()
if not has_identity(self.instance):
return None
@@ -283,7 +295,8 @@ class CollectionHistory(object):
deleted = util.IdentitySet(apply_to.deleted_items)
added = apply_to.added_items
coll = AppenderQuery(attr, state).autoflush(False)
- self.unchanged_items = [o for o in util.IdentitySet(coll) if o not in deleted]
+ self.unchanged_items = [o for o in util.IdentitySet(coll)
+ if o not in deleted]
self.added_items = apply_to.added_items
self.deleted_items = apply_to.deleted_items
else:
diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py
index 6ecc202b5..945b657e4 100644
--- a/lib/sqlalchemy/orm/interfaces.py
+++ b/lib/sqlalchemy/orm/interfaces.py
@@ -474,7 +474,7 @@ class MapperProperty(object):
return iter(())
- def set_parent(self, parent):
+ def set_parent(self, parent, init):
self.parent = parent
def instrument_class(self, mapper):
diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py
index b566610f1..b268cf6b5 100644
--- a/lib/sqlalchemy/orm/mapper.py
+++ b/lib/sqlalchemy/orm/mapper.py
@@ -57,8 +57,6 @@ _COMPILE_MUTEX = util.threading.RLock()
# initialize these lazily
ColumnProperty = None
-SynonymProperty = None
-ComparableProperty = None
RelationshipProperty = None
ConcreteInheritedProperty = None
_expire_state = None
@@ -708,35 +706,11 @@ class Mapper(object):
for col in col.proxy_set:
self._columntoproperty[col] = prop
- elif isinstance(prop, (ComparableProperty, SynonymProperty)) and \
- setparent:
- if prop.descriptor is None:
- desc = getattr(self.class_, key, None)
- if self._is_userland_descriptor(desc):
- prop.descriptor = desc
- if getattr(prop, 'map_column', False):
- if key not in self.mapped_table.c:
- raise sa_exc.ArgumentError(
- "Can't compile synonym '%s': no column on table "
- "'%s' named '%s'"
- % (prop.name, self.mapped_table.description, key))
- elif self.mapped_table.c[key] in self._columntoproperty and \
- self._columntoproperty[
- self.mapped_table.c[key]
- ].key == prop.name:
- raise sa_exc.ArgumentError(
- "Can't call map_column=True for synonym %r=%r, "
- "a ColumnProperty already exists keyed to the name "
- "%r for column %r" %
- (key, prop.name, prop.name, key)
- )
- p = ColumnProperty(self.mapped_table.c[key])
- self._configure_property(
- prop.name, p,
- init=init,
- setparent=setparent)
- p._mapped_by_synonym = key
-
+ prop.key = key
+
+ if setparent:
+ prop.set_parent(self, init)
+
if key in self._props and \
getattr(self._props[key], '_mapped_by_synonym', False):
syn = self._props[key]._mapped_by_synonym
@@ -747,10 +721,6 @@ class Mapper(object):
)
self._props[key] = prop
- prop.key = key
-
- if setparent:
- prop.set_parent(self)
if not self.non_primary:
prop.instrument_class(self)
@@ -904,26 +874,36 @@ class Mapper(object):
def has_property(self, key):
return key in self._props
- def get_property(self, key, resolve_synonyms=False, raiseerr=True):
- """return a MapperProperty associated with the given key."""
+ def get_property(self, key,
+ resolve_synonyms=False,
+ raiseerr=True, _compile_mappers=True):
+
+ """return a :class:`.MapperProperty` associated with the given key.
+
+ resolve_synonyms=False and raiseerr=False are deprecated.
+
+ """
- if not self.compiled:
+ if _compile_mappers and not self.compiled:
self.compile()
- return self._get_property(key,
- resolve_synonyms=resolve_synonyms,
- raiseerr=raiseerr)
-
- def _get_property(self, key, resolve_synonyms=False, raiseerr=True):
- prop = self._props.get(key, None)
- if resolve_synonyms:
- while isinstance(prop, SynonymProperty):
- prop = self._props.get(prop.name, None)
- if prop is None and raiseerr:
- raise sa_exc.InvalidRequestError(
- "Mapper '%s' has no property '%s'" %
- (self, key))
- return prop
-
+
+ if not resolve_synonyms:
+ prop = self._props.get(key, None)
+ if prop is None and raiseerr:
+ raise sa_exc.InvalidRequestError(
+ "Mapper '%s' has no property '%s'" %
+ (self, key))
+ return prop
+ else:
+ try:
+ return getattr(self.class_, key).property
+ except AttributeError:
+ if raiseerr:
+ raise sa_exc.InvalidRequestError(
+ "Mapper '%s' has no property '%s'" % (self, key))
+ else:
+ return None
+
@property
def iterate_properties(self):
"""return an iterator of all MapperProperty objects."""
diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py
index cf5f31162..cbfba91f3 100644
--- a/lib/sqlalchemy/orm/properties.py
+++ b/lib/sqlalchemy/orm/properties.py
@@ -237,7 +237,22 @@ class CompositeProperty(ColumnProperty):
def __str__(self):
return str(self.parent.class_.__name__) + "." + self.key
-class ConcreteInheritedProperty(MapperProperty):
+class DescriptorProperty(MapperProperty):
+ """:class:`MapperProperty` which proxies access to a
+ plain descriptor."""
+
+ def setup(self, context, entity, path, adapter, **kwargs):
+ pass
+
+ def create_row_processor(self, selectcontext, path, mapper, row, adapter):
+ return (None, None)
+
+ def merge(self, session, source_state, source_dict,
+ dest_state, dest_dict, load, _recursive):
+ pass
+
+
+class ConcreteInheritedProperty(DescriptorProperty):
"""A 'do nothing' :class:`MapperProperty` that disables
an attribute on a concrete subclass that is only present
on the inherited mapper, not the concrete classes' mapper.
@@ -254,18 +269,6 @@ class ConcreteInheritedProperty(MapperProperty):
"""
- extension = None
-
- def setup(self, context, entity, path, adapter, **kwargs):
- pass
-
- def create_row_processor(self, selectcontext, path, mapper, row, adapter):
- return (None, None)
-
- def merge(self, session, source_state, source_dict, dest_state,
- dest_dict, load, _recursive):
- pass
-
def instrument_class(self, mapper):
def warn():
raise AttributeError("Concrete %s does not implement "
@@ -284,7 +287,7 @@ class ConcreteInheritedProperty(MapperProperty):
comparator_callable = None
# TODO: put this process into a deferred callable?
for m in self.parent.iterate_to_root():
- p = m._get_property(self.key)
+ p = m.get_property(self.key, _compile_mappers=False)
if not isinstance(p, ConcreteInheritedProperty):
comparator_callable = p.comparator_factory
break
@@ -299,9 +302,7 @@ class ConcreteInheritedProperty(MapperProperty):
)
-class SynonymProperty(MapperProperty):
-
- extension = None
+class SynonymProperty(DescriptorProperty):
def __init__(self, name, map_column=None,
descriptor=None, comparator_factory=None,
@@ -313,14 +314,40 @@ class SynonymProperty(MapperProperty):
self.doc = doc or (descriptor and descriptor.__doc__) or None
util.set_creation_order(self)
- def setup(self, context, entity, path, adapter, **kwargs):
- pass
+ def set_parent(self, parent, init):
+ if self.map_column:
+ # implement the 'map_column' option.
+ if self.key not in parent.mapped_table.c:
+ raise sa_exc.ArgumentError(
+ "Can't compile synonym '%s': no column on table "
+ "'%s' named '%s'"
+ % (self.name, parent.mapped_table.description, self.key))
+ elif parent.mapped_table.c[self.key] in \
+ parent._columntoproperty and \
+ parent._columntoproperty[
+ parent.mapped_table.c[self.key]
+ ].key == self.name:
+ raise sa_exc.ArgumentError(
+ "Can't call map_column=True for synonym %r=%r, "
+ "a ColumnProperty already exists keyed to the name "
+ "%r for column %r" %
+ (self.key, self.name, self.name, self.key)
+ )
+ p = ColumnProperty(parent.mapped_table.c[self.key])
+ parent._configure_property(
+ self.name, p,
+ init=init,
+ setparent=True)
+ p._mapped_by_synonym = self.key
- def create_row_processor(self, selectcontext, path, mapper, row, adapter):
- return (None, None)
+ self.parent = parent
def instrument_class(self, mapper):
- class_ = self.parent.class_
+
+ if self.descriptor is None:
+ desc = getattr(mapper.class_, self.key, None)
+ if mapper._is_userland_descriptor(desc):
+ self.descriptor = desc
if self.descriptor is None:
class SynonymProp(object):
@@ -337,8 +364,9 @@ class SynonymProperty(MapperProperty):
def comparator_callable(prop, mapper):
def comparator():
- prop = self.parent._get_property(
- self.key, resolve_synonyms=True)
+ prop = mapper.get_property(
+ self.name, resolve_synonyms=True,
+ _compile_mappers=False)
if self.comparator_factory:
return self.comparator_factory(prop, mapper)
else:
@@ -355,17 +383,10 @@ class SynonymProperty(MapperProperty):
doc=self.doc
)
- def merge(self, session, source_state, source_dict, dest_state,
- dest_dict, load, _recursive):
- pass
-log.class_logger(SynonymProperty)
-
-class ComparableProperty(MapperProperty):
+class ComparableProperty(DescriptorProperty):
"""Instruments a Python property for use in query expressions."""
- extension = None
-
def __init__(self, comparator_factory, descriptor=None, doc=None):
self.descriptor = descriptor
self.comparator_factory = comparator_factory
@@ -374,6 +395,11 @@ class ComparableProperty(MapperProperty):
def instrument_class(self, mapper):
"""Set up a proxy to the unmanaged descriptor."""
+
+ if self.descriptor is None:
+ desc = getattr(mapper.class_, self.key, None)
+ if mapper._is_userland_descriptor(desc):
+ self.descriptor = desc
attributes.register_descriptor(
mapper.class_,
@@ -385,16 +411,6 @@ class ComparableProperty(MapperProperty):
doc=self.doc,
)
- def setup(self, context, entity, path, adapter, **kwargs):
- pass
-
- def create_row_processor(self, selectcontext, path, mapper, row, adapter):
- return (None, None)
-
- def merge(self, session, source_state, source_dict,
- dest_state, dest_dict, load, _recursive):
- pass
-
class RelationshipProperty(StrategizedProperty):
"""Describes an object property that holds a single item or list
@@ -837,7 +853,7 @@ class RelationshipProperty(StrategizedProperty):
yield (c, instance_mapper, instance_state)
def _add_reverse_property(self, key):
- other = self.mapper._get_property(key)
+ other = self.mapper.get_property(key, _compile_mappers=False)
self._reverse_property.add(other)
other._reverse_property.add(self)
@@ -924,8 +940,7 @@ class RelationshipProperty(StrategizedProperty):
if not self.parent.concrete:
for inheriting in self.parent.iterate_to_root():
if inheriting is not self.parent \
- and inheriting._get_property(self.key,
- raiseerr=False):
+ and inheriting.has_property(self.key):
util.warn("Warning: relationship '%s' on mapper "
"'%s' supercedes the same relationship "
"on inherited mapper '%s'; this can "
@@ -1216,7 +1231,7 @@ class RelationshipProperty(StrategizedProperty):
def _assert_is_primary(self):
if not self.is_primary() \
and not mapper.class_mapper(self.parent.class_,
- compile=False)._get_property(self.key, raiseerr=False):
+ compile=False).has_property(self.key):
raise sa_exc.ArgumentError("Attempting to assign a new "
"relationship '%s' to a non-primary mapper on "
"class '%s'. New relationships can only be added "
@@ -1234,8 +1249,7 @@ class RelationshipProperty(StrategizedProperty):
else:
backref_key, kwargs = self.backref
mapper = self.mapper.primary_mapper()
- if mapper._get_property(backref_key, raiseerr=False) \
- is not None:
+ if mapper.has_property(backref_key):
raise sa_exc.ArgumentError("Error creating backref "
"'%s' on relationship '%s': property of that "
"name exists on mapper '%s'" % (backref_key,
@@ -1416,7 +1430,5 @@ PropertyLoader = RelationProperty = RelationshipProperty
log.class_logger(RelationshipProperty)
mapper.ColumnProperty = ColumnProperty
-mapper.SynonymProperty = SynonymProperty
-mapper.ComparableProperty = ComparableProperty
mapper.RelationshipProperty = RelationshipProperty
mapper.ConcreteInheritedProperty = ConcreteInheritedProperty
diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py
index cc6d15a74..c269c8bb9 100644
--- a/lib/sqlalchemy/orm/query.py
+++ b/lib/sqlalchemy/orm/query.py
@@ -864,7 +864,7 @@ class Query(object):
"""apply the given filtering criterion to the query and return
the newly resulting ``Query``."""
- clauses = [_entity_descriptor(self._joinpoint_zero(), key)[0] == value
+ clauses = [_entity_descriptor(self._joinpoint_zero(), key) == value
for key, value in kwargs.iteritems()]
return self.filter(sql.and_(*clauses))
@@ -1158,7 +1158,7 @@ class Query(object):
if isinstance(onclause, basestring):
left_entity = self._joinpoint_zero()
- descriptor, prop = _entity_descriptor(left_entity, onclause)
+ descriptor = _entity_descriptor(left_entity, onclause)
onclause = descriptor
# check for q.join(Class.propname, from_joinpoint=True)
@@ -1171,7 +1171,7 @@ class Query(object):
_entity_info(self._joinpoint_zero())
if left_mapper is left_entity:
left_entity = self._joinpoint_zero()
- descriptor, prop = _entity_descriptor(left_entity,
+ descriptor = _entity_descriptor(left_entity,
onclause.key)
onclause = descriptor
diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py
index d092375a6..8516a22de 100644
--- a/lib/sqlalchemy/orm/session.py
+++ b/lib/sqlalchemy/orm/session.py
@@ -235,7 +235,7 @@ class SessionTransaction(object):
def _assert_is_open(self, error_msg="The transaction is closed"):
if self.session is None:
- raise sa_exc.InvalidRequestError(error_msg)
+ raise sa_exc.ResourceClosedError(error_msg)
@property
def _is_transaction_boundary(self):
@@ -427,7 +427,8 @@ class SessionTransaction(object):
return self
def __exit__(self, type, value, traceback):
- self._assert_is_open("Cannot end transaction context. The transaction was closed from within the context")
+ self._assert_is_open("Cannot end transaction context. The transaction "
+ "was closed from within the context")
if self.session.transaction is None:
return
if type is None:
diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py
index 62602ff37..1c4571aed 100644
--- a/lib/sqlalchemy/orm/strategies.py
+++ b/lib/sqlalchemy/orm/strategies.py
@@ -806,6 +806,12 @@ class SubqueryLoader(AbstractRelationshipLoader):
]
def create_row_processor(self, context, path, mapper, row, adapter):
+ if not self.parent.class_manager[self.key].impl.supports_population:
+ raise sa_exc.InvalidRequestError(
+ "'%s' does not support object "
+ "population - eager loading cannot be applied." %
+ self)
+
path = path + (self.key,)
path = interfaces._reduce_path(path)
@@ -876,6 +882,7 @@ class EagerLoader(AbstractRelationshipLoader):
**kwargs):
"""Add a left outer join to the statement thats being constructed."""
+
if not context.query._enable_eagerloads:
return
@@ -1069,7 +1076,14 @@ class EagerLoader(AbstractRelationshipLoader):
return False
def create_row_processor(self, context, path, mapper, row, adapter):
+ if not self.parent.class_manager[self.key].impl.supports_population:
+ raise sa_exc.InvalidRequestError(
+ "'%s' does not support object "
+ "population - eager loading cannot be applied." %
+ self)
+
path = path + (self.key,)
+
eager_adapter = self._create_eager_adapter(
context,
diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py
index ef5413724..0f4adec00 100644
--- a/lib/sqlalchemy/orm/util.py
+++ b/lib/sqlalchemy/orm/util.py
@@ -347,9 +347,12 @@ class AliasedClass(object):
return queryattr
def __getattr__(self, key):
- prop = self.__mapper._get_property(key, raiseerr=False)
- if prop:
- return self.__adapt_prop(prop)
+ if self.__mapper.has_property(key):
+ return self.__adapt_prop(
+ self.__mapper.get_property(
+ key, _compile_mappers=False
+ )
+ )
for base in self.__target.__mro__:
try:
@@ -537,40 +540,22 @@ def _entity_info(entity, compile=True):
return mapper, mapper._with_polymorphic_selectable, False
def _entity_descriptor(entity, key):
- """Return attribute/property information given an entity and string name.
-
- Returns a 2-tuple representing InstrumentedAttribute/MapperProperty.
+ """Return a class attribute given an entity and string name.
+
+ May return :class:`.InstrumentedAttribute` or user-defined
+ attribute.
"""
- if isinstance(entity, AliasedClass):
- try:
- desc = getattr(entity, key)
- return desc, desc.property
- except AttributeError:
- raise sa_exc.InvalidRequestError(
- "Entity '%s' has no property '%s'" %
- (entity, key)
- )
-
- elif isinstance(entity, type):
- try:
- desc = attributes.manager_of_class(entity)[key]
- return desc, desc.property
- except KeyError:
- raise sa_exc.InvalidRequestError(
- "Entity '%s' has no property '%s'" %
- (entity, key)
- )
-
- else:
- try:
- desc = entity.class_manager[key]
- return desc, desc.property
- except KeyError:
- raise sa_exc.InvalidRequestError(
- "Entity '%s' has no property '%s'" %
- (entity, key)
- )
+ if not isinstance(entity, (AliasedClass, type)):
+ entity = entity.class_
+
+ try:
+ return getattr(entity, key)
+ except AttributeError:
+ raise sa_exc.InvalidRequestError(
+ "Entity '%s' has no property '%s'" %
+ (entity, key)
+ )
def _orm_columns(entity):
mapper, selectable, is_aliased_class = _entity_info(entity)
diff --git a/lib/sqlalchemy/schema.py b/lib/sqlalchemy/schema.py
index 75bbfd070..5f82a0200 100644
--- a/lib/sqlalchemy/schema.py
+++ b/lib/sqlalchemy/schema.py
@@ -806,7 +806,11 @@ class Column(SchemaItem, expression.ColumnClause):
for fk in col.foreign_keys:
col.foreign_keys.remove(fk)
table.foreign_keys.remove(fk)
- table.constraints.remove(fk.constraint)
+ if fk.constraint in table.constraints:
+ # this might have been removed
+ # already, if it's a composite constraint
+ # and more than one col being replaced
+ table.constraints.remove(fk.constraint)
table._columns.replace(self)
@@ -1019,7 +1023,20 @@ class ForeignKey(SchemaItem):
return "ForeignKey(%r)" % self._get_colspec()
def copy(self, schema=None):
- """Produce a copy of this ForeignKey object."""
+ """Produce a copy of this :class:`ForeignKey` object.
+
+ The new :class:`ForeignKey` will not be bound
+ to any :class:`Column`.
+
+ This method is usually used by the internal
+ copy procedures of :class:`Column`, :class:`Table`,
+ and :class:`MetaData`.
+
+ :param schema: The returned :class:`ForeignKey` will
+ reference the original table and column name, qualified
+ by the given string schema name.
+
+ """
return ForeignKey(
self._get_colspec(schema=schema),
@@ -1033,6 +1050,12 @@ class ForeignKey(SchemaItem):
)
def _get_colspec(self, schema=None):
+ """Return a string based 'column specification' for this :class:`ForeignKey`.
+
+ This is usually the equivalent of the string-based "tablename.colname"
+ argument first passed to the object's constructor.
+
+ """
if schema:
return schema + "." + self.column.table.name + \
"." + self.column.key
@@ -1048,15 +1071,16 @@ class ForeignKey(SchemaItem):
target_fullname = property(_get_colspec)
def references(self, table):
- """Return True if the given table is referenced by this ForeignKey."""
+ """Return True if the given :class:`Table` is referenced by this :class:`ForeignKey`."""
return table.corresponding_column(self.column) is not None
def get_referent(self, table):
- """Return the column in the given table referenced by this ForeignKey.
+ """Return the :class:`.Column` in the given :class:`.Table`
+ referenced by this :class:`ForeignKey`.
- Returns None if this ``ForeignKey`` does not reference the given
- table.
+ Returns None if this :class:`ForeignKey` does not reference the given
+ :class:`Table`.
"""
@@ -1064,6 +1088,18 @@ class ForeignKey(SchemaItem):
@util.memoized_property
def column(self):
+ """Return the target :class:`.Column` referenced by this :class:`.ForeignKey`.
+
+ If this :class:`ForeignKey` was created using a
+ string-based target column specification, this
+ attribute will on first access initiate a resolution
+ process to locate the referenced remote
+ :class:`.Column`. The resolution process traverses
+ to the parent :class:`.Column`, :class:`.Table`, and
+ :class:`.MetaData` to proceed - if any of these aren't
+ yet present, an error is raised.
+
+ """
# ForeignKey inits its remote column as late as possible, so tables
# can be defined without dependencies
if isinstance(self._colspec, basestring):
@@ -1108,8 +1144,9 @@ class ForeignKey(SchemaItem):
if _get_table_key(tname, schema) not in parenttable.metadata:
raise exc.NoReferencedTableError(
- "Could not find table '%s' with which to generate a "
- "foreign key" % tname)
+ "Foreign key assocated with column '%s' could not find "
+ "table '%s' with which to generate a "
+ "foreign key to target column '%s'" % (self.parent, tname, colname))
table = Table(tname, parenttable.metadata,
mustexist=True, schema=schema)
diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py
index 050b5c05b..0a5edb42f 100644
--- a/lib/sqlalchemy/sql/expression.py
+++ b/lib/sqlalchemy/sql/expression.py
@@ -428,8 +428,8 @@ def case(whens, value=None, else_=None):
The expressions used for THEN and ELSE,
when specified as strings, will be interpreted
as bound values. To specify textual SQL expressions
- for these, use the literal_column(<string>) or
- text(<string>) construct.
+ for these, use the :func:`literal_column`
+ construct.
The expressions used for the WHEN criterion
may only be literal strings when "value" is
@@ -1035,6 +1035,14 @@ def _no_literals(element):
else:
return element
+def _only_column_elements(element):
+ if hasattr(element, '__clause_element__'):
+ element = element.__clause_element__()
+ if not isinstance(element, ColumnElement):
+ raise exc.ArgumentError("Column-based expression object expected; "
+ "got: %r" % element)
+ return element
+
def _corresponding_column_or_error(fromclause, column, require_embedded=False):
c = fromclause.corresponding_column(column,
require_embedded=require_embedded)
@@ -1813,7 +1821,7 @@ class ColumnElement(ClauseElement, _CompareMixin):
else:
name = str(self)
co = ColumnClause(self.anon_label, selectable, type_=getattr(self, 'type', None))
-
+
co.proxies = [self]
selectable.columns[name] = co
return co
@@ -2428,7 +2436,7 @@ class _TextClause(Executable, ClauseElement):
if self.typemap is not None and len(self.typemap) == 1:
return list(self.typemap)[0]
else:
- return None
+ return sqltypes.NULLTYPE
def self_group(self, against=None):
if against is operators.in_op:
@@ -3179,7 +3187,8 @@ class _Label(ColumnElement):
self._element = element
self._type = type_
self.quote = element.quote
-
+ self.proxies = [element]
+
@util.memoized_property
def type(self):
return sqltypes.to_instance(
@@ -3190,17 +3199,13 @@ class _Label(ColumnElement):
def element(self):
return self._element.self_group(against=operators.as_)
- def _proxy_attr(name):
- get = attrgetter(name)
- def attr(self):
- return get(self.element)
- return property(attr)
+ @property
+ def primary_key(self):
+ return self.element.primary_key
- proxies = _proxy_attr('proxies')
- base_columns = _proxy_attr('base_columns')
- proxy_set = _proxy_attr('proxy_set')
- primary_key = _proxy_attr('primary_key')
- foreign_keys = _proxy_attr('foreign_keys')
+ @property
+ def foreign_keys(self):
+ return self.element.foreign_keys
def get_children(self, **kwargs):
return self.element,
@@ -3217,6 +3222,7 @@ class _Label(ColumnElement):
e = self.element._make_proxy(selectable, name=self.name)
else:
e = column(self.name)._make_proxy(selectable=selectable)
+
e.proxies.append(self)
return e