summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2010-09-19 16:11:16 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2010-09-19 16:11:16 -0400
commit90c8d8e0c9e2d0a9eeace7fa326df26a5f28465a (patch)
tree48b1e7fa44d5368f56be00c78c0e3d647186c497 /lib/sqlalchemy
parente4bc7d289477e22815f4c6ab86b3f0c1bf356e08 (diff)
parentc5c8cdf3b4d7dc456cfef29ea04b2b7300060c7a (diff)
downloadsqlalchemy-90c8d8e0c9e2d0a9eeace7fa326df26a5f28465a.tar.gz
merge tip
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/__init__.py2
-rw-r--r--lib/sqlalchemy/dialects/firebird/base.py3
-rw-r--r--lib/sqlalchemy/dialects/informix/base.py27
-rw-r--r--lib/sqlalchemy/dialects/informix/informixdb.py11
-rw-r--r--lib/sqlalchemy/dialects/mssql/base.py2
-rw-r--r--lib/sqlalchemy/dialects/oracle/base.py35
-rw-r--r--lib/sqlalchemy/engine/__init__.py36
-rw-r--r--lib/sqlalchemy/engine/base.py108
-rw-r--r--lib/sqlalchemy/engine/reflection.py14
-rw-r--r--lib/sqlalchemy/exc.py4
-rw-r--r--lib/sqlalchemy/ext/compiler.py2
-rwxr-xr-xlib/sqlalchemy/ext/declarative.py7
-rw-r--r--lib/sqlalchemy/ext/sqlsoup.py10
-rw-r--r--lib/sqlalchemy/orm/__init__.py118
-rw-r--r--lib/sqlalchemy/orm/attributes.py98
-rw-r--r--lib/sqlalchemy/orm/collections.py14
-rw-r--r--lib/sqlalchemy/orm/dependency.py15
-rw-r--r--lib/sqlalchemy/orm/dynamic.py2
-rw-r--r--lib/sqlalchemy/orm/evaluator.py3
-rw-r--r--lib/sqlalchemy/orm/exc.py2
-rw-r--r--lib/sqlalchemy/orm/identity.py77
-rw-r--r--lib/sqlalchemy/orm/interfaces.py39
-rw-r--r--lib/sqlalchemy/orm/mapper.py222
-rw-r--r--lib/sqlalchemy/orm/properties.py39
-rw-r--r--lib/sqlalchemy/orm/query.py94
-rw-r--r--lib/sqlalchemy/orm/scoping.py9
-rw-r--r--lib/sqlalchemy/orm/session.py118
-rw-r--r--lib/sqlalchemy/orm/state.py2
-rw-r--r--lib/sqlalchemy/orm/strategies.py122
-rw-r--r--lib/sqlalchemy/orm/sync.py2
-rw-r--r--lib/sqlalchemy/orm/unitofwork.py6
-rw-r--r--lib/sqlalchemy/orm/util.py40
-rw-r--r--lib/sqlalchemy/pool.py43
-rw-r--r--lib/sqlalchemy/schema.py118
-rw-r--r--lib/sqlalchemy/sql/compiler.py4
-rw-r--r--lib/sqlalchemy/sql/expression.py75
-rw-r--r--lib/sqlalchemy/sql/util.py25
-rw-r--r--lib/sqlalchemy/test/requires.py8
-rw-r--r--lib/sqlalchemy/test/testing.py3
-rw-r--r--lib/sqlalchemy/types.py346
-rw-r--r--lib/sqlalchemy/util.py75
41 files changed, 1335 insertions, 645 deletions
diff --git a/lib/sqlalchemy/__init__.py b/lib/sqlalchemy/__init__.py
index 2d809e339..cb4e8e10b 100644
--- a/lib/sqlalchemy/__init__.py
+++ b/lib/sqlalchemy/__init__.py
@@ -114,6 +114,6 @@ from sqlalchemy.engine import create_engine, engine_from_config
__all__ = sorted(name for name, obj in locals().items()
if not (name.startswith('_') or inspect.ismodule(obj)))
-__version__ = '0.6.4'
+__version__ = '0.6.5'
del inspect, sys
diff --git a/lib/sqlalchemy/dialects/firebird/base.py b/lib/sqlalchemy/dialects/firebird/base.py
index 04439afb9..1eb2549c9 100644
--- a/lib/sqlalchemy/dialects/firebird/base.py
+++ b/lib/sqlalchemy/dialects/firebird/base.py
@@ -571,9 +571,10 @@ class FBDialect(default.DefaultDialect):
if row['fdefault'] is not None:
# the value comes down as "DEFAULT 'value'": there may be
# more than one whitespace around the "DEFAULT" keyword
+ # and it may also be lower case
# (see also http://tracker.firebirdsql.org/browse/CORE-356)
defexpr = row['fdefault'].lstrip()
- assert defexpr[:8].rstrip() == \
+ assert defexpr[:8].rstrip().upper() == \
'DEFAULT', "Unrecognized default value: %s" % \
defexpr
defvalue = defexpr[8:].strip()
diff --git a/lib/sqlalchemy/dialects/informix/base.py b/lib/sqlalchemy/dialects/informix/base.py
index bc7b6c3e7..242b8a328 100644
--- a/lib/sqlalchemy/dialects/informix/base.py
+++ b/lib/sqlalchemy/dialects/informix/base.py
@@ -105,29 +105,20 @@ class InfoSQLCompiler(compiler.SQLCompiler):
s += ""
return s
- def visit_select(self, select):
- # the column in order by clause must in select too
-
- def __label(c):
- try:
- return c._label.lower()
- except:
- return ''
-
- # TODO: dont modify the original select, generate a new one
- a = [__label(c) for c in select._raw_columns]
- for c in select._order_by_clause.clauses:
- if __label(c) not in a:
- select.append_column(c)
-
- return compiler.SQLCompiler.visit_select(self, select)
+ def visit_select(self, select, asfrom=False, parens=True, **kw):
+ text = compiler.SQLCompiler.visit_select(self, select, asfrom, parens, **kw)
+ if asfrom and parens and self.dialect.server_version_info < (11,):
+ #assuming that 11 version doesn't need this, not tested
+ return "table(multiset" + text + ")"
+ else:
+ return text
def limit_clause(self, select):
if select._offset is not None and select._offset > 0:
raise NotImplementedError("Informix does not support OFFSET")
return ""
- def visit_function(self, func):
+ def visit_function(self, func, **kw):
if func.name.lower() == 'current_date':
return "today"
elif func.name.lower() == 'current_time':
@@ -135,7 +126,7 @@ class InfoSQLCompiler(compiler.SQLCompiler):
elif func.name.lower() in ('current_timestamp', 'now'):
return "CURRENT YEAR TO SECOND"
else:
- return compiler.SQLCompiler.visit_function(self, func)
+ return compiler.SQLCompiler.visit_function(self, func, **kw)
class InfoDDLCompiler(compiler.DDLCompiler):
diff --git a/lib/sqlalchemy/dialects/informix/informixdb.py b/lib/sqlalchemy/dialects/informix/informixdb.py
index 54e5a994a..8edcc953b 100644
--- a/lib/sqlalchemy/dialects/informix/informixdb.py
+++ b/lib/sqlalchemy/dialects/informix/informixdb.py
@@ -31,10 +31,13 @@ class InformixDialect_informixdb(InformixDialect):
def _get_server_version_info(self, connection):
# http://informixdb.sourceforge.net/manual.html#inspecting-version-numbers
- vers = connection.dbms_version
-
- # TODO: not tested
- return tuple([int(x) for x in vers.split('.')])
+ version = []
+ for n in connection.connection.dbms_version.split('.'):
+ try:
+ version.append(int(n))
+ except ValueError:
+ version.append(n)
+ return tuple(version)
def is_disconnect(self, e):
if isinstance(e, self.dbapi.OperationalError):
diff --git a/lib/sqlalchemy/dialects/mssql/base.py b/lib/sqlalchemy/dialects/mssql/base.py
index 089d2f71d..a17b2484d 100644
--- a/lib/sqlalchemy/dialects/mssql/base.py
+++ b/lib/sqlalchemy/dialects/mssql/base.py
@@ -104,7 +104,7 @@ Compatibility Levels
MSSQL supports the notion of setting compatibility levels at the
database level. This allows, for instance, to run a database that
is compatibile with SQL2000 while running on a SQL2005 database
-server. ``server_version_info`` will always retrun the database
+server. ``server_version_info`` will always return the database
server version information (in this case SQL2005) and not the
compatibiility level information. Because of this, if running under
a backwards compatibility mode SQAlchemy may attempt to use T-SQL
diff --git a/lib/sqlalchemy/dialects/oracle/base.py b/lib/sqlalchemy/dialects/oracle/base.py
index 4c153dac2..0aa348953 100644
--- a/lib/sqlalchemy/dialects/oracle/base.py
+++ b/lib/sqlalchemy/dialects/oracle/base.py
@@ -899,11 +899,22 @@ class OracleDialect(default.DefaultDialect):
uniqueness = dict(NONUNIQUE=False, UNIQUE=True)
oracle_sys_col = re.compile(r'SYS_NC\d+\$', re.IGNORECASE)
+
+ def upper_name_set(names):
+ return set([i.upper() for i in names])
+
+ pk_names = upper_name_set(pkeys)
+
+ def remove_if_primary_key(index):
+ # don't include the primary key index
+ if index is not None and \
+ upper_name_set(index['column_names']) == pk_names:
+ indexes.pop()
+
+ index = None
for rset in rp:
- # don't include the primary key columns
- if rset.column_name in [s.upper() for s in pkeys]:
- continue
if rset.index_name != last_index_name:
+ remove_if_primary_key(index)
index = dict(name=self.normalize_name(rset.index_name), column_names=[])
indexes.append(index)
index['unique'] = uniqueness.get(rset.uniqueness, False)
@@ -913,6 +924,7 @@ class OracleDialect(default.DefaultDialect):
if not oracle_sys_col.match(rset.column_name):
index['column_names'].append(self.normalize_name(rset.column_name))
last_index_name = rset.index_name
+ remove_if_primary_key(index)
return indexes
@reflection.cache
@@ -945,7 +957,6 @@ class OracleDialect(default.DefaultDialect):
constraint_data = rp.fetchall()
return constraint_data
- @reflection.cache
def get_primary_keys(self, connection, table_name, schema=None, **kw):
"""
@@ -956,7 +967,10 @@ class OracleDialect(default.DefaultDialect):
dblink
"""
+ return self._get_primary_keys(connection, table_name, schema, **kw)[0]
+ @reflection.cache
+ def _get_primary_keys(self, connection, table_name, schema=None, **kw):
resolve_synonyms = kw.get('oracle_resolve_synonyms', False)
dblink = kw.get('dblink', '')
info_cache = kw.get('info_cache')
@@ -966,6 +980,7 @@ class OracleDialect(default.DefaultDialect):
resolve_synonyms, dblink,
info_cache=info_cache)
pkeys = []
+ constraint_name = None
constraint_data = self._get_constraint_data(connection, table_name,
schema, dblink,
info_cache=kw.get('info_cache'))
@@ -975,8 +990,18 @@ class OracleDialect(default.DefaultDialect):
(cons_name, cons_type, local_column, remote_table, remote_column, remote_owner) = \
row[0:2] + tuple([self.normalize_name(x) for x in row[2:6]])
if cons_type == 'P':
+ if constraint_name is None:
+ constraint_name = self.normalize_name(cons_name)
pkeys.append(local_column)
- return pkeys
+ return pkeys, constraint_name
+
+ def get_pk_constraint(self, connection, table_name, schema=None, **kw):
+ cols, name = self._get_primary_keys(connection, table_name, schema=schema, **kw)
+
+ return {
+ 'constrained_columns':cols,
+ 'name':name
+ }
@reflection.cache
def get_foreign_keys(self, connection, table_name, schema=None, **kw):
diff --git a/lib/sqlalchemy/engine/__init__.py b/lib/sqlalchemy/engine/__init__.py
index b4894250f..43d3dd038 100644
--- a/lib/sqlalchemy/engine/__init__.py
+++ b/lib/sqlalchemy/engine/__init__.py
@@ -132,12 +132,19 @@ def create_engine(*args, **kwargs):
additional keyword arguments.
:param convert_unicode=False: if set to True, all
- String/character based types will convert Unicode values to raw
- byte values going into the database, and all raw byte values to
+ String/character based types will convert Python Unicode values to raw
+ byte values sent to the DBAPI as bind parameters, and all raw byte values to
Python Unicode coming out in result sets. This is an
- engine-wide method to provide unicode conversion across the
- board. For unicode conversion on a column-by-column level, use
- the ``Unicode`` column type instead, described in `types`.
+ engine-wide method to provide Unicode conversion across the
+ board for those DBAPIs that do not accept Python Unicode objects
+ as input. For Unicode conversion on a column-by-column level, use
+ the ``Unicode`` column type instead, described in :ref:`types_toplevel`. Note that
+ many DBAPIs have the ability to return Python Unicode objects in
+ result sets directly - SQLAlchemy will use these modes of operation
+ if possible and will also attempt to detect "Unicode returns"
+ behavior by the DBAPI upon first connect by the
+ :class:`.Engine`. When this is detected, string values in
+ result sets are passed through without further processing.
:param creator: a callable which returns a DBAPI connection.
This creation function will be passed to the underlying
@@ -188,10 +195,13 @@ def create_engine(*args, **kwargs):
opened above and beyond the pool_size setting, which defaults
to five. this is only used with :class:`~sqlalchemy.pool.QueuePool`.
- :param module=None: used by database implementations which
- support multiple DBAPI modules, this is a reference to a DBAPI2
- module to be used instead of the engine's default module. For
- PostgreSQL, the default is psycopg2. For Oracle, it's cx_Oracle.
+ :param module=None: reference to a Python module object (the module itself, not
+ its string name). Specifies an alternate DBAPI module to be used
+ by the engine's dialect. Each sub-dialect references a specific DBAPI which
+ will be imported before first connect. This parameter causes the
+ import to be bypassed, and the given module to be used instead.
+ Can be used for testing of DBAPIs as well as to inject "mock"
+ DBAPI implementations into the :class:`.Engine`.
:param pool=None: an already-constructed instance of
:class:`~sqlalchemy.pool.Pool`, such as a
@@ -199,7 +209,7 @@ def create_engine(*args, **kwargs):
pool will be used directly as the underlying connection pool
for the engine, bypassing whatever connection parameters are
present in the URL argument. For information on constructing
- connection pools manually, see `pooling`.
+ connection pools manually, see :ref:`pooling_toplevel`.
:param poolclass=None: a :class:`~sqlalchemy.pool.Pool`
subclass, which will be used to create a connection pool
@@ -224,7 +234,7 @@ def create_engine(*args, **kwargs):
connections after the given number of seconds has passed. It
defaults to -1, or no timeout. For example, setting to 3600
means connections will be recycled after one hour. Note that
- MySQL in particular will ``disconnect automatically`` if no
+ MySQL in particular will disconnect automatically if no
activity is detected on a connection for eight hours (although
this is configurable with the MySQLDB connection itself and the
server configuration as well).
@@ -233,8 +243,8 @@ def create_engine(*args, **kwargs):
up on getting a connection from the pool. This is only used
with :class:`~sqlalchemy.pool.QueuePool`.
- :param strategy='plain': used to invoke alternate :class:`~sqlalchemy.engine.base.Engine.`
- implementations. Currently available is the ``threadlocal``
+ :param strategy='plain': selects alternate engine implementations.
+ Currently available is the ``threadlocal``
strategy, which is described in :ref:`threadlocal_strategy`.
"""
diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py
index 14ebf916b..4d6912ce4 100644
--- a/lib/sqlalchemy/engine/base.py
+++ b/lib/sqlalchemy/engine/base.py
@@ -797,11 +797,22 @@ class Connectable(object):
class Connection(Connectable):
"""Provides high-level functionality for a wrapped DB-API connection.
- Provides execution support for string-based SQL statements as well
- as ClauseElement, Compiled and DefaultGenerator objects. Provides
- a :meth:`begin` method to return Transaction objects.
-
- The Connection object is **not** thread-safe.
+ Provides execution support for string-based SQL statements as well as
+ :class:`.ClauseElement`, :class:`.Compiled` and :class:`.DefaultGenerator`
+ objects. Provides a :meth:`begin` method to return :class:`.Transaction`
+ objects.
+
+ The Connection object is **not** thread-safe. While a Connection can be
+ shared among threads using properly synchronized access, it is still
+ possible that the underlying DBAPI connection may not support shared
+ access between threads. Check the DBAPI documentation for details.
+
+ The Connection object represents a single dbapi connection checked out
+ from the connection pool. In this state, the connection pool has no affect
+ upon the connection, including its expiration or timeout state. For the
+ connection pool to properly manage connections, connections should be
+ returned to the connection pool (i.e. ``connection.close()``) whenever the
+ connection is not in use.
.. index::
single: thread safety; Connection
@@ -812,9 +823,9 @@ class Connection(Connectable):
_branch=False, _execution_options=None):
"""Construct a new Connection.
- Connection objects are typically constructed by an
- :class:`~sqlalchemy.engine.Engine`, see the ``connect()`` and
- ``contextual_connect()`` methods of Engine.
+ The constructor here is not public and is only called only by an
+ :class:`.Engine`. See :meth:`.Engine.connect` and
+ :meth:`.Engine.contextual_connect` methods.
"""
self.engine = engine
@@ -1154,7 +1165,22 @@ class Connection(Connectable):
return self.execute(object, *multiparams, **params).scalar()
def execute(self, object, *multiparams, **params):
- """Executes and returns a ResultProxy."""
+ """Executes the given construct and returns a :class:`.ResultProxy`.
+
+ The construct can be one of:
+
+ * a textual SQL string
+ * any :class:`.ClauseElement` construct that is also
+ a subclass of :class:`.Executable`, such as a
+ :func:`.select` construct
+ * a :class:`.FunctionElement`, such as that generated
+ by :attr:`.func`, will be automatically wrapped in
+ a SELECT statement, which is then executed.
+ * a :class:`.DDLElement` object
+ * a :class:`.DefaultGenerator` object
+ * a :class:`.Compiled` object
+
+ """
for c in type(object).__mro__:
if c in Connection.executors:
@@ -1451,6 +1477,12 @@ class Connection(Connectable):
class Transaction(object):
"""Represent a Transaction in progress.
+ The object provides :meth:`.rollback` and :meth:`.commit`
+ methods in order to control transaction boundaries. It
+ also implements a context manager interface so that
+ the Python ``with`` statement can be used with the
+ :meth:`.Connection.begin` method.
+
The Transaction object is **not** threadsafe.
.. index::
@@ -1458,12 +1490,17 @@ class Transaction(object):
"""
def __init__(self, connection, parent):
+ """The constructor for :class:`.Transaction` is private
+ and is called from within the :class:`.Connection.begin`
+ implementation.
+
+ """
self.connection = connection
self._parent = parent or self
self.is_active = True
def close(self):
- """Close this transaction.
+ """Close this :class:`.Transaction`.
If this transaction is the base transaction in a begin/commit
nesting, the transaction will rollback(). Otherwise, the
@@ -1471,6 +1508,7 @@ class Transaction(object):
This is used to cancel a Transaction without affecting the scope of
an enclosing transaction.
+
"""
if not self._parent.is_active:
return
@@ -1478,6 +1516,9 @@ class Transaction(object):
self.rollback()
def rollback(self):
+ """Roll back this :class:`.Transaction`.
+
+ """
if not self._parent.is_active:
return
self._do_rollback()
@@ -1487,6 +1528,8 @@ class Transaction(object):
self._parent.rollback()
def commit(self):
+ """Commit this :class:`.Transaction`."""
+
if not self._parent.is_active:
raise exc.InvalidRequestError("This transaction is inactive")
self._do_commit()
@@ -1726,7 +1769,20 @@ class Engine(Connectable, log.Identified):
conn.close()
def execute(self, statement, *multiparams, **params):
- """Executes and returns a ResultProxy."""
+ """Executes the given construct and returns a :class:`.ResultProxy`.
+
+ The arguments are the same as those used by
+ :meth:`.Connection.execute`.
+
+ Here, a :class:`.Connection` is acquired using the
+ :meth:`~.Engine.contextual_connect` method, and the statement executed
+ with that connection. The returned :class:`.ResultProxy` is flagged
+ such that when the :class:`.ResultProxy` is exhausted and its
+ underlying cursor is closed, the :class:`.Connection` created here
+ will also be closed, which allows its associated DBAPI connection
+ resource to be returned to the connection pool.
+
+ """
connection = self.contextual_connect(close_with_result=True)
return connection.execute(statement, *multiparams, **params)
@@ -1743,16 +1799,30 @@ class Engine(Connectable, log.Identified):
return connection._execute_compiled(compiled, multiparams, params)
def connect(self, **kwargs):
- """Return a newly allocated Connection object."""
+ """Return a new :class:`.Connection` object.
+
+ The :class:`.Connection`, upon construction, will procure a DBAPI connection
+ from the :class:`.Pool` referenced by this :class:`.Engine`,
+ returning it back to the :class:`.Pool` after the :meth:`.Connection.close`
+ method is called.
+
+ """
return self.Connection(self, **kwargs)
def contextual_connect(self, close_with_result=False, **kwargs):
- """Return a Connection object which may be newly allocated,
- or may be part of some ongoing context.
+ """Return a :class:`.Connection` object which may be part of some ongoing context.
+
+ By default, this method does the same thing as :meth:`.Engine.connect`.
+ Subclasses of :class:`.Engine` may override this method
+ to provide contextual behavior.
- This Connection is meant to be used by the various
- "auto-connecting" operations.
+ :param close_with_result: When True, the first :class:`.ResultProxy` created
+ by the :class:`.Connection` will call the :meth:`.Connection.close` method
+ of that connection as soon as any pending result rows are exhausted.
+ This is used to supply the "connectionless execution" behavior provided
+ by the :meth:`.Engine.execute` method.
+
"""
return self.Connection(self,
@@ -2179,7 +2249,7 @@ class ResultProxy(object):
self.context = context
self.dialect = context.dialect
self.closed = False
- self.cursor = context.cursor
+ self.cursor = self._saved_cursor = context.cursor
self.connection = context.root_connection
self._echo = self.connection._echo and \
context.engine._should_log_debug()
@@ -2234,12 +2304,12 @@ class ResultProxy(object):
regardless of database backend.
"""
- return self.cursor.lastrowid
+ return self._saved_cursor.lastrowid
def _cursor_description(self):
"""May be overridden by subclasses."""
- return self.cursor.description
+ return self._saved_cursor.description
def _autoclose(self):
"""called by the Connection to autoclose cursors that have no pending
diff --git a/lib/sqlalchemy/engine/reflection.py b/lib/sqlalchemy/engine/reflection.py
index 4a34ef1c6..def889e6d 100644
--- a/lib/sqlalchemy/engine/reflection.py
+++ b/lib/sqlalchemy/engine/reflection.py
@@ -50,27 +50,27 @@ class Inspector(object):
consistent interface as well as caching support for previously
fetched metadata.
- The preferred method to construct an :class:`Inspector` is via the
+ The preferred method to construct an :class:`.Inspector` is via the
:meth:`Inspector.from_engine` method. I.e.::
engine = create_engine('...')
insp = Inspector.from_engine(engine)
Where above, the :class:`~sqlalchemy.engine.base.Dialect` may opt
- to return an :class:`Inspector` subclass that provides additional
+ to return an :class:`.Inspector` subclass that provides additional
methods specific to the dialect's target database.
"""
def __init__(self, bind):
- """Initialize a new :class:`Inspector`.
+ """Initialize a new :class:`.Inspector`.
:param bind: a :class:`~sqlalchemy.engine.base.Connectable`,
which is typically an instance of
:class:`~sqlalchemy.engine.base.Engine` or
:class:`~sqlalchemy.engine.base.Connection`.
- For a dialect-specific instance of :class:`Inspector`, see
+ For a dialect-specific instance of :class:`.Inspector`, see
:meth:`Inspector.from_engine`
"""
@@ -98,12 +98,12 @@ class Inspector(object):
:class:`~sqlalchemy.engine.base.Engine` or
:class:`~sqlalchemy.engine.base.Connection`.
- This method differs from direct a direct constructor call of :class:`Inspector`
+ This method differs from direct a direct constructor call of :class:`.Inspector`
in that the :class:`~sqlalchemy.engine.base.Dialect` is given a chance to provide
- a dialect-specific :class:`Inspector` instance, which may provide additional
+ a dialect-specific :class:`.Inspector` instance, which may provide additional
methods.
- See the example at :class:`Inspector`.
+ See the example at :class:`.Inspector`.
"""
if hasattr(bind.dialect, 'inspector'):
diff --git a/lib/sqlalchemy/exc.py b/lib/sqlalchemy/exc.py
index 1c412824c..003969f56 100644
--- a/lib/sqlalchemy/exc.py
+++ b/lib/sqlalchemy/exc.py
@@ -5,9 +5,9 @@
"""Exceptions used with SQLAlchemy.
-The base exception class is SQLAlchemyError. Exceptions which are raised as a
+The base exception class is :class:`.SQLAlchemyError`. Exceptions which are raised as a
result of DBAPI exceptions are all subclasses of
-:class:`~sqlalchemy.exc.DBAPIError`.
+:class:`.DBAPIError`.
"""
diff --git a/lib/sqlalchemy/ext/compiler.py b/lib/sqlalchemy/ext/compiler.py
index 12f1e443d..1bf4b1447 100644
--- a/lib/sqlalchemy/ext/compiler.py
+++ b/lib/sqlalchemy/ext/compiler.py
@@ -119,6 +119,8 @@ overriding routine and cause an endless loop. Such as, to add "prefix" to all
The above compiler will prefix all INSERT statements with "some prefix" when compiled.
+.. _type_compilation_extension:
+
Changing Compilation of Types
=============================
diff --git a/lib/sqlalchemy/ext/declarative.py b/lib/sqlalchemy/ext/declarative.py
index 3201433f4..40263c7b8 100755
--- a/lib/sqlalchemy/ext/declarative.py
+++ b/lib/sqlalchemy/ext/declarative.py
@@ -251,7 +251,8 @@ Similarly, :func:`comparable_using` is a front end for the
Defining SQL Expressions
========================
-The usage of :func:`.column_property` with Declarative is
+The usage of :func:`.column_property` with Declarative to define
+load-time, mapped SQL expressions is
pretty much the same as that described in
:ref:`mapper_sql_expressions`. Local columns within the same
class declaration can be referenced directly::
@@ -391,6 +392,8 @@ class declaration::
'version_id_generator': lambda v:datetime.now()
}
+.. _declarative_inheritance:
+
Inheritance Configuration
=========================
@@ -1189,7 +1192,7 @@ def _deferred_relationship(cls, prop):
return x
except NameError, n:
raise exceptions.InvalidRequestError(
- "When compiling mapper %s, expression %r failed to "
+ "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." %
diff --git a/lib/sqlalchemy/ext/sqlsoup.py b/lib/sqlalchemy/ext/sqlsoup.py
index e54cde3ed..e8234e7c7 100644
--- a/lib/sqlalchemy/ext/sqlsoup.py
+++ b/lib/sqlalchemy/ext/sqlsoup.py
@@ -3,7 +3,15 @@ Introduction
============
SqlSoup provides a convenient way to access existing database tables without
-having to declare table or mapper classes ahead of time. It is built on top of the SQLAlchemy ORM and provides a super-minimalistic interface to an existing database.
+having to declare table or mapper classes ahead of time. It is built on top of
+the SQLAlchemy ORM and provides a super-minimalistic interface to an existing
+database.
+
+SqlSoup effectively provides a coarse grained, alternative interface to
+working with the SQLAlchemy ORM, providing a "self configuring" interface
+for extremely rudimental operations. It's somewhat akin to a
+"super novice mode" version of the ORM. While SqlSoup can be very handy,
+users are strongly encouraged to use the full ORM for non-trivial applications.
Suppose we have a database with users, books, and loans tables
(corresponding to the PyWebOff dataset, if you're curious).
diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py
index 50ea4c283..39c68f0aa 100644
--- a/lib/sqlalchemy/orm/__init__.py
+++ b/lib/sqlalchemy/orm/__init__.py
@@ -51,7 +51,7 @@ from sqlalchemy.orm.mapper import reconstructor, validates
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 Session
from sqlalchemy.orm.session import object_session, sessionmaker, \
make_transient
from sqlalchemy.orm.scoping import ScopedSession
@@ -66,6 +66,7 @@ __all__ = (
'Validator',
'PropComparator',
'Query',
+ 'Session',
'aliased',
'backref',
'class_mapper',
@@ -109,17 +110,19 @@ __all__ = (
def scoped_session(session_factory, scopefunc=None):
- """Provides thread-local management of Sessions.
+ """Provides thread-local or scoped management of :class:`.Session` objects.
This is a front-end function to
- :class:`~sqlalchemy.orm.scoping.ScopedSession`.
+ :class:`.ScopedSession`.
:param session_factory: a callable function that produces
:class:`Session` instances, such as :func:`sessionmaker`.
- :param scopefunc: optional, TODO
+ :param scopefunc: Optional "scope" function which would be
+ passed to the :class:`.ScopedRegistry`. If None, the
+ :class:`.ThreadLocalRegistry` is used by default.
- :returns: an :class:`~sqlalchemy.orm.scoping.ScopedSession` instance
+ :returns: an :class:`.ScopedSession` instance
Usage::
@@ -173,17 +176,13 @@ def create_session(bind=None, **kwargs):
kwargs.setdefault('autoflush', False)
kwargs.setdefault('autocommit', True)
kwargs.setdefault('expire_on_commit', False)
- return _Session(bind=bind, **kwargs)
+ return Session(bind=bind, **kwargs)
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.
+ .. note:: :func:`relationship` is historically known as
+ :func:`relation` prior to version 0.6.
This corresponds to a parent-child or associative table relationship. The
constructed class is an instance of :class:`RelationshipProperty`.
@@ -260,6 +259,8 @@ def relationship(argument, secondary=None, **kwargs):
:param collection_class:
a class or callable that returns a new list-holding object. will
be used in place of a plain list for storing elements.
+ Behavior of this attribute is described in detail at
+ :ref:`custom_collections`.
:param comparator_factory:
a class which extends :class:`RelationshipProperty.Comparator` which
@@ -317,25 +318,28 @@ def relationship(argument, secondary=None, **kwargs):
which is already higher up in the chain. This option applies
both to joined- and subquery- eager loaders.
- :param lazy=('select'|'joined'|'subquery'|'noload'|'dynamic'): specifies
- how the related items should be loaded. Values include:
+ :param lazy='select': specifies
+ how the related items should be loaded. Default value is
+ ``select``. Values include:
- * 'select' - items should be loaded lazily when the property is first
- accessed.
+ * ``select`` - items should be loaded lazily when the property is first
+ accessed, using a separate SELECT statement.
- * 'joined' - items should be loaded "eagerly" in the same query as
- that of the parent, using a JOIN or LEFT OUTER JOIN.
+ * ``joined`` - items should be loaded "eagerly" in the same query as
+ that of the parent, using a JOIN or LEFT OUTER JOIN. Whether
+ the join is "outer" or not is determined by the ``innerjoin``
+ parameter.
- * 'subquery' - items should be loaded "eagerly" within the same
+ * ``subquery`` - items should be loaded "eagerly" within the same
query as that of the parent, using a second SQL statement
which issues a JOIN to a subquery of the original
statement.
- * 'noload' - no loading should occur at any time. This is to
+ * ``noload`` - no loading should occur at any time. This is to
support "write-only" attributes, or attributes which are
populated in some manner specific to the application.
- * 'dynamic' - the attribute will return a pre-configured
+ * ``dynamic`` - the attribute will return a pre-configured
:class:`~sqlalchemy.orm.query.Query` object for all read
operations, onto which further filtering operations can be
applied before iterating the results. The dynamic
@@ -350,6 +354,33 @@ def relationship(argument, secondary=None, **kwargs):
* None - a synonym for 'noload'
+ Detailed discussion of loader strategies is at :ref:`loading_toplevel`.
+
+ :param load_on_pending=False:
+ Indicates loading behavior for transient or pending parent objects.
+
+ When set to ``True``, causes the lazy-loader to
+ issue a query for a parent object that is not persistent, meaning it has
+ never been flushed. This may take effect for a pending object when
+ autoflush is disabled, or for a transient object that has been
+ "attached" to a :class:`.Session` but is not part of its pending
+ collection. Attachment of transient objects to the session without
+ moving to the "pending" state is not a supported behavior at this time.
+
+ Note that the load of related objects on a pending or transient object
+ also does not trigger any attribute change events - no user-defined
+ events will be emitted for these attributes, and if and when the
+ object is ultimately flushed, only the user-specific foreign key
+ attributes will be part of the modified state.
+
+ The load_on_pending flag does not improve behavior
+ when the ORM is used normally - object references should be constructed
+ at the object level, not at the foreign key level, so that they
+ are present in an ordinary way before flush() proceeds. This flag
+ is not not intended for general use.
+
+ New in 0.6.5.
+
:param order_by:
indicates the ordering that should be applied when loading these
items.
@@ -628,7 +659,7 @@ def deferred(*columns, **kwargs):
return ColumnProperty(deferred=True, *columns, **kwargs)
def mapper(class_, local_table=None, *args, **params):
- """Return a new :class:`~sqlalchemy.orm.Mapper` object.
+ """Return a new :class:`~.Mapper` object.
:param class\_: The class to be mapped.
@@ -670,22 +701,27 @@ def mapper(class_, local_table=None, *args, **params):
: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 exclude_properties: A list or set of string column names to
+ be excluded from mapping. As of SQLAlchemy 0.6.4, this collection
+ may also include :class:`.Column` objects. Columns named or present
+ in this list will not be automatically mapped. Note that neither
+ this option nor include_properties will allow one to circumvent plan
+ 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 include_properties: An inclusive list or set of string column
+ names to map. As of SQLAlchemy 0.6.4, this collection may also
+ include :class:`.Column` objects in order to disambiguate between
+ same-named columns in a selectable (such as a
+ :func:`~.expression.join()`). If this list is not ``None``, columns
+ present in the mapped table but not named or present in this list
+ will not be automatically mapped. See also "exclude_properties".
:param inherits: Another :class:`~sqlalchemy.orm.Mapper` for which
this :class:`~sqlalchemy.orm.Mapper` will have an inheritance
@@ -714,7 +750,7 @@ def mapper(class_, local_table=None, *args, **params):
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
+ 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.
@@ -723,20 +759,20 @@ def mapper(class_, local_table=None, *args, **params):
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
+ When False, it is assumed that the database does not enforce
referential integrity and will not be issuing its own CASCADE
operation for an update. The relationship() will issue the
appropriate UPDATE statements to the database in response to the
change of a referenced key, and items locally present in the
session during a flush will also be refreshed.
- This flag should probably be set to False if primary key changes
+ This flag should probably be set to False if primary key changes
are expected and the database in use doesn't support CASCADE (i.e.
SQLite, MySQL MyISAM tables).
Also see the passive_updates flag on :func:`relationship()`.
- A future SQLAlchemy release will provide a "detect" feature for
+ A future SQLAlchemy release will provide a "detect" feature for
this flag.
:param polymorphic_on: Used with mappers in an inheritance
@@ -852,7 +888,8 @@ def synonym(name, map_column=False, descriptor=None,
doc=doc)
def comparable_property(comparator_factory, descriptor=None):
- """Provide query semantics for an unmanaged attribute.
+ """Provides a method of applying a :class:`.PropComparator`
+ to any Python descriptor attribute.
Allows a regular Python @property (descriptor) to be used in Queries and
SQL constructs like a managed attribute. comparable_property wraps a
@@ -1035,8 +1072,6 @@ def subqueryload(*keys):
"""Return a ``MapperOption`` that will convert the property
of the given name into an subquery eager load.
- .. note:: This function is new as of SQLAlchemy version 0.6beta3.
-
Used with :meth:`~sqlalchemy.orm.query.Query.options`.
examples::
@@ -1061,8 +1096,6 @@ def subqueryload_all(*keys):
"""Return a ``MapperOption`` that will convert all properties along the
given dot-separated path into a subquery eager load.
- .. note:: This function is new as of SQLAlchemy version 0.6beta3.
-
Used with :meth:`~sqlalchemy.orm.query.Query.options`.
For example::
@@ -1147,6 +1180,9 @@ def contains_eager(*keys, **kwargs):
See also :func:`eagerload` for the "automatic" version of this
functionality.
+ For additional examples of :func:`contains_eager` see
+ :ref:`contains_eager`.
+
"""
alias = kwargs.pop('alias', None)
if kwargs:
diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py
index 800f3889f..b56de5f05 100644
--- a/lib/sqlalchemy/orm/attributes.py
+++ b/lib/sqlalchemy/orm/attributes.py
@@ -35,18 +35,25 @@ NEVER_SET = util.symbol('NEVER_SET')
# "passive" get settings
# TODO: the True/False values need to be factored out
-# of the rest of ORM code
-# don't fire off any callables, and don't initialize the attribute to
-# an empty value
PASSIVE_NO_INITIALIZE = True #util.symbol('PASSIVE_NO_INITIALIZE')
+"""Symbol indicating that loader callables should
+ not be fired off, and a non-initialized attribute
+ should remain that way."""
-# don't fire off any callables, but if no callables present
-# then initialize to an empty value/collection
# this is used by backrefs.
PASSIVE_NO_FETCH = util.symbol('PASSIVE_NO_FETCH')
+"""Symbol indicating that loader callables should not be fired off.
+ Non-initialized attributes should be initialized to an empty value."""
+
+PASSIVE_ONLY_PERSISTENT = util.symbol('PASSIVE_ONLY_PERSISTENT')
+"""Symbol indicating that loader callables should only fire off for
+persistent objects.
+
+Loads of "previous" values during change events use this flag.
+"""
-# fire callables/initialize as needed
PASSIVE_OFF = False #util.symbol('PASSIVE_OFF')
+"""Symbol indicating that loader callables should be executed."""
INSTRUMENTATION_MANAGER = '__sa_instrumentation_manager__'
"""Attribute, elects custom instrumentation when present on a mapped class.
@@ -448,7 +455,7 @@ class ScalarAttributeImpl(AttributeImpl):
self, state, dict_.get(self.key, NO_VALUE))
def set(self, state, dict_, value, initiator, passive=PASSIVE_OFF):
- if initiator is self:
+ if initiator and initiator.parent_token is self.parent_token:
return
if self.active_history:
@@ -527,7 +534,7 @@ class MutableScalarAttributeImpl(ScalarAttributeImpl):
state.mutable_dict.pop(self.key)
def set(self, state, dict_, value, initiator, passive=PASSIVE_OFF):
- if initiator is self:
+ if initiator and initiator.parent_token is self.parent_token:
return
if self.extensions:
@@ -589,14 +596,14 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl):
setter operation.
"""
- if initiator is self:
+ if initiator and initiator.parent_token is self.parent_token:
return
if self.active_history:
- old = self.get(state, dict_)
+ old = self.get(state, dict_, passive=PASSIVE_ONLY_PERSISTENT)
else:
old = self.get(state, dict_, passive=PASSIVE_NO_FETCH)
-
+
value = self.fire_replace_event(state, dict_, value, old, initiator)
dict_[self.key] = value
@@ -615,7 +622,7 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl):
previous is not None and
previous is not PASSIVE_NO_RESULT):
self.sethasparent(instance_state(previous), False)
-
+
for ext in self.extensions:
value = ext.set(state, value, previous, initiator or self)
@@ -719,7 +726,7 @@ class CollectionAttributeImpl(AttributeImpl):
self.key, state, self.collection_factory)
def append(self, state, dict_, value, initiator, passive=PASSIVE_OFF):
- if initiator is self:
+ if initiator and initiator.parent_token is self.parent_token:
return
collection = self.get_collection(state, dict_, passive=passive)
@@ -732,7 +739,7 @@ class CollectionAttributeImpl(AttributeImpl):
collection.append_with_event(value, initiator)
def remove(self, state, dict_, value, initiator, passive=PASSIVE_OFF):
- if initiator is self:
+ if initiator and initiator.parent_token is self.parent_token:
return
collection = self.get_collection(state, state.dict, passive=passive)
@@ -752,7 +759,7 @@ class CollectionAttributeImpl(AttributeImpl):
setter operation.
"""
- if initiator is self:
+ if initiator and initiator.parent_token is self.parent_token:
return
self._set_iterable(
@@ -777,15 +784,16 @@ class CollectionAttributeImpl(AttributeImpl):
else:
new_values = list(iterable)
- old = self.get(state, dict_)
-
- # ignore re-assignment of the current collection, as happens
- # implicitly with in-place operators (foo.collection |= other)
- if old is iterable:
+ old = self.get(state, dict_, passive=PASSIVE_ONLY_PERSISTENT)
+ if old is PASSIVE_NO_RESULT:
+ old = self.initialize(state, dict_)
+ elif old is iterable:
+ # ignore re-assignment of the current collection, as happens
+ # implicitly with in-place operators (foo.collection |= other)
return
state.modified_event(dict_, self, True, old)
-
+
old_collection = self.get_collection(state, dict_, old)
dict_[self.key] = user_data
@@ -855,7 +863,7 @@ class GenericBackrefExtension(interfaces.AttributeExtension):
def set(self, state, child, oldchild, initiator):
if oldchild is child:
return child
-
+
if oldchild is not None and oldchild is not PASSIVE_NO_RESULT:
# With lazy=None, there's no guarantee that the full collection is
# present when updating via a backref.
@@ -1293,8 +1301,10 @@ class _ClassInstrumentationAdapter(ClassManager):
return self._get_dict
class History(tuple):
- """A 3-tuple of added, unchanged and deleted values.
-
+ """A 3-tuple of added, unchanged and deleted values,
+ representing the changes which have occured on an instrumented
+ attribute.
+
Each tuple member is an iterable sequence.
"""
@@ -1302,9 +1312,18 @@ class History(tuple):
__slots__ = ()
added = property(itemgetter(0))
+ """Return the collection of items added to the attribute (the first tuple
+ element)."""
+
unchanged = property(itemgetter(1))
+ """Return the collection of items that have not changed on the attribute
+ (the second tuple element)."""
+
+
deleted = property(itemgetter(2))
-
+ """Return the collection of items that have been removed from the
+ attribute (the third tuple element)."""
+
def __new__(cls, added, unchanged, deleted):
return tuple.__new__(cls, (added, unchanged, deleted))
@@ -1312,25 +1331,38 @@ class History(tuple):
return self != HISTORY_BLANK
def empty(self):
+ """Return True if this :class:`History` has no changes
+ and no existing, unchanged state.
+
+ """
+
return not bool(
(self.added or self.deleted)
or self.unchanged and self.unchanged != [None]
)
def sum(self):
+ """Return a collection of added + unchanged + deleted."""
+
return (self.added or []) +\
(self.unchanged or []) +\
(self.deleted or [])
def non_deleted(self):
+ """Return a collection of added + unchanged."""
+
return (self.added or []) +\
(self.unchanged or [])
def non_added(self):
+ """Return a collection of unchanged + deleted."""
+
return (self.unchanged or []) +\
(self.deleted or [])
def has_changes(self):
+ """Return True if this :class:`History` has changes."""
+
return bool(self.added or self.deleted)
def as_state(self):
@@ -1391,11 +1423,19 @@ class History(tuple):
HISTORY_BLANK = History(None, None, None)
def get_history(obj, key, **kwargs):
- """Return a History record for the given object and attribute key.
+ """Return a :class:`.History` record for the given object
+ and attribute key.
- obj is an instrumented object instance. An InstanceState
- is accepted directly for backwards compatibility but
- this usage is deprecated.
+ :param obj: an object whose class is instrumented by the
+ attributes package.
+
+ :param key: string attribute name.
+
+ :param kwargs: Optional keyword arguments currently
+ include the ``passive`` flag, which indicates if the attribute should be
+ loaded from the database if not already present (:attr:`PASSIVE_NO_FETCH`), and
+ if the attribute should be not initialized to a blank value otherwise
+ (:attr:`PASSIVE_NO_INITIALIZE`). Default is :attr:`PASSIVE_OFF`.
"""
return get_state_history(instance_state(obj), key, **kwargs)
diff --git a/lib/sqlalchemy/orm/collections.py b/lib/sqlalchemy/orm/collections.py
index a9ad34239..b52329523 100644
--- a/lib/sqlalchemy/orm/collections.py
+++ b/lib/sqlalchemy/orm/collections.py
@@ -189,7 +189,7 @@ class collection(object):
The recipe decorators all require parens, even those that take no
arguments::
- @collection.adds('entity'):
+ @collection.adds('entity')
def insert(self, position, entity): ...
@collection.removes_return()
@@ -253,7 +253,7 @@ class collection(object):
The remover method is called with one positional argument: the value
to remove. The method will be automatically decorated with
- 'removes_return()' if not already decorated::
+ :meth:`removes_return` if not already decorated::
@collection.remover
def zap(self, entity): ...
@@ -293,7 +293,7 @@ class collection(object):
"""Tag the method as instrumented.
This tag will prevent any decoration from being applied to the method.
- Use this if you are orchestrating your own calls to collection_adapter
+ Use this if you are orchestrating your own calls to :func:`.collection_adapter`
in one of the basic SQLAlchemy interface methods, or to prevent
an automatic ABC method decoration from wrapping your implementation::
@@ -339,7 +339,7 @@ class collection(object):
The default converter implementation will use duck-typing to do the
conversion. A dict-like collection will be convert into an iterable
- of dictionary values, and other types will simply be iterated.
+ of dictionary values, and other types will simply be iterated::
@collection.converter
def convert(self, other): ...
@@ -442,7 +442,8 @@ class collection(object):
# public instrumentation interface for 'internally instrumented'
# implementations
def collection_adapter(collection):
- """Fetch the CollectionAdapter for a collection."""
+ """Fetch the :class:`.CollectionAdapter` for a collection."""
+
return getattr(collection, '_sa_adapter', None)
def collection_iter(collection):
@@ -545,6 +546,7 @@ class CollectionAdapter(object):
def append_with_event(self, item, initiator=None):
"""Add an entity to the collection, firing mutation events."""
+
getattr(self._data(), '_sa_appender')(item, _sa_initiator=initiator)
def append_without_event(self, item):
@@ -585,7 +587,7 @@ class CollectionAdapter(object):
def fire_append_event(self, item, initiator=None):
"""Notify that a entity has entered the collection.
- Initiator is the InstrumentedAttribute that initiated the membership
+ Initiator is a token owned by the InstrumentedAttribute that initiated the membership
mutation, and should be left as None unless you are passing along
an initiator value from a chained operation.
diff --git a/lib/sqlalchemy/orm/dependency.py b/lib/sqlalchemy/orm/dependency.py
index 376afd88d..4458a8547 100644
--- a/lib/sqlalchemy/orm/dependency.py
+++ b/lib/sqlalchemy/orm/dependency.py
@@ -734,7 +734,12 @@ class DetectKeySwitch(DependencyProcessor):
def per_property_preprocessors(self, uow):
if self.prop._reverse_property:
- return
+ if self.passive_updates:
+ return
+ else:
+ if False in (prop.passive_updates for \
+ prop in self.prop._reverse_property):
+ return
uow.register_preprocessor(self, False)
@@ -797,14 +802,14 @@ class DetectKeySwitch(DependencyProcessor):
if switchers:
# if primary key values have actually changed somewhere, perform
# a linear search through the UOW in search of a parent.
- # note that this handler isn't used if the many-to-one
- # relationship has a backref.
for state in uowcommit.session.identity_map.all_states():
if not issubclass(state.class_, self.parent.class_):
continue
dict_ = state.dict
- related = dict_.get(self.key)
- if related is not None:
+ related = state.get_impl(self.key).get(state, dict_,
+ passive=self.passive_updates)
+ if related is not attributes.PASSIVE_NO_RESULT and \
+ related is not None:
related_state = attributes.instance_state(dict_[self.key])
if related_state in switchers:
uowcommit.register_object(state,
diff --git a/lib/sqlalchemy/orm/dynamic.py b/lib/sqlalchemy/orm/dynamic.py
index d55838011..c5ddaca40 100644
--- a/lib/sqlalchemy/orm/dynamic.py
+++ b/lib/sqlalchemy/orm/dynamic.py
@@ -112,7 +112,7 @@ class DynamicAttributeImpl(attributes.AttributeImpl):
def set(self, state, dict_, value, initiator,
passive=attributes.PASSIVE_OFF):
- if initiator is self:
+ if initiator and initiator.parent_token is self.parent_token:
return
self._set_iterable(state, dict_, value)
diff --git a/lib/sqlalchemy/orm/evaluator.py b/lib/sqlalchemy/orm/evaluator.py
index 3ee70782d..e3cbffe98 100644
--- a/lib/sqlalchemy/orm/evaluator.py
+++ b/lib/sqlalchemy/orm/evaluator.py
@@ -35,7 +35,8 @@ class EvaluatorCompiler(object):
def visit_column(self, clause):
if 'parentmapper' in clause._annotations:
- key = clause._annotations['parentmapper']._get_col_to_prop(clause).key
+ key = clause._annotations['parentmapper'].\
+ _columntoproperty[clause].key
else:
key = clause.key
get_corresponding_attr = operator.attrgetter(key)
diff --git a/lib/sqlalchemy/orm/exc.py b/lib/sqlalchemy/orm/exc.py
index 3f28a3dd3..8f257bdd5 100644
--- a/lib/sqlalchemy/orm/exc.py
+++ b/lib/sqlalchemy/orm/exc.py
@@ -38,7 +38,7 @@ class FlushError(sa.exc.SQLAlchemyError):
class UnmappedError(sa.exc.InvalidRequestError):
- """TODO"""
+ """Base for exceptions that involve expected mappings not present."""
class DetachedInstanceError(sa.exc.SQLAlchemyError):
"""An attempt to access unloaded attributes on a
diff --git a/lib/sqlalchemy/orm/identity.py b/lib/sqlalchemy/orm/identity.py
index 4650b066f..30c3a06b7 100644
--- a/lib/sqlalchemy/orm/identity.py
+++ b/lib/sqlalchemy/orm/identity.py
@@ -15,7 +15,7 @@ class IdentityMap(dict):
self._mutable_attrs = set()
self._modified = set()
self._wr = weakref.ref(self)
-
+
def replace(self, state):
raise NotImplementedError()
@@ -61,7 +61,7 @@ class IdentityMap(dict):
def has_key(self, key):
return key in self
-
+
def popitem(self):
raise NotImplementedError("IdentityMap uses remove() to remove data")
@@ -81,6 +81,9 @@ class IdentityMap(dict):
raise NotImplementedError("IdentityMap uses remove() to remove data")
class WeakInstanceDict(IdentityMap):
+ def __init__(self):
+ IdentityMap.__init__(self)
+ self._remove_mutex = base_util.threading.Lock()
def __getitem__(self, key):
state = dict.__getitem__(self, key)
@@ -134,8 +137,13 @@ class WeakInstanceDict(IdentityMap):
self.remove(state)
def remove(self, state):
- if dict.pop(self, state.key) is not state:
- raise AssertionError("State %s is not present in this identity map" % state)
+ self._remove_mutex.acquire()
+ try:
+ if dict.pop(self, state.key) is not state:
+ raise AssertionError("State %s is not present in this identity map" % state)
+ finally:
+ self._remove_mutex.release()
+
self._manage_removed_state(state)
def discard(self, state):
@@ -153,43 +161,56 @@ class WeakInstanceDict(IdentityMap):
if o is None:
return default
return o
-
- # Py2K
+
+
def items(self):
+ # Py2K
return list(self.iteritems())
-
+
def iteritems(self):
- for state in dict.itervalues(self):
# end Py2K
- # Py3K
- #def items(self):
- # for state in dict.values(self):
- value = state.obj()
- if value is not None:
- yield state.key, value
+ self._remove_mutex.acquire()
+ try:
+ result = []
+ for state in dict.values(self):
+ value = state.obj()
+ if value is not None:
+ result.append((state.key, value))
- # Py2K
+ return iter(result)
+ finally:
+ self._remove_mutex.release()
+
def values(self):
+ # Py2K
return list(self.itervalues())
def itervalues(self):
- for state in dict.itervalues(self):
# end Py2K
- # Py3K
- #def values(self):
- # for state in dict.values(self):
- instance = state.obj()
- if instance is not None:
- yield instance
+ self._remove_mutex.acquire()
+ try:
+ result = []
+ for state in dict.values(self):
+ value = state.obj()
+ if value is not None:
+ result.append(value)
+ return iter(result)
+ finally:
+ self._remove_mutex.release()
+
def all_states(self):
- # Py3K
- # return list(dict.values(self))
+ self._remove_mutex.acquire()
+ try:
+ # Py3K
+ # return list(dict.values(self))
- # Py2K
- return dict.values(self)
- # end Py2K
-
+ # Py2K
+ return dict.values(self)
+ # end Py2K
+ finally:
+ self._remove_mutex.release()
+
def prune(self):
return 0
diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py
index edd0558ac..0a65c8a44 100644
--- a/lib/sqlalchemy/orm/interfaces.py
+++ b/lib/sqlalchemy/orm/interfaces.py
@@ -51,7 +51,7 @@ MANYTOMANY = util.symbol('MANYTOMANY')
class MapperExtension(object):
"""Base implementation for customizing ``Mapper`` behavior.
-
+
New extension classes subclass ``MapperExtension`` and are specified
using the ``extension`` mapper() argument, which is a single
``MapperExtension`` or a list of such. A single mapper
@@ -74,8 +74,9 @@ class MapperExtension(object):
when this symbol is returned. Like EXT_CONTINUE, it also
has additional significance in some cases that a default
mapper activity will not be performed.
-
+
"""
+
def instrument_class(self, mapper, class_):
"""Receive a class when the mapper is first constructed, and has
applied instrumentation to the mapped class.
@@ -185,7 +186,7 @@ class MapperExtension(object):
\**flags
extra information about the row, same as criterion in
``create_row_processor()`` method of
- :class:`~sqlalchemy.orm.interfaces.MapperProperty`
+ :class:`~sqlalchemy.orm.interfaces.MapperProperty`
"""
return EXT_CONTINUE
@@ -324,10 +325,10 @@ class MapperExtension(object):
def after_delete(self, mapper, connection, instance):
"""Receive an object instance after that instance is deleted.
-
- The return value is only significant within the ``MapperExtension``
+
+ The return value is only significant within the ``MapperExtension``
chain; the parent mapper's behavior isn't modified by this method.
-
+
"""
return EXT_CONTINUE
@@ -552,11 +553,29 @@ class MapperProperty(object):
return operator(self.comparator, value)
class PropComparator(expression.ColumnOperators):
- """defines comparison operations for MapperProperty objects.
+ """Defines comparison operations for MapperProperty objects.
+
+ User-defined subclasses of :class:`.PropComparator` may be created. The
+ built-in Python comparison and math operator methods, such as
+ ``__eq__()``, ``__lt__()``, ``__add__()``, can be overridden to provide
+ new operator behaivor. The custom :class:`.PropComparator` is passed to
+ the mapper property via the ``comparator_factory`` argument. In each case,
+ the appropriate subclass of :class:`.PropComparator` should be used::
+
+ from sqlalchemy.orm.properties import \\
+ ColumnProperty,\\
+ CompositeProperty,\\
+ RelationshipProperty
- PropComparator instances should also define an accessor 'property'
- which returns the MapperProperty associated with this
- PropComparator.
+ class MyColumnComparator(ColumnProperty.Comparator):
+ pass
+
+ class MyCompositeComparator(CompositeProperty.Comparator):
+ pass
+
+ class MyRelationshipComparator(RelationshipProperty.Comparator):
+ pass
+
"""
def __init__(self, prop, mapper, adapter=None):
diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py
index c3e4b042e..49f5d2190 100644
--- a/lib/sqlalchemy/orm/mapper.py
+++ b/lib/sqlalchemy/orm/mapper.py
@@ -30,6 +30,7 @@ from sqlalchemy.orm.util import (
ExtensionCarrier, _INSTRUMENTOR, _class_to_mapper,
_state_mapper, class_mapper, instance_str, state_str,
)
+import sys
__all__ = (
'Mapper',
@@ -43,6 +44,7 @@ _new_mappers = False
_already_compiling = False
_none_set = frozenset([None])
+_memoized_compiled_property = util.group_expirable_memoized_property()
# a list of MapperExtensions that will be installed in all mappers by default
global_extensions = []
@@ -102,7 +104,7 @@ class Mapper(object):
"""Construct a new mapper.
Mappers are normally constructed via the
- :func:`~sqlalchemy.orm.mapper` function. See for details.
+ :func:`~sqlalchemy.orm.mapper` function. See for details.
"""
@@ -192,8 +194,14 @@ class Mapper(object):
else:
self.polymorphic_map = _polymorphic_map
- self.include_properties = include_properties
- self.exclude_properties = exclude_properties
+ if include_properties is not None:
+ self.include_properties = util.to_set(include_properties)
+ else:
+ self.include_properties = None
+ if exclude_properties:
+ self.exclude_properties = util.to_set(exclude_properties)
+ else:
+ self.exclude_properties = None
self.compiled = False
@@ -210,6 +218,7 @@ class Mapper(object):
global _new_mappers
_new_mappers = True
self._log("constructed")
+ self._expire_memoizations()
finally:
_COMPILE_MUTEX.release()
@@ -274,11 +283,6 @@ class Mapper(object):
self.version_id_col = self.inherits.version_id_col
self.version_id_generator = self.inherits.version_id_generator
- for mapper in self.iterate_to_root():
- util.reset_memoized(mapper, '_equivalent_columns')
- util.reset_memoized(mapper, '_sorted_tables')
- util.reset_memoized(mapper, '_compiled_cache')
-
if self.order_by is False and \
not self.concrete and \
self.inherits.order_by is not False:
@@ -471,7 +475,7 @@ class Mapper(object):
for col in self._columntoproperty
if not hasattr(col, 'table') or
col.table not in self._cols_by_table)
-
+
# if explicit PK argument sent, add those columns to the
# primary key mappings
if self.primary_key_argument:
@@ -479,13 +483,14 @@ class Mapper(object):
if k.table not in self._pks_by_table:
self._pks_by_table[k.table] = util.OrderedSet()
self._pks_by_table[k.table].add(k)
-
- if self.mapped_table not in self._pks_by_table or \
- len(self._pks_by_table[self.mapped_table]) == 0:
- raise sa_exc.ArgumentError(
- "Mapper %s could not assemble any primary "
- "key columns for mapped table '%s'" %
- (self, self.mapped_table.description))
+
+ # otherwise, see that we got a full PK for the mapped table
+ elif self.mapped_table not in self._pks_by_table or \
+ len(self._pks_by_table[self.mapped_table]) == 0:
+ raise sa_exc.ArgumentError(
+ "Mapper %s could not assemble any primary "
+ "key columns for mapped table '%s'" %
+ (self, self.mapped_table.description))
if self.inherits and \
not self.concrete and \
@@ -526,7 +531,7 @@ class Mapper(object):
# table columns mapped to lists of MapperProperty objects
# using a list allows a single column to be defined as
# populating multiple object attributes
- self._columntoproperty = util.column_dict()
+ self._columntoproperty = _ColumnMapping(self)
# load custom properties
if self._init_properties:
@@ -537,7 +542,7 @@ class Mapper(object):
if self.inherits:
for key, prop in self.inherits._props.iteritems():
if key not in self._props and \
- not self._should_exclude(key, key, local=False):
+ not self._should_exclude(key, key, local=False, column=None):
self._adapt_inherited_property(key, prop, False)
# create properties for each column in the mapped table,
@@ -550,7 +555,8 @@ class Mapper(object):
if self._should_exclude(
column.key, column_key,
- local=self.local_table.c.contains_column(column)
+ local=self.local_table.c.contains_column(column),
+ column=column
):
continue
@@ -583,7 +589,7 @@ class Mapper(object):
% col.description)
else:
instrument = True
- if self._should_exclude(col.key, col.key, local=False):
+ if self._should_exclude(col.key, col.key, local=False, column=col):
raise sa_exc.InvalidRequestError(
"Cannot exclude or override the discriminator column %r" %
col.key)
@@ -625,8 +631,18 @@ class Mapper(object):
# existing ColumnProperty from an inheriting mapper.
# make a copy and append our column to it
prop = prop.copy()
+ else:
+ util.warn(
+ "Implicitly combining column %s with column "
+ "%s under attribute '%s'. This usage will be "
+ "prohibited in 0.7. Please configure one "
+ "or more attributes for these same-named columns "
+ "explicitly."
+ % (prop.columns[-1], column, key))
+
prop.columns.append(column)
self._log("appending to existing ColumnProperty %s" % (key))
+
elif prop is None or isinstance(prop, ConcreteInheritedProperty):
mapped_column = []
for c in columns:
@@ -772,12 +788,13 @@ class Mapper(object):
# the order of mapper compilation
for mapper in list(_mapper_registry):
if getattr(mapper, '_compile_failed', False):
- raise sa_exc.InvalidRequestError(
- "One or more mappers failed to compile. "
- "Exception was probably "
- "suppressed within a hasattr() call. "
- "Message was: %s" %
- mapper._compile_failed)
+ e = sa_exc.InvalidRequestError(
+ "One or more mappers failed to initialize - "
+ "can't proceed with initialization of other "
+ "mappers. Original exception was: %s"
+ % mapper._compile_failed)
+ e._compile_failed = mapper._compile_failed
+ raise e
if not mapper.compiled:
mapper._post_configure_properties()
@@ -786,11 +803,12 @@ class Mapper(object):
finally:
_already_compiling = False
except:
- import sys
exc = sys.exc_info()[1]
- self._compile_failed = exc
+ if not hasattr(exc, '_compile_failed'):
+ self._compile_failed = exc
raise
finally:
+ self._expire_memoizations()
_COMPILE_MUTEX.release()
def _post_configure_properties(self):
@@ -835,7 +853,11 @@ class Mapper(object):
"""
self._init_properties[key] = prop
self._configure_property(key, prop, init=self.compiled)
+ self._expire_memoizations()
+ def _expire_memoizations(self):
+ for mapper in self.iterate_to_root():
+ _memoized_compiled_property.expire_instance(mapper)
def _log(self, msg, *args):
self.logger.info(
@@ -911,7 +933,19 @@ class Mapper(object):
"Mapper '%s' has no property '%s'" % (self, key))
else:
return None
+
+ @util.deprecated('0.6.4',
+ 'Call to deprecated function mapper._get_col_to_pr'
+ 'op(). Use mapper.get_property_by_column()')
+ def _get_col_to_prop(self, col):
+ return self._columntoproperty[col]
+
+ def get_property_by_column(self, column):
+ """Given a :class:`.Column` object, return the
+ :class:`.MapperProperty` which maps this column."""
+ return self._columntoproperty[column]
+
@property
def iterate_properties(self):
"""return an iterator of all MapperProperty objects."""
@@ -928,7 +962,7 @@ class Mapper(object):
"""
if spec == '*':
- mappers = list(self.polymorphic_iterator())
+ mappers = list(self.self_and_descendants)
elif spec:
mappers = [_class_to_mapper(m) for m in util.to_list(spec)]
for m in mappers:
@@ -967,7 +1001,7 @@ class Mapper(object):
return from_obj
- @property
+ @_memoized_compiled_property
def _single_table_criterion(self):
if self.single and \
self.inherits and \
@@ -975,18 +1009,17 @@ class Mapper(object):
self.polymorphic_identity is not None:
return self.polymorphic_on.in_(
m.polymorphic_identity
- for m in self.polymorphic_iterator())
+ for m in self.self_and_descendants)
else:
return None
-
-
- @util.memoized_property
+
+ @_memoized_compiled_property
def _with_polymorphic_mappers(self):
if not self.with_polymorphic:
return [self]
return self._mappers_from_spec(*self.with_polymorphic)
- @util.memoized_property
+ @_memoized_compiled_property
def _with_polymorphic_selectable(self):
if not self.with_polymorphic:
return self.mapped_table
@@ -1011,6 +1044,11 @@ class Mapper(object):
else:
return mappers, self._selectable_from_mappers(mappers)
+ @_memoized_compiled_property
+ def _polymorphic_properties(self):
+ return tuple(self._iterate_polymorphic_properties(
+ self._with_polymorphic_mappers))
+
def _iterate_polymorphic_properties(self, mappers=None):
"""Return an iterator of MapperProperty objects which will render into
a SELECT."""
@@ -1042,7 +1080,7 @@ class Mapper(object):
"provided by the get_property() and iterate_properties "
"accessors.")
- @util.memoized_property
+ @_memoized_compiled_property
def _get_clause(self):
"""create a "get clause" based on the primary key. this is used
by query.get() and many-to-one lazyloads to load this item
@@ -1054,7 +1092,7 @@ class Mapper(object):
return sql.and_(*[k==v for (k, v) in params]), \
util.column_dict(params)
- @util.memoized_property
+ @_memoized_compiled_property
def _equivalent_columns(self):
"""Create a map of all *equivalent* columns, based on
the determination of column pairs that are equated to
@@ -1086,7 +1124,7 @@ class Mapper(object):
result[binary.right].add(binary.left)
else:
result[binary.right] = util.column_set((binary.left,))
- for mapper in self.base_mapper.polymorphic_iterator():
+ for mapper in self.base_mapper.self_and_descendants:
if mapper.inherit_condition is not None:
visitors.traverse(
mapper.inherit_condition, {},
@@ -1099,7 +1137,7 @@ class Mapper(object):
(MapperProperty, attributes.InstrumentedAttribute)) and \
hasattr(obj, '__get__')
- def _should_exclude(self, name, assigned_name, local):
+ def _should_exclude(self, name, assigned_name, local, column):
"""determine whether a particular property should be implicitly
present on the class.
@@ -1121,13 +1159,17 @@ class Mapper(object):
getattr(self.class_, assigned_name)):
return True
- if (self.include_properties is not None and
- name not in self.include_properties):
+ if self.include_properties is not None and \
+ name not in self.include_properties and \
+ (column is None or column not in self.include_properties):
self._log("not including property %s" % (name))
return True
- if (self.exclude_properties is not None and
- name in self.exclude_properties):
+ if self.exclude_properties is not None and \
+ (
+ name in self.exclude_properties or \
+ (column is not None and column in self.exclude_properties)
+ ):
self._log("excluding property %s" % (name))
return True
@@ -1160,6 +1202,22 @@ class Mapper(object):
yield m
m = m.inherits
+ @_memoized_compiled_property
+ def self_and_descendants(self):
+ """The collection including this mapper and all descendant mappers.
+
+ This includes not just the immediately inheriting mappers but
+ all their inheriting mappers as well.
+
+ """
+ descendants = []
+ stack = deque([self])
+ while stack:
+ item = stack.popleft()
+ descendants.append(item)
+ stack.extend(item._inheriting_mappers)
+ return tuple(descendants)
+
def polymorphic_iterator(self):
"""Iterate through the collection including this mapper and
all descendant mappers.
@@ -1169,14 +1227,9 @@ class Mapper(object):
To iterate through an entire hierarchy, use
``mapper.base_mapper.polymorphic_iterator()``.
-
+
"""
- stack = deque([self])
- while stack:
- item = stack.popleft()
- yield item
- stack.extend(item._inheriting_mappers)
-
+ return iter(self.self_and_descendants)
def primary_mapper(self):
"""Return the primary mapper corresponding to this mapper's class key
@@ -1240,33 +1293,15 @@ class Mapper(object):
def _primary_key_from_state(self, state):
dict_ = state.dict
- return [
- self._get_state_attr_by_column(state, dict_, column) for
- column in self.primary_key]
-
- def _get_col_to_prop(self, column):
- try:
- return self._columntoproperty[column]
- except KeyError:
- prop = self._props.get(column.key, None)
- if prop:
- raise orm_exc.UnmappedColumnError(
- "Column '%s.%s' is not available, due to "
- "conflicting property '%s':%r" %
- (column.table.name, column.name,
- column.key, prop))
- else:
- raise orm_exc.UnmappedColumnError(
- "No column %s is configured on mapper %s..." %
- (column, self))
+ return [self._get_state_attr_by_column(state, dict_, column) for
+ column in self.primary_key]
# TODO: improve names?
- def _get_state_attr_by_column(self, state, dict_, column):
- return self._get_col_to_prop(column)._getattr(state, dict_, column)
+ def _get_state_attr_by_column(self, state, dict_, column, passive=False):
+ return self._columntoproperty[column]._getattr(state, dict_, column, passive=passive)
def _set_state_attr_by_column(self, state, dict_, column, value):
- return self._get_col_to_prop(column).\
- _setattr(state, dict_, value, column)
+ return self._columntoproperty[column]._setattr(state, dict_, value, column)
def _get_committed_attr_by_column(self, obj, column):
state = attributes.instance_state(obj)
@@ -1275,9 +1310,8 @@ class Mapper(object):
def _get_committed_state_attr_by_column(self, state, dict_, column,
passive=False):
- return self._get_col_to_prop(column).\
- _getcommitted(state, dict_,
- column, passive=passive)
+ return self._columntoproperty[column]._getcommitted(
+ state, dict_, column, passive=passive)
def _optimized_get_statement(self, state, attribute_names):
"""assemble a WHERE clause which retrieves a given state by primary
@@ -1354,11 +1388,11 @@ class Mapper(object):
"""Iterate each element and its mapper in an object graph,
for all relationships that meet the given cascade rule.
- ``type\_``:
+ :param type_:
The name of the cascade rule (i.e. save-update, delete,
etc.)
- ``state``:
+ :param state:
The lead InstanceState. child items will be processed per
the relationships defined for this object's mapper.
@@ -1387,14 +1421,14 @@ class Mapper(object):
except StopIteration:
visitables.pop()
- @util.memoized_property
+ @_memoized_compiled_property
def _compiled_cache(self):
return util.LRUCache(self._compiled_cache_size)
- @util.memoized_property
+ @_memoized_compiled_property
def _sorted_tables(self):
table_to_mapper = {}
- for mapper in self.base_mapper.polymorphic_iterator():
+ for mapper in self.base_mapper.self_and_descendants:
for t in mapper.tables:
table_to_mapper[t] = mapper
@@ -2341,6 +2375,11 @@ def validates(*names):
can then raise validation exceptions to halt the process from continuing,
or can modify or replace the value before proceeding. The function
should otherwise return the given value.
+
+ Note that a validator for a collection **cannot** issue a load of that
+ collection within the validation routine - this usage raises
+ an assertion to avoid recursion overflows. This is a reentrant
+ condition which is not supported.
"""
def wrap(fn):
@@ -2411,7 +2450,8 @@ def _load_scalar_attributes(state, attribute_names):
# this codepath is rare - only valid when inside a flush, and the
# object is becoming persistent but hasn't yet been assigned an identity_key.
# check here to ensure we have the attrs we need.
- pk_attrs = [mapper._get_col_to_prop(col).key for col in mapper.primary_key]
+ pk_attrs = [mapper._columntoproperty[col].key
+ for col in mapper.primary_key]
if state.expired_attributes.intersection(pk_attrs):
raise sa_exc.InvalidRequestError("Instance %s cannot be refreshed - it's not "
" persistent and does not "
@@ -2438,3 +2478,21 @@ def _load_scalar_attributes(state, attribute_names):
raise orm_exc.ObjectDeletedError(
"Instance '%s' has been deleted." %
state_str(state))
+
+
+class _ColumnMapping(util.py25_dict):
+ """Error reporting helper for mapper._columntoproperty."""
+
+ def __init__(self, mapper):
+ self.mapper = mapper
+
+ def __missing__(self, column):
+ prop = self.mapper._props.get(column)
+ if prop:
+ raise orm_exc.UnmappedColumnError(
+ "Column '%s.%s' is not available, due to "
+ "conflicting property '%s':%r" % (
+ column.table.name, column.name, column.key, prop))
+ raise orm_exc.UnmappedColumnError(
+ "No column %s is configured on mapper %s..." %
+ (column, self.mapper))
diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py
index 7e19d7b16..80443a7f3 100644
--- a/lib/sqlalchemy/orm/properties.py
+++ b/lib/sqlalchemy/orm/properties.py
@@ -60,7 +60,17 @@ class ColumnProperty(StrategizedProperty):
self.__class__.Comparator)
self.descriptor = kwargs.pop('descriptor', None)
self.extension = kwargs.pop('extension', None)
- self.doc = kwargs.pop('doc', getattr(columns[0], 'doc', None))
+
+ if 'doc' in kwargs:
+ self.doc = kwargs.pop('doc')
+ else:
+ for col in reversed(self.columns):
+ doc = getattr(col, 'doc', None)
+ if doc is not None:
+ self.doc = doc
+ break
+ else:
+ self.doc = None
if kwargs:
raise TypeError(
@@ -106,8 +116,8 @@ class ColumnProperty(StrategizedProperty):
group=self.group,
*self.columns)
- def _getattr(self, state, dict_, column):
- return state.get_impl(self.key).get(state, dict_)
+ def _getattr(self, state, dict_, column, passive=False):
+ return state.get_impl(self.key).get(state, dict_, passive=passive)
def _getcommitted(self, state, dict_, column, passive=False):
return state.get_impl(self.key).\
@@ -181,8 +191,8 @@ class CompositeProperty(ColumnProperty):
# which issues assertions that do not apply to CompositeColumnProperty
super(ColumnProperty, self).do_init()
- def _getattr(self, state, dict_, column):
- obj = state.get_impl(self.key).get(state, dict_)
+ def _getattr(self, state, dict_, column, passive=False):
+ obj = state.get_impl(self.key).get(state, dict_, passive=passive)
return self.get_col_value(column, obj)
def _getcommitted(self, state, dict_, column, passive=False):
@@ -434,6 +444,7 @@ class RelationshipProperty(StrategizedProperty):
comparator_factory=None,
single_parent=False, innerjoin=False,
doc=None,
+ load_on_pending=False,
strategy_class=None, _local_remote_pairs=None, query_class=None):
self.uselist = uselist
@@ -458,6 +469,7 @@ class RelationshipProperty(StrategizedProperty):
self.join_depth = join_depth
self.local_remote_pairs = _local_remote_pairs
self.extension = extension
+ self.load_on_pending = load_on_pending
self.comparator_factory = comparator_factory or \
RelationshipProperty.Comparator
self.comparator = self.comparator_factory(self, None)
@@ -710,7 +722,9 @@ class RelationshipProperty(StrategizedProperty):
self.prop.parent.compile()
return self.prop
- def compare(self, op, value, value_is_parent=False, alias_secondary=True):
+ def compare(self, op, value,
+ value_is_parent=False,
+ alias_secondary=True):
if op == operators.eq:
if value is None:
if self.uselist:
@@ -720,14 +734,15 @@ class RelationshipProperty(StrategizedProperty):
value_is_parent=value_is_parent,
alias_secondary=alias_secondary)
else:
- return self._optimized_compare(value,
- value_is_parent=value_is_parent,
- alias_secondary=alias_secondary)
+ return self._optimized_compare(value,
+ value_is_parent=value_is_parent,
+ alias_secondary=alias_secondary)
else:
return op(self.comparator, value)
def _optimized_compare(self, value, value_is_parent=False,
- adapt_source=None, alias_secondary=True):
+ adapt_source=None,
+ alias_secondary=True):
if value is not None:
value = attributes.instance_state(value)
return self._get_strategy(strategies.LazyLoader).lazy_clause(value,
@@ -1197,6 +1212,10 @@ class RelationshipProperty(StrategizedProperty):
'when single_parent is not set. Set '
'single_parent=True on the relationship().'
% self)
+ if self.direction is MANYTOONE and self.passive_deletes:
+ util.warn("On %s, 'passive_deletes' is normally configured "
+ "on one-to-many, one-to-one, many-to-many relationships only."
+ % self)
def _determine_local_remote_pairs(self):
if not self.local_remote_pairs:
diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py
index 18ffd108a..b22a10b55 100644
--- a/lib/sqlalchemy/orm/query.py
+++ b/lib/sqlalchemy/orm/query.py
@@ -32,7 +32,7 @@ from sqlalchemy.orm import (
from sqlalchemy.orm.util import (
AliasedClass, ORMAdapter, _entity_descriptor, _entity_info,
_is_aliased_class, _is_mapped_class, _orm_columns, _orm_selectable,
- join as orm_join,
+ join as orm_join,with_parent
)
@@ -98,6 +98,7 @@ class Query(object):
_attributes = util.frozendict()
_with_options = ()
_with_hints = ()
+ _enable_single_crit = True
def __init__(self, entities, session=None):
self.session = session
@@ -633,44 +634,41 @@ class Query(object):
@_generative()
def populate_existing(self):
- """Return a Query that will refresh all instances loaded.
-
- This includes all entities accessed from the database, including
- secondary entities, eagerly-loaded collection items.
-
- All changes present on entities which are already present in the
- session will be reset and the entities will all be marked "clean".
-
- An alternative to populate_existing() is to expire the Session
- fully using session.expire_all().
+ """Return a :class:`Query` that will expire and refresh all instances
+ as they are loaded, or reused from the current :class:`.Session`.
+
+ :meth:`.populate_existing` does not improve behavior when
+ the ORM is used normally - the :class:`.Session` object's usual
+ behavior of maintaining a transaction and expiring all attributes
+ after rollback or commit handles object state automatically.
+ This method is not intended for general use.
"""
self._populate_existing = True
def with_parent(self, instance, property=None):
- """Add a join criterion corresponding to a relationship to the given
- parent instance.
-
- instance
- a persistent or detached instance which is related to class
- represented by this query.
-
- property
- string name of the property which relates this query's class to the
- instance. if None, the method will attempt to find a suitable
- property.
-
- Currently, this method only works with immediate parent relationships,
- but in the future may be enhanced to work across a chain of parent
- mappers.
-
+ """Add filtering criterion that relates the given instance
+ to a child object or collection, using its attribute state
+ as well as an established :func:`.relationship()`
+ configuration.
+
+ The method uses the :func:`.with_parent` function to generate
+ the clause, the result of which is passed to :meth:`.Query.filter`.
+
+ Parameters are the same as :func:`.with_parent`, with the exception
+ that the given property can be None, in which case a search is
+ performed against this :class:`.Query` object's target mapper.
+
"""
- from sqlalchemy.orm import properties
- mapper = object_mapper(instance)
+
if property is None:
+ from sqlalchemy.orm import properties
+ mapper = object_mapper(instance)
+
for prop in mapper.iterate_properties:
if isinstance(prop, properties.PropertyLoader) and \
prop.mapper is self._mapper_zero():
+ property = prop
break
else:
raise sa_exc.InvalidRequestError(
@@ -680,11 +678,8 @@ class Query(object):
self._mapper_zero().class_.__name__,
instance.__class__.__name__)
)
- else:
- prop = mapper.get_property(property, resolve_synonyms=True)
- return self.filter(prop.compare(
- operators.eq,
- instance, value_is_parent=True))
+
+ return self.filter(with_parent(instance, property))
@_generative()
def add_entity(self, entity, alias=None):
@@ -707,12 +702,17 @@ class Query(object):
"""
fromclause = self.with_labels().enable_eagerloads(False).\
+ _enable_single_crit(False).\
statement.correlate(None)
q = self._from_selectable(fromclause)
if entities:
q._set_entities(entities)
return q
-
+
+ @_generative()
+ def _enable_single_crit(self, val):
+ self._enable_single_crit = val
+
@_generative()
def _from_selectable(self, fromclause):
for attr in ('_statement', '_criterion', '_order_by', '_group_by',
@@ -779,8 +779,13 @@ class Query(object):
def options(self, *args):
"""Return a new Query object, applying the given list of
- MapperOptions.
-
+ mapper options.
+
+ Most supplied options regard changing how column- and
+ relationship-mapped attributes are loaded. See the sections
+ :ref:`deferred` and :ref:`loading_toplevel` for reference
+ documentation.
+
"""
return self._options(False, *args)
@@ -1937,7 +1942,8 @@ class Query(object):
else:
from_obj = context.froms
- self._adjust_for_single_inheritance(context)
+ if self._enable_single_crit:
+ self._adjust_for_single_inheritance(context)
whereclause = context.whereclause
@@ -2274,7 +2280,8 @@ class Query(object):
# i.e. when each _MappedEntity has its own FROM
froms = context.froms
- self._adjust_for_single_inheritance(context)
+ if self._enable_single_crit:
+ self._adjust_for_single_inheritance(context)
if not context.primary_columns:
if self._only_load_props:
@@ -2406,6 +2413,7 @@ class Query(object):
selected from the total results.
"""
+
for entity, (mapper, adapter, s, i, w) in \
self._mapper_adapter_map.iteritems():
single_crit = mapper._single_table_criterion
@@ -2567,8 +2575,12 @@ class _MapperEntity(_QueryEntity):
)
)
- for value in self.mapper._iterate_polymorphic_properties(
- self._with_polymorphic):
+ if self._with_polymorphic:
+ poly_properties = self.mapper._iterate_polymorphic_properties(
+ self._with_polymorphic)
+ else:
+ poly_properties = self.mapper._polymorphic_properties
+ for value in poly_properties:
if query._only_load_props and \
value.key not in query._only_load_props:
continue
diff --git a/lib/sqlalchemy/orm/scoping.py b/lib/sqlalchemy/orm/scoping.py
index af518e407..c1a5fd577 100644
--- a/lib/sqlalchemy/orm/scoping.py
+++ b/lib/sqlalchemy/orm/scoping.py
@@ -22,9 +22,13 @@ class ScopedSession(object):
Usage::
- Session = scoped_session(sessionmaker(autoflush=True))
+ Session = scoped_session(sessionmaker())
- ... use session normally.
+ ... use Session normally.
+
+ The internal registry is accessible as well,
+ and by default is an instance of :class:`.ThreadLocalRegistry`.
+
"""
@@ -89,6 +93,7 @@ class ScopedSession(object):
class when called.
e.g.::
+
Session = scoped_session(sessionmaker())
class MyClass(object):
diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py
index 06d5b89a1..bab98f4fa 100644
--- a/lib/sqlalchemy/orm/session.py
+++ b/lib/sqlalchemy/orm/session.py
@@ -22,6 +22,7 @@ from sqlalchemy.orm.util import (
from sqlalchemy.orm.mapper import Mapper, _none_set
from sqlalchemy.orm.unitofwork import UOWTransaction
from sqlalchemy.orm import identity
+import sys
__all__ = ['Session', 'SessionTransaction', 'SessionExtension']
@@ -206,7 +207,9 @@ class SessionTransaction(object):
single: thread safety; SessionTransaction
"""
-
+
+ _rollback_exception = None
+
def __init__(self, session, parent=None, nested=False):
self.session = session
self._connections = {}
@@ -229,9 +232,21 @@ class SessionTransaction(object):
def _assert_is_active(self):
self._assert_is_open()
if not self._active:
- raise sa_exc.InvalidRequestError(
- "The transaction is inactive due to a rollback in a "
- "subtransaction. Issue rollback() to cancel the transaction.")
+ if self._rollback_exception:
+ raise sa_exc.InvalidRequestError(
+ "This Session's transaction has been rolled back "
+ "due to a previous exception during flush."
+ " To begin a new transaction with this Session, "
+ "first issue Session.rollback()."
+ " Original exception was: %s"
+ % self._rollback_exception
+ )
+ else:
+ raise sa_exc.InvalidRequestError(
+ "This Session's transaction has been rolled back "
+ "by a nested rollback() call. To begin a new "
+ "transaction, issue Session.rollback() first."
+ )
def _assert_is_open(self, error_msg="The transaction is closed"):
if self.session is None:
@@ -288,14 +303,16 @@ class SessionTransaction(object):
assert not self.session._deleted
for s in self.session.identity_map.all_states():
- _expire_state(s, s.dict, None, instance_dict=self.session.identity_map)
+ _expire_state(s, s.dict, None,
+ instance_dict=self.session.identity_map)
def _remove_snapshot(self):
assert self._is_transaction_boundary
if not self.nested and self.session.expire_on_commit:
for s in self.session.identity_map.all_states():
- _expire_state(s, s.dict, None, instance_dict=self.session.identity_map)
+ _expire_state(s, s.dict, None,
+ instance_dict=self.session.identity_map)
def _connection_for_bind(self, bind):
self._assert_is_active()
@@ -379,7 +396,7 @@ class SessionTransaction(object):
self.close()
return self._parent
- def rollback(self):
+ def rollback(self, _capture_exception=False):
self._assert_is_open()
stx = self.session.transaction
@@ -397,6 +414,8 @@ class SessionTransaction(object):
transaction._deactivate()
self.close()
+ if self._parent and _capture_exception:
+ self._parent._rollback_exception = sys.exc_info()[1]
return self._parent
def _rollback_impl(self):
@@ -415,7 +434,8 @@ class SessionTransaction(object):
def close(self):
self.session.transaction = self._parent
if self._parent is None:
- for connection, transaction, autoclose in set(self._connections.values()):
+ for connection, transaction, autoclose in \
+ set(self._connections.values()):
if autoclose:
connection.close()
else:
@@ -446,71 +466,8 @@ class SessionTransaction(object):
class Session(object):
"""Manages persistence operations for ORM-mapped objects.
- The Session is the front end to SQLAlchemy's **Unit of Work**
- implementation. The concept behind Unit of Work is to track modifications
- to a field of objects, and then be able to flush those changes to the
- database in a single operation.
-
- SQLAlchemy's unit of work includes these functions:
-
- * The ability to track in-memory changes on scalar- and collection-based
- object attributes, such that database persistence operations can be
- assembled based on those changes.
-
- * The ability to organize individual SQL queries and population of newly
- generated primary and foreign key-holding attributes during a persist
- operation such that referential integrity is maintained at all times.
-
- * The ability to maintain insert ordering against the order in which new
- instances were added to the session.
-
- * An Identity Map, which is a dictionary keying instances to their unique
- primary key identity. This ensures that only one copy of a particular
- entity is ever present within the session, even if repeated load
- operations for the same entity occur. This allows many parts of an
- application to get a handle to a particular object without any chance of
- modifications going to two different places.
-
- When dealing with instances of mapped classes, an instance may be
- *attached* to a particular Session, else it is *unattached* . An instance
- also may or may not correspond to an actual row in the database. These
- conditions break up into four distinct states:
-
- * *Transient* - an instance that's not in a session, and is not saved to
- the database; i.e. it has no database identity. The only relationship
- such an object has to the ORM is that its class has a ``mapper()``
- associated with it.
-
- * *Pending* - when you ``add()`` a transient instance, it becomes
- pending. It still wasn't actually flushed to the database yet, but it
- will be when the next flush occurs.
-
- * *Persistent* - An instance which is present in the session and has a
- record in the database. You get persistent instances by either flushing
- so that the pending instances become persistent, or by querying the
- database for existing instances (or moving persistent instances from
- other sessions into your local session).
-
- * *Detached* - an instance which has a record in the database, but is not
- in any session. Theres nothing wrong with this, and you can use objects
- normally when they're detached, **except** they will not be able to
- issue any SQL in order to load collections or attributes which are not
- yet loaded, or were marked as "expired".
-
- The session methods which control instance state include ``add()``,
- ``delete()``, ``merge()``, and ``expunge()``.
-
- The Session object is generally **not** threadsafe. A session which is
- set to ``autocommit`` and is only read from may be used by concurrent
- threads if it's acceptable that some object instances may be loaded twice.
-
- The typical pattern to managing Sessions in a multi-threaded environment
- is either to use mutexes to limit concurrent access to one thread at a
- time, or more commonly to establish a unique session for every thread,
- using a threadlocal variable. SQLAlchemy provides a thread-managed
- Session adapter, provided by the :func:`~sqlalchemy.orm.scoped_session`
- function.
-
+ The Session's usage paradigm is described at :ref:`session_toplevel`.
+
"""
public_methods = (
@@ -529,8 +486,9 @@ class Session(object):
query_cls=query.Query):
"""Construct a new Session.
- Arguments to ``Session`` are described using the
- :func:`~sqlalchemy.orm.sessionmaker` function.
+ Arguments to :class:`.Session` are described using the
+ :func:`.sessionmaker` function, which is the
+ typical point of entry.
"""
@@ -1080,6 +1038,14 @@ class Session(object):
if obj is not None:
instance_key = mapper._identity_key_from_state(state)
+
+ if _none_set.issubset(instance_key[1]) and \
+ not mapper.allow_partial_pks or \
+ _none_set.issuperset(instance_key[1]):
+ raise exc.FlushError('Instance %s has a NULL identity '
+ 'key. Check if this flush is occuring at an '
+ 'inappropriate time, such as during a load '
+ 'operation.' % mapperutil.state_str(state))
if state.key is None:
state.key = instance_key
@@ -1505,7 +1471,7 @@ class Session(object):
ext.after_flush(self, flush_context)
transaction.commit()
except:
- transaction.rollback()
+ transaction.rollback(_capture_exception=True)
raise
flush_context.finalize_flush_changes()
diff --git a/lib/sqlalchemy/orm/state.py b/lib/sqlalchemy/orm/state.py
index f6828f5a9..e6502df8c 100644
--- a/lib/sqlalchemy/orm/state.py
+++ b/lib/sqlalchemy/orm/state.py
@@ -333,7 +333,7 @@ class InstanceState(object):
previous = dict_[attr.key]
else:
previous = attr.get(self, dict_)
-
+
if should_copy and previous not in (None, NO_VALUE, NEVER_SET):
previous = attr.copy(previous)
diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py
index 1c4571aed..3e6b6a21f 100644
--- a/lib/sqlalchemy/orm/strategies.py
+++ b/lib/sqlalchemy/orm/strategies.py
@@ -48,7 +48,7 @@ def _register_attribute(strategy, mapper, useobject,
attribute_ext.append(sessionlib.UOWEventHandler(prop.key))
- for m in mapper.polymorphic_iterator():
+ for m in mapper.self_and_descendants:
if prop is m._props.get(prop.key):
attributes.register_attribute_impl(
@@ -118,17 +118,20 @@ class ColumnLoader(LoaderStrategy):
)
def create_row_processor(self, selectcontext, path, mapper, row, adapter):
- key, col = self.key, self.columns[0]
- if adapter:
- col = adapter.columns[col]
-
- if col is not None and col in row:
- def new_execute(state, dict_, row):
- dict_[key] = row[col]
+ key = self.key
+ # look through list of columns represented here
+ # to see which, if any, is present in the row.
+ for col in self.columns:
+ if adapter:
+ col = adapter.columns[col]
+ if col is not None and col in row:
+ def new_execute(state, dict_, row):
+ dict_[key] = row[col]
+ return new_execute, None
else:
def new_execute(state, dict_, row):
state.expire_attribute_pre_commit(dict_, key)
- return new_execute, None
+ return new_execute, None
log.class_logger(ColumnLoader)
@@ -253,8 +256,8 @@ class LoadDeferredColumns(object):
def __init__(self, state, key):
self.state, self.key = state, key
- def __call__(self, **kw):
- if kw.get('passive') is attributes.PASSIVE_NO_FETCH:
+ def __call__(self, passive=False):
+ if passive is attributes.PASSIVE_NO_FETCH:
return attributes.PASSIVE_NO_RESULT
state = self.state
@@ -401,7 +404,8 @@ class LazyLoader(AbstractRelationshipLoader):
)
def lazy_clause(self, state, reverse_direction=False,
- alias_secondary=False, adapt_source=None):
+ alias_secondary=False,
+ adapt_source=None):
if state is None:
return self._lazy_none_clause(
reverse_direction,
@@ -423,18 +427,26 @@ class LazyLoader(AbstractRelationshipLoader):
else:
mapper = self.parent_property.parent
- def visit_bindparam(bindparam):
- if bindparam.key in bind_to_col:
- # use the "committed" (database) version to get
- # query column values
- # also its a deferred value; so that when used
- # by Query, the committed value is used
- # after an autoflush occurs
- o = state.obj() # strong ref
- bindparam.value = \
- lambda: mapper._get_committed_attr_by_column(
- o, bind_to_col[bindparam.key])
-
+ o = state.obj() # strong ref
+ dict_ = attributes.instance_dict(o)
+
+ # use the "committed state" only if we're in a flush
+ # for this state.
+
+ sess = sessionlib._state_session(state)
+ if sess is not None and sess._flushing:
+ def visit_bindparam(bindparam):
+ if bindparam.key in bind_to_col:
+ bindparam.value = \
+ lambda: mapper._get_committed_state_attr_by_column(
+ state, dict_, bind_to_col[bindparam.key])
+ else:
+ def visit_bindparam(bindparam):
+ if bindparam.key in bind_to_col:
+ bindparam.value = lambda: mapper._get_state_attr_by_column(
+ state, dict_, bind_to_col[bindparam.key])
+
+
if self.parent_property.secondary is not None and alias_secondary:
criterion = sql_util.ClauseAdapter(
self.parent_property.secondary.alias()).\
@@ -442,6 +454,7 @@ class LazyLoader(AbstractRelationshipLoader):
criterion = visitors.cloned_traverse(
criterion, {}, {'bindparam':visit_bindparam})
+
if adapt_source:
criterion = adapt_source(criterion)
return criterion
@@ -465,7 +478,8 @@ class LazyLoader(AbstractRelationshipLoader):
return criterion
def _class_level_loader(self, state):
- if not state.has_identity:
+ if not state.has_identity and \
+ (not self.parent_property.load_on_pending or not state.session_id):
return None
return LoadLazyAttribute(state, self.key)
@@ -555,16 +569,22 @@ class LoadLazyAttribute(object):
def __setstate__(self, state):
self.state, self.key = state
- def __call__(self, **kw):
+ def __call__(self, passive=False):
state = self.state
instance_mapper = mapper._state_mapper(state)
prop = instance_mapper.get_property(self.key)
strategy = prop._get_strategy(LazyLoader)
-
- if kw.get('passive') is attributes.PASSIVE_NO_FETCH and \
- not strategy.use_get:
+ pending = not state.key
+
+ if (
+ passive is attributes.PASSIVE_NO_FETCH and
+ not strategy.use_get
+ ) or (
+ passive is attributes.PASSIVE_ONLY_PERSISTENT and
+ pending
+ ):
return attributes.PASSIVE_NO_RESULT
-
+
if strategy._should_log_debug():
strategy.logger.debug("loading %s",
mapperutil.state_attribute_str(
@@ -580,21 +600,35 @@ class LoadLazyAttribute(object):
q = session.query(prop.mapper)._adapt_all_clauses()
+ # don't autoflush on pending
+ # this would be something that's prominent in the
+ # docs and such
+ if pending:
+ q = q.autoflush(False)
+
if state.load_path:
q = q._with_current_path(state.load_path + (self.key,))
-
+
# if we have a simple primary key load, use mapper.get()
# to possibly save a DB round trip
if strategy.use_get:
ident = []
allnulls = True
+ if session._flushing:
+ get_attr = instance_mapper._get_committed_state_attr_by_column
+ else:
+ get_attr = instance_mapper._get_state_attr_by_column
+
+ # The many-to-one get is intended to be very fast. Note
+ # that we don't want to autoflush() if the get() doesn't
+ # actually have to hit the DB. It is now not necessary
+ # now that we use the pending attribute state.
for primary_key in prop.mapper.primary_key:
- val = instance_mapper.\
- _get_committed_state_attr_by_column(
+ val = get_attr(
state,
state.dict,
strategy._equated_columns[primary_key],
- **kw)
+ passive=passive)
if val is attributes.PASSIVE_NO_RESULT:
return val
allnulls = allnulls and val is None
@@ -607,7 +641,7 @@ class LoadLazyAttribute(object):
q = q._conditional_options(*state.load_options)
key = prop.mapper.identity_key_from_primary_key(ident)
- return q._get(key, ident, **kw)
+ return q._get(key, ident, passive=passive)
if prop.order_by:
@@ -623,8 +657,15 @@ class LoadLazyAttribute(object):
if state.load_options:
q = q._conditional_options(*state.load_options)
+
+ lazy_clause = strategy.lazy_clause(state)
+
+ if pending:
+ bind_values = sql_util.bind_values(lazy_clause)
+ if None in bind_values:
+ return None
- q = q.filter(strategy.lazy_clause(state))
+ q = q.filter(lazy_clause)
result = q.all()
if strategy.uselist:
@@ -693,7 +734,7 @@ class SubqueryLoader(AbstractRelationshipLoader):
leftmost_cols, remote_cols = self._local_remote_columns(leftmost_prop)
leftmost_attr = [
- leftmost_mapper._get_col_to_prop(c).class_attribute
+ leftmost_mapper._columntoproperty[c].class_attribute
for c in leftmost_cols
]
@@ -724,6 +765,7 @@ class SubqueryLoader(AbstractRelationshipLoader):
("orig_query", SubqueryLoader): orig_query,
('subquery_path', None) : subq_path
}
+ q = q._enable_single_crit(False)
# figure out what's being joined. a.k.a. the fun part
to_join = [
@@ -740,7 +782,7 @@ class SubqueryLoader(AbstractRelationshipLoader):
self._local_remote_columns(self.parent_property)
local_attr = [
- getattr(parent_alias, self.parent._get_col_to_prop(c).key)
+ getattr(parent_alias, self.parent._columntoproperty[c].key)
for c in local_cols
]
q = q.order_by(*local_attr)
@@ -822,7 +864,7 @@ class SubqueryLoader(AbstractRelationshipLoader):
local_cols, remote_cols = self._local_remote_columns(self.parent_property)
remote_attr = [
- self.mapper._get_col_to_prop(c).key
+ self.mapper._columntoproperty[c].key
for c in remote_cols]
q = context.attributes[('subquery', path)]
@@ -940,7 +982,7 @@ class EagerLoader(AbstractRelationshipLoader):
("eager_row_processor", reduced_path)
] = clauses
- for value in self.mapper._iterate_polymorphic_properties():
+ for value in self.mapper._polymorphic_properties:
value.setup(
context,
entity,
diff --git a/lib/sqlalchemy/orm/sync.py b/lib/sqlalchemy/orm/sync.py
index 3b2a291bd..05298767d 100644
--- a/lib/sqlalchemy/orm/sync.py
+++ b/lib/sqlalchemy/orm/sync.py
@@ -71,7 +71,7 @@ def source_modified(uowcommit, source, source_mapper, synchronize_pairs):
"""
for l, r in synchronize_pairs:
try:
- prop = source_mapper._get_col_to_prop(l)
+ prop = source_mapper._columntoproperty[l]
except exc.UnmappedColumnError:
_raise_col_to_prop(False, source_mapper, l, None, r)
history = uowcommit.get_attribute_history(source, prop.key, passive=True)
diff --git a/lib/sqlalchemy/orm/unitofwork.py b/lib/sqlalchemy/orm/unitofwork.py
index e10891924..830ac3c0c 100644
--- a/lib/sqlalchemy/orm/unitofwork.py
+++ b/lib/sqlalchemy/orm/unitofwork.py
@@ -219,7 +219,7 @@ class UOWTransaction(object):
def states_for_mapper_hierarchy(self, mapper, isdelete, listonly):
checktup = (isdelete, listonly)
- for mapper in mapper.base_mapper.polymorphic_iterator():
+ for mapper in mapper.base_mapper.self_and_descendants:
for state in self.mappers[mapper]:
if self.states[state] == checktup:
yield state
@@ -318,11 +318,11 @@ class IterateMappersMixin(object):
def _mappers(self, uow):
if self.fromparent:
return iter(
- m for m in self.dependency_processor.parent.polymorphic_iterator()
+ m for m in self.dependency_processor.parent.self_and_descendants
if uow._mapper_for_dep[(m, self.dependency_processor)]
)
else:
- return self.dependency_processor.mapper.polymorphic_iterator()
+ return self.dependency_processor.mapper.self_and_descendants
class Preprocess(IterateMappersMixin):
def __init__(self, dependency_processor, fromparent):
diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py
index 0f4adec00..d68ff4473 100644
--- a/lib/sqlalchemy/orm/util.py
+++ b/lib/sqlalchemy/orm/util.py
@@ -482,18 +482,30 @@ def outerjoin(left, right, onclause=None, join_to_left=True):
return _ORMJoin(left, right, onclause, True, join_to_left)
def with_parent(instance, prop):
- """Return criterion which selects instances with a given parent.
-
- :param instance: a parent instance, which should be persistent
- or detached.
-
- :param property: a class-attached descriptor, MapperProperty or
- string property name
- attached to the parent instance.
-
- :param \**kwargs: all extra keyword arguments are propagated
- to the constructor of Query.
-
+ """Create filtering criterion that relates this query's primary entity
+ to the given related instance, using established :func:`.relationship()`
+ configuration.
+
+ The SQL rendered is the same as that rendered when a lazy loader
+ would fire off from the given parent on that attribute, meaning
+ that the appropriate state is taken from the parent object in
+ Python without the need to render joins to the parent table
+ in the rendered statement.
+
+ As of 0.6.4, this method accepts parent instances in all
+ persistence states, including transient, persistent, and detached.
+ Only the requisite primary key/foreign key attributes need to
+ be populated. Previous versions didn't work with transient
+ instances.
+
+ :param instance:
+ An instance which has some :func:`.relationship`.
+
+ :param property:
+ String property name, or class-bound attribute, which indicates
+ what relationship from the instance should be used to reconcile the
+ parent/child relationship.
+
"""
if isinstance(prop, basestring):
mapper = object_mapper(instance)
@@ -501,7 +513,9 @@ def with_parent(instance, prop):
elif isinstance(prop, attributes.QueryableAttribute):
prop = prop.property
- return prop.compare(operators.eq, instance, value_is_parent=True)
+ return prop.compare(operators.eq,
+ instance,
+ value_is_parent=True)
def _entity_info(entity, compile=True):
diff --git a/lib/sqlalchemy/pool.py b/lib/sqlalchemy/pool.py
index 9d37b1838..c70a41069 100644
--- a/lib/sqlalchemy/pool.py
+++ b/lib/sqlalchemy/pool.py
@@ -132,13 +132,30 @@ class Pool(log.Identified):
self.add_listener(l)
def unique_connection(self):
+ """Produce a DBAPI connection that is not referenced by any
+ thread-local context.
+
+ This method is different from :meth:`.Pool.connect` only if the
+ ``use_threadlocal`` flag has been set to ``True``.
+
+ """
+
return _ConnectionFairy(self).checkout()
def create_connection(self):
+ """Called by subclasses to create a new ConnectionRecord."""
+
return _ConnectionRecord(self)
def recreate(self):
- """Return a new instance with identical creation arguments."""
+ """Return a new :class:`.Pool`, of the same class as this one
+ and configured with identical creation arguments.
+
+ This method is used in conjunection with :meth:`dispose`
+ to close out an entire :class:`.Pool` and create a new one in
+ its place.
+
+ """
raise NotImplementedError()
@@ -149,11 +166,19 @@ class Pool(log.Identified):
remaining open, It is advised to not reuse the pool once dispose()
is called, and to instead use a new pool constructed by the
recreate() method.
+
"""
raise NotImplementedError()
def connect(self):
+ """Return a DBAPI connection from the pool.
+
+ The connection is instrumented such that when its
+ ``close()`` method is called, the connection will be returned to
+ the pool.
+
+ """
if not self._use_threadlocal:
return _ConnectionFairy(self).checkout()
@@ -169,17 +194,33 @@ class Pool(log.Identified):
return agent.checkout()
def return_conn(self, record):
+ """Given a _ConnectionRecord, return it to the :class:`.Pool`.
+
+ This method is called when an instrumented DBAPI connection
+ has its ``close()`` method called.
+
+ """
if self._use_threadlocal and hasattr(self._threadconns, "current"):
del self._threadconns.current
self.do_return_conn(record)
def get(self):
+ """Return a non-instrumented DBAPI connection from this :class:`.Pool`.
+
+ This is called by ConnectionRecord in order to get its DBAPI
+ resource.
+
+ """
return self.do_get()
def do_get(self):
+ """Implementation for :meth:`get`, supplied by subclasses."""
+
raise NotImplementedError()
def do_return_conn(self, conn):
+ """Implementation for :meth:`return_conn`, supplied by subclasses."""
+
raise NotImplementedError()
def status(self):
diff --git a/lib/sqlalchemy/schema.py b/lib/sqlalchemy/schema.py
index 5b5983858..59f9fae7f 100644
--- a/lib/sqlalchemy/schema.py
+++ b/lib/sqlalchemy/schema.py
@@ -118,7 +118,7 @@ class Table(SchemaItem, expression.TableClause):
:param \*args: Additional positional arguments are used primarily
to add the list of :class:`Column` objects contained within this
table. Similar to the style of a CREATE TABLE statement, other
- :class:`SchemaItem` constructs may be added here, including
+ :class:`.SchemaItem` constructs may be added here, including
:class:`PrimaryKeyConstraint`, and :class:`ForeignKeyConstraint`.
:param autoload: Defaults to False: the Columns for this table should
@@ -450,21 +450,51 @@ class Table(SchemaItem, expression.TableClause):
def tometadata(self, metadata, schema=RETAIN_SCHEMA):
- """Return a copy of this ``Table`` associated with a different
- ``MetaData``."""
+ """Return a copy of this :class:`Table` associated with a different
+ :class:`MetaData`.
+
+ E.g.::
+
+ # create two metadata
+ meta1 = MetaData('sqlite:///querytest.db')
+ meta2 = MetaData()
- try:
- if schema is RETAIN_SCHEMA:
- schema = self.schema
- key = _get_table_key(self.name, schema)
+ # load 'users' from the sqlite engine
+ users_table = Table('users', meta1, autoload=True)
+
+ # create the same Table object for the plain metadata
+ users_table_2 = users_table.tometadata(meta2)
+
+ """
+
+ if schema is RETAIN_SCHEMA:
+ schema = self.schema
+ key = _get_table_key(self.name, schema)
+ if key in metadata.tables:
+ util.warn("Table '%s' already exists within the given "
+ "MetaData - not copying." % self.description)
return metadata.tables[key]
- except KeyError:
- args = []
- for c in self.columns:
- args.append(c.copy(schema=schema))
- for c in self.constraints:
- args.append(c.copy(schema=schema))
- return Table(self.name, metadata, schema=schema, *args)
+
+ args = []
+ for c in self.columns:
+ args.append(c.copy(schema=schema))
+ for c in self.constraints:
+ args.append(c.copy(schema=schema))
+ table = Table(
+ self.name, metadata, schema=schema,
+ *args, **self.kwargs
+ )
+ for index in self.indexes:
+ # skip indexes that would be generated
+ # by the 'index' flag on Column
+ if len(index.columns) == 1 and \
+ list(index.columns)[0].index:
+ continue
+ Index(index.name,
+ unique=index.unique,
+ *[table.c[col] for col in index.columns.keys()],
+ **index.kwargs)
+ return table
class Column(SchemaItem, expression.ColumnClause):
"""Represents a column in a database table."""
@@ -512,7 +542,7 @@ class Column(SchemaItem, expression.ColumnClause):
may not function in all cases.
:param \*args: Additional positional arguments include various
- :class:`SchemaItem` derived constructs which will be applied
+ :class:`.SchemaItem` derived constructs which will be applied
as options to the column. These include instances of
:class:`Constraint`, :class:`ForeignKey`, :class:`ColumnDefault`,
and :class:`Sequence`. In some cases an equivalent keyword
@@ -892,6 +922,10 @@ class Column(SchemaItem, expression.ColumnClause):
"""
fk = [ForeignKey(f.column) for f in self.foreign_keys]
+ if name is None and self.name is None:
+ raise exc.InvalidRequestError("Cannot initialize a sub-selectable"
+ " with this Column object until it's 'name' has "
+ "been assigned.")
c = self._constructor(
name or self.name,
self.type,
@@ -1243,7 +1277,23 @@ class DefaultGenerator(SchemaItem):
class ColumnDefault(DefaultGenerator):
"""A plain default value on a column.
- This could correspond to a constant, a callable function, or a SQL clause.
+ This could correspond to a constant, a callable function,
+ or a SQL clause.
+
+ :class:`.ColumnDefault` is generated automatically
+ whenever the ``default``, ``onupdate`` arguments of
+ :class:`.Column` are used. A :class:`.ColumnDefault`
+ can be passed positionally as well.
+
+ For example, the following::
+
+ Column('foo', Integer, default=50)
+
+ Is equivalent to::
+
+ Column('foo', Integer, ColumnDefault(50))
+
+
"""
def __init__(self, arg, **kwargs):
@@ -1374,7 +1424,20 @@ class Sequence(DefaultGenerator):
class FetchedValue(object):
- """A default that takes effect on the database side."""
+ """A marker for a transparent database-side default.
+
+ Use :class:`.FetchedValue` when the database is configured
+ to provide some automatic default for a column.
+
+ E.g.::
+
+ Column('foo', Integer, FetchedValue())
+
+ Would indicate that some trigger or default generator
+ will create a new value for the ``foo`` column during an
+ INSERT.
+
+ """
def __init__(self, for_update=False):
self.for_update = for_update
@@ -1391,7 +1454,26 @@ class FetchedValue(object):
class DefaultClause(FetchedValue):
- """A DDL-specified DEFAULT column value."""
+ """A DDL-specified DEFAULT column value.
+
+ :class:`.DefaultClause` is a :class:`.FetchedValue`
+ that also generates a "DEFAULT" clause when
+ "CREATE TABLE" is emitted.
+
+ :class:`.DefaultClause` is generated automatically
+ whenever the ``server_default``, ``server_onupdate`` arguments of
+ :class:`.Column` are used. A :class:`.DefaultClause`
+ can be passed positionally as well.
+
+ For example, the following::
+
+ Column('foo', Integer, server_default="50")
+
+ Is equivalent to::
+
+ Column('foo', Integer, DefaultClause("50"))
+
+ """
def __init__(self, arg, for_update=False):
util.assert_arg_type(arg, (basestring,
diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py
index 584e43a88..154ede1bf 100644
--- a/lib/sqlalchemy/sql/compiler.py
+++ b/lib/sqlalchemy/sql/compiler.py
@@ -332,6 +332,10 @@ class SQLCompiler(engine.Compiled):
def visit_column(self, column, result_map=None, **kwargs):
name = column.name
+ if name is None:
+ raise exc.CompileError("Cannot compile Column object until "
+ "it's 'name' is assigned.")
+
if not column.is_literal and isinstance(name, sql._generated_label):
name = self._truncated_identifier("colident", name)
diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py
index d184816ab..5df3b8794 100644
--- a/lib/sqlalchemy/sql/expression.py
+++ b/lib/sqlalchemy/sql/expression.py
@@ -1269,10 +1269,15 @@ class ClauseElement(Visitable):
return engine
else:
return None
-
+
+ @util.pending_deprecation('0.7',
+ 'Only SQL expressions which subclass '
+ ':class:`.Executable` may provide the '
+ ':func:`.execute` method.')
def execute(self, *multiparams, **params):
- """Compile and execute this :class:`ClauseElement`."""
-
+ """Compile and execute this :class:`ClauseElement`.
+
+ """
e = self.bind
if e is None:
label = getattr(self, 'description', self.__class__.__name__)
@@ -1284,9 +1289,13 @@ class ClauseElement(Visitable):
raise exc.UnboundExecutionError(msg)
return e._execute_clauseelement(self, multiparams, params)
+ @util.pending_deprecation('0.7',
+ 'Only SQL expressions which subclass '
+ ':class:`.Executable` may provide the '
+ ':func:`.scalar` method.')
def scalar(self, *multiparams, **params):
- """Compile and execute this :class:`ClauseElement`, returning the
- result's scalar representation.
+ """Compile and execute this :class:`ClauseElement`, returning
+ the result's scalar representation.
"""
return self.execute(*multiparams, **params).scalar()
@@ -1843,17 +1852,19 @@ class ColumnElement(ClauseElement, _CompareMixin):
descending selectable.
"""
-
- if name:
- co = ColumnClause(name, selectable, type_=getattr(self,
- 'type', None))
+ if name is None:
+ name = self.anon_label
+ # TODO: may want to change this to anon_label,
+ # or some value that is more useful than the
+ # compiled form of the expression
+ key = str(self)
else:
- name = str(self)
- co = ColumnClause(self.anon_label, selectable,
- type_=getattr(self, 'type', None))
-
+ key = name
+
+ co = ColumnClause(name, selectable, type_=getattr(self,
+ 'type', None))
co.proxies = [self]
- selectable.columns[name] = co
+ selectable.columns[key] = co
return co
def compare(self, other, use_proxies=False, equivalents=None, **kw):
@@ -2401,7 +2412,7 @@ class Executable(_Generative):
COMMIT will be invoked in order to provide its "autocommit" feature.
Typically, all INSERT/UPDATE/DELETE statements as well as
CREATE/DROP statements have autocommit behavior enabled; SELECT
- constructs do not. Use this option when invokving a SELECT or other
+ constructs do not. Use this option when invoking a SELECT or other
specific SQL construct where COMMIT is desired (typically when
calling stored procedures and such).
@@ -2436,6 +2447,27 @@ class Executable(_Generative):
"""
self._execution_options = self._execution_options.union(kw)
+ def execute(self, *multiparams, **params):
+ """Compile and execute this :class:`.Executable`."""
+
+ e = self.bind
+ if e is None:
+ label = getattr(self, 'description', self.__class__.__name__)
+ msg = ('This %s is not bound and does not support direct '
+ 'execution. Supply this statement to a Connection or '
+ 'Engine for execution. Or, assign a bind to the statement '
+ 'or the Metadata of its underlying tables to enable '
+ 'implicit execution via this method.' % label)
+ raise exc.UnboundExecutionError(msg)
+ return e._execute_clauseelement(self, multiparams, params)
+
+ def scalar(self, *multiparams, **params):
+ """Compile and execute this :class:`.Executable`, returning the
+ result's scalar representation.
+
+ """
+ return self.execute(*multiparams, **params).scalar()
+
# legacy, some outside users may be calling this
_Executable = Executable
@@ -3653,8 +3685,7 @@ class _ScalarSelect(_Grouping):
def __init__(self, element):
self.element = element
- cols = list(element.c)
- self.type = cols[0].type
+ self.type = element._scalar_type()
@property
def columns(self):
@@ -3705,7 +3736,10 @@ class CompoundSelect(_SelectBaseMixin, FromClause):
self.selects.append(s.self_group(self))
_SelectBaseMixin.__init__(self, **kwargs)
-
+
+ def _scalar_type(self):
+ return self.selects[0]._scalar_type()
+
def self_group(self, against=None):
return _FromGrouping(self)
@@ -3878,6 +3912,11 @@ class Select(_SelectBaseMixin, FromClause):
return froms
+ def _scalar_type(self):
+ elem = self._raw_columns[0]
+ cols = list(elem._select_iterable)
+ return cols[0].type
+
@property
def froms(self):
"""Return the displayed list of FromClause elements."""
diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py
index c999ab786..bd4f70247 100644
--- a/lib/sqlalchemy/sql/util.py
+++ b/lib/sqlalchemy/sql/util.py
@@ -92,6 +92,31 @@ def find_columns(clause):
visitors.traverse(clause, {}, {'column':cols.add})
return cols
+def bind_values(clause):
+ """Return an ordered list of "bound" values in the given clause.
+
+ E.g.::
+
+ >>> expr = and_(
+ ... table.c.foo==5, table.c.foo==7
+ ... )
+ >>> bind_values(expr)
+ [5, 7]
+ """
+
+ v = []
+ def visit_bindparam(bind):
+ value = bind.value
+
+ # evaluate callables
+ if callable(value):
+ value = value()
+
+ v.append(value)
+
+ visitors.traverse(clause, {}, {'bindparam':visit_bindparam})
+ return v
+
def _quote_ddl_expr(element):
if isinstance(element, basestring):
element = element.replace("'", "''")
diff --git a/lib/sqlalchemy/test/requires.py b/lib/sqlalchemy/test/requires.py
index fefb00330..a9b84e85d 100644
--- a/lib/sqlalchemy/test/requires.py
+++ b/lib/sqlalchemy/test/requires.py
@@ -247,6 +247,12 @@ def sane_rowcount(fn):
skip_if(lambda: not testing.db.dialect.supports_sane_rowcount)
)
+def dbapi_lastrowid(fn):
+ return _chain_decorators_on(
+ fn,
+ fails_on_everything_except('mysql+mysqldb', 'mysql+oursql', 'sqlite+pysqlite')
+ )
+
def sane_multi_rowcount(fn):
return _chain_decorators_on(
fn,
@@ -257,7 +263,7 @@ def reflects_pk_names(fn):
"""Target driver reflects the name of primary key constraints."""
return _chain_decorators_on(
fn,
- fails_on_everything_except('postgresql')
+ fails_on_everything_except('postgresql', 'oracle')
)
def python2(fn):
diff --git a/lib/sqlalchemy/test/testing.py b/lib/sqlalchemy/test/testing.py
index 78cd74d22..41ba3038f 100644
--- a/lib/sqlalchemy/test/testing.py
+++ b/lib/sqlalchemy/test/testing.py
@@ -398,6 +398,7 @@ def uses_deprecated(*messages):
verbiage emitted by the sqlalchemy.util.deprecated decorator.
"""
+
def decorate(fn):
def safe(*args, **kw):
# todo: should probably be strict about this, too
@@ -435,8 +436,6 @@ def resetwarnings():
# warnings.simplefilter('error')
- if sys.version_info < (2, 4):
- warnings.filterwarnings('ignore', category=FutureWarning)
def global_cleanup_assertions():
"""Check things that have to be finalized at the end of a test suite.
diff --git a/lib/sqlalchemy/types.py b/lib/sqlalchemy/types.py
index 777353714..af7ef22e6 100644
--- a/lib/sqlalchemy/types.py
+++ b/lib/sqlalchemy/types.py
@@ -16,9 +16,9 @@ __all__ = [ 'TypeEngine', 'TypeDecorator', 'AbstractType', 'UserDefinedType',
'FLOAT', 'NUMERIC', 'DECIMAL', 'TIMESTAMP', 'DATETIME', 'CLOB',
'BLOB', 'BOOLEAN', 'SMALLINT', 'INTEGER', 'DATE', 'TIME',
'String', 'Integer', 'SmallInteger', 'BigInteger', 'Numeric',
- 'Float', 'DateTime', 'Date', 'Time', 'LargeBinary', 'Binary', 'Boolean',
- 'Unicode', 'MutableType', 'Concatenable', 'UnicodeText',
- 'PickleType', 'Interval', 'type_map', 'Enum' ]
+ 'Float', 'DateTime', 'Date', 'Time', 'LargeBinary', 'Binary',
+ 'Boolean', 'Unicode', 'MutableType', 'Concatenable',
+ 'UnicodeText','PickleType', 'Interval', 'type_map', 'Enum' ]
import inspect
import datetime as dt
@@ -35,18 +35,13 @@ from sqlalchemy import util
from sqlalchemy import processors
import collections
+DefaultDialect = None
NoneType = type(None)
if util.jython:
import array
class AbstractType(Visitable):
- def __init__(self, *args, **kwargs):
- pass
-
- def compile(self, dialect):
- return dialect.type_compiler.process(self)
-
def copy_value(self, value):
return value
@@ -80,12 +75,14 @@ class AbstractType(Visitable):
This allows systems like the ORM to know if a column value can
be considered 'not changed' by comparing the identity of
- objects alone.
-
- Use the :class:`MutableType` mixin or override this method to
- return True in custom types that hold mutable values such as
- ``dict``, ``list`` and custom objects.
-
+ objects alone. Values such as dicts, lists which
+ are serialized into strings are examples of "mutable"
+ column structures.
+
+ When this method is overridden, :meth:`copy_value` should
+ also be supplied. The :class:`.MutableType` mixin
+ is recommended as a helper.
+
"""
return False
@@ -107,7 +104,8 @@ class AbstractType(Visitable):
@util.memoized_property
def _type_affinity(self):
- """Return a rudimental 'affinity' value expressing the general class of type."""
+ """Return a rudimental 'affinity' value expressing the general class
+ of type."""
typ = None
for t in self.__class__.__mro__:
@@ -120,7 +118,8 @@ class AbstractType(Visitable):
def _coerce_compared_value(self, op, value):
_coerced_type = type_map.get(type(value), NULLTYPE)
- if _coerced_type is NULLTYPE or _coerced_type._type_affinity is self._type_affinity:
+ if _coerced_type is NULLTYPE or _coerced_type._type_affinity \
+ is self._type_affinity:
return self
else:
return _coerced_type
@@ -128,6 +127,30 @@ class AbstractType(Visitable):
def _compare_type_affinity(self, other):
return self._type_affinity is other._type_affinity
+ def compile(self, dialect=None):
+ # arg, return value is inconsistent with
+ # ClauseElement.compile()....this is a mistake.
+
+ if not dialect:
+ global DefaultDialect
+ if DefaultDialect is None:
+ from sqlalchemy.engine.default import DefaultDialect
+ dialect = DefaultDialect()
+
+ return dialect.type_compiler.process(self)
+
+ def __str__(self):
+ # Py3K
+ #return unicode(self.compile())
+ # Py2K
+ return unicode(self.compile()).encode('ascii', 'backslashreplace')
+ # end Py2K
+
+ def __init__(self, *args, **kwargs):
+ # supports getargspec of the __init__ method
+ # used by generic __repr__
+ pass
+
def __repr__(self):
return "%s(%s)" % (
self.__class__.__name__,
@@ -142,12 +165,12 @@ class TypeEngine(AbstractType):
return {}
def dialect_impl(self, dialect, **kwargs):
- key = (dialect.__class__, dialect.server_version_info)
-
+ key = dialect.__class__, dialect.server_version_info
try:
return self._impl_dict[key]
except KeyError:
- return self._impl_dict.setdefault(key, dialect.type_descriptor(self))
+ return self._impl_dict.setdefault(key,
+ dialect.type_descriptor(self))
def __getstate__(self):
d = self.__dict__.copy()
@@ -268,11 +291,11 @@ class TypeDecorator(AbstractType):
given; in this case, the "impl" variable can reference
``TypeEngine`` as a placeholder.
- Types that receive a Python type that isn't similar to the
- ultimate type used may want to define the :meth:`TypeDecorator.coerce_compared_value`
- method. This is used to give the expression system a hint
- when coercing Python objects into bind parameters within expressions.
- Consider this expression::
+ Types that receive a Python type that isn't similar to the ultimate type
+ used may want to define the :meth:`TypeDecorator.coerce_compared_value`
+ method. This is used to give the expression system a hint when coercing
+ Python objects into bind parameters within expressions. Consider this
+ expression::
mytable.c.somecol + datetime.date(2009, 5, 15)
@@ -282,10 +305,10 @@ class TypeDecorator(AbstractType):
The expression system does the right thing by not attempting to
coerce the "date()" value into an integer-oriented bind parameter.
- However, in the case of ``TypeDecorator``, we are usually changing
- an incoming Python type to something new - ``TypeDecorator`` by
- default will "coerce" the non-typed side to be the same type as itself.
- Such as below, we define an "epoch" type that stores a date value as an integer::
+ However, in the case of ``TypeDecorator``, we are usually changing an
+ incoming Python type to something new - ``TypeDecorator`` by default will
+ "coerce" the non-typed side to be the same type as itself. Such as below,
+ we define an "epoch" type that stores a date value as an integer::
class MyEpochType(types.TypeDecorator):
impl = types.Integer
@@ -301,10 +324,11 @@ class TypeDecorator(AbstractType):
Our expression of ``somecol + date`` with the above type will coerce the
"date" on the right side to also be treated as ``MyEpochType``.
- This behavior can be overridden via the :meth:`~TypeDecorator.coerce_compared_value`
- method, which returns a type that should be used for the value of the expression.
- Below we set it such that an integer value will be treated as an ``Integer``,
- and any other value is assumed to be a date and will be treated as a ``MyEpochType``::
+ This behavior can be overridden via the
+ :meth:`~TypeDecorator.coerce_compared_value` method, which returns a type
+ that should be used for the value of the expression. Below we set it such
+ that an integer value will be treated as an ``Integer``, and any other
+ value is assumed to be a date and will be treated as a ``MyEpochType``::
def coerce_compared_value(self, op, value):
if isinstance(value, int):
@@ -318,8 +342,10 @@ class TypeDecorator(AbstractType):
def __init__(self, *args, **kwargs):
if not hasattr(self.__class__, 'impl'):
- raise AssertionError("TypeDecorator implementations require a class-level "
- "variable 'impl' which refers to the class of type being decorated")
+ raise AssertionError("TypeDecorator implementations "
+ "require a class-level variable "
+ "'impl' which refers to the class of "
+ "type being decorated")
self.impl = self.__class__.impl(*args, **kwargs)
def adapt(self, cls):
@@ -346,8 +372,10 @@ class TypeDecorator(AbstractType):
typedesc = self.load_dialect_impl(dialect)
tt = self.copy()
if not isinstance(tt, self.__class__):
- raise AssertionError("Type object %s does not properly implement the copy() "
- "method, it must return an object of type %s" % (self, self.__class__))
+ raise AssertionError('Type object %s does not properly '
+ 'implement the copy() method, it must '
+ 'return an object of type %s' % (self,
+ self.__class__))
tt.impl = typedesc
self._impl_dict[key] = tt
return tt
@@ -376,7 +404,8 @@ class TypeDecorator(AbstractType):
return dialect.type_descriptor(self.impl)
def __getattr__(self, key):
- """Proxy all other undefined accessors to the underlying implementation."""
+ """Proxy all other undefined accessors to the underlying
+ implementation."""
return getattr(self.impl, key)
@@ -387,29 +416,36 @@ class TypeDecorator(AbstractType):
raise NotImplementedError()
def bind_processor(self, dialect):
- if self.__class__.process_bind_param.func_code is not TypeDecorator.process_bind_param.func_code:
+ if self.__class__.process_bind_param.func_code \
+ is not TypeDecorator.process_bind_param.func_code:
process_param = self.process_bind_param
impl_processor = self.impl.bind_processor(dialect)
if impl_processor:
def process(value):
return impl_processor(process_param(value, dialect))
+
else:
def process(value):
return process_param(value, dialect)
+
return process
else:
return self.impl.bind_processor(dialect)
def result_processor(self, dialect, coltype):
- if self.__class__.process_result_value.func_code is not TypeDecorator.process_result_value.func_code:
+ if self.__class__.process_result_value.func_code \
+ is not TypeDecorator.process_result_value.func_code:
process_value = self.process_result_value
- impl_processor = self.impl.result_processor(dialect, coltype)
+ impl_processor = self.impl.result_processor(dialect,
+ coltype)
if impl_processor:
def process(value):
return process_value(impl_processor(value), dialect)
+
else:
def process(value):
return process_value(value, dialect)
+
return process
else:
return self.impl.result_processor(dialect, coltype)
@@ -451,6 +487,19 @@ class TypeDecorator(AbstractType):
return self.impl.compare_values(x, y)
def is_mutable(self):
+ """Return True if the target Python type is 'mutable'.
+
+ This allows systems like the ORM to know if a column value can
+ be considered 'not changed' by comparing the identity of
+ objects alone. Values such as dicts, lists which
+ are serialized into strings are examples of "mutable"
+ column structures.
+
+ When this method is overridden, :meth:`copy_value` should
+ also be supplied. The :class:`.MutableType` mixin
+ is recommended as a helper.
+
+ """
return self.impl.is_mutable()
def _adapt_expression(self, op, othertype):
@@ -528,7 +577,12 @@ class MutableType(object):
"""
def is_mutable(self):
- """Return True, mutable."""
+ """Return True if the target Python type is 'mutable'.
+
+ For :class:`.MutableType`, this method is set to
+ return ``True``.
+
+ """
return True
def copy_value(self, value):
@@ -593,10 +647,12 @@ class NullType(TypeEngine):
NullTypeEngine = NullType
class Concatenable(object):
- """A mixin that marks a type as supporting 'concatenation', typically strings."""
+ """A mixin that marks a type as supporting 'concatenation',
+ typically strings."""
def _adapt_expression(self, op, othertype):
- if op is operators.add and issubclass(othertype._type_affinity, (Concatenable, NullType)):
+ if op is operators.add and issubclass(othertype._type_affinity,
+ (Concatenable, NullType)):
return operators.concat_op, self
else:
return op, self
@@ -604,8 +660,9 @@ class Concatenable(object):
class _DateAffinity(object):
"""Mixin date/time specific expression adaptations.
- Rules are implemented within Date,Time,Interval,DateTime, Numeric, Integer.
- Based on http://www.postgresql.org/docs/current/static/functions-datetime.html.
+ Rules are implemented within Date,Time,Interval,DateTime, Numeric,
+ Integer. Based on http://www.postgresql.org/docs/current/static
+ /functions-datetime.html.
"""
@@ -673,11 +730,11 @@ class String(Concatenable, TypeEngine):
set convert_unicode='force'. This will incur significant
performance overhead when fetching unicode result columns.
- :param assert_unicode: Deprecated. A warning is raised in all cases when a non-Unicode
- object is passed when SQLAlchemy would coerce into an encoding
- (note: but **not** when the DBAPI handles unicode objects natively).
- To suppress or raise this warning to an
- error, use the Python warnings filter documented at:
+ :param assert_unicode: Deprecated. A warning is raised in all cases
+ when a non-Unicode object is passed when SQLAlchemy would coerce
+ into an encoding (note: but **not** when the DBAPI handles unicode
+ objects natively). To suppress or raise this warning to an error,
+ use the Python warnings filter documented at:
http://docs.python.org/library/warnings.html
:param unicode_error: Optional, a method to use to handle Unicode
@@ -699,12 +756,14 @@ class String(Concatenable, TypeEngine):
"when unicode_error is set.")
if assert_unicode:
- util.warn_deprecated("assert_unicode is deprecated. "
- "SQLAlchemy emits a warning in all cases where it "
- "would otherwise like to encode a Python unicode object "
- "into a specific encoding but a plain bytestring is received. "
- "This does *not* apply to DBAPIs that coerce Unicode natively."
- )
+ util.warn_deprecated('assert_unicode is deprecated. '
+ 'SQLAlchemy emits a warning in all '
+ 'cases where it would otherwise like '
+ 'to encode a Python unicode object '
+ 'into a specific encoding but a plain '
+ 'bytestring is received. This does '
+ '*not* apply to DBAPIs that coerce '
+ 'Unicode natively.')
self.length = length
self.convert_unicode = convert_unicode
self.unicode_error = unicode_error
@@ -720,7 +779,8 @@ class String(Concatenable, TypeEngine):
def bind_processor(self, dialect):
if self.convert_unicode or dialect.convert_unicode:
- if dialect.supports_unicode_binds and self.convert_unicode != 'force':
+ if dialect.supports_unicode_binds and \
+ self.convert_unicode != 'force':
if self._warn_on_bytestring:
def process(value):
# Py3K
@@ -948,7 +1008,8 @@ class Numeric(_DateAffinity, TypeEngine):
"""
Construct a Numeric.
- :param precision: the numeric precision for use in DDL ``CREATE TABLE``.
+ :param precision: the numeric precision for use in DDL ``CREATE
+ TABLE``.
:param scale: the numeric scale for use in DDL ``CREATE TABLE``.
@@ -1000,18 +1061,21 @@ class Numeric(_DateAffinity, TypeEngine):
# we're a "numeric", DBAPI will give us Decimal directly
return None
else:
- util.warn("Dialect %s+%s does *not* support Decimal objects natively, "
- "and SQLAlchemy must convert from floating point - "
- "rounding errors and other issues may occur. "
- "Please consider storing Decimal numbers as strings or "
- "integers on this platform for lossless storage." %
- (dialect.name, dialect.driver))
+ util.warn('Dialect %s+%s does *not* support Decimal '
+ 'objects natively, and SQLAlchemy must '
+ 'convert from floating point - rounding '
+ 'errors and other issues may occur. Please '
+ 'consider storing Decimal numbers as strings '
+ 'or integers on this platform for lossless '
+ 'storage.' % (dialect.name, dialect.driver))
# we're a "numeric", DBAPI returns floats, convert.
if self.scale is not None:
- return processors.to_decimal_processor_factory(_python_Decimal, self.scale)
+ return processors.to_decimal_processor_factory(
+ _python_Decimal, self.scale)
else:
- return processors.to_decimal_processor_factory(_python_Decimal)
+ return processors.to_decimal_processor_factory(
+ _python_Decimal)
else:
if dialect.supports_native_decimal:
return processors.to_float
@@ -1060,7 +1124,8 @@ class Float(Numeric):
"""
Construct a Float.
- :param precision: the numeric precision for use in DDL ``CREATE TABLE``.
+ :param precision: the numeric precision for use in DDL ``CREATE
+ TABLE``.
:param asdecimal: the same flag as that of :class:`Numeric`, but
defaults to ``False``. Note that setting this flag to ``True``
@@ -1277,7 +1342,8 @@ class Binary(LargeBinary):
"""Deprecated. Renamed to LargeBinary."""
def __init__(self, *arg, **kw):
- util.warn_deprecated("The Binary type has been renamed to LargeBinary.")
+ util.warn_deprecated('The Binary type has been renamed to '
+ 'LargeBinary.')
LargeBinary.__init__(self, *arg, **kw)
class SchemaType(object):
@@ -1295,36 +1361,26 @@ class SchemaType(object):
self.schema = kw.pop('schema', None)
self.metadata = kw.pop('metadata', None)
if self.metadata:
- self.metadata.append_ddl_listener(
- 'before-create',
- util.portable_instancemethod(self._on_metadata_create)
- )
- self.metadata.append_ddl_listener(
- 'after-drop',
- util.portable_instancemethod(self._on_metadata_drop)
- )
+ self.metadata.append_ddl_listener('before-create',
+ util.portable_instancemethod(self._on_metadata_create))
+ self.metadata.append_ddl_listener('after-drop',
+ util.portable_instancemethod(self._on_metadata_drop))
def _set_parent(self, column):
column._on_table_attach(util.portable_instancemethod(self._set_table))
def _set_table(self, table, column):
- table.append_ddl_listener(
- 'before-create',
- util.portable_instancemethod(self._on_table_create)
- )
- table.append_ddl_listener(
- 'after-drop',
- util.portable_instancemethod(self._on_table_drop)
- )
+ table.append_ddl_listener('before-create',
+ util.portable_instancemethod(
+ self._on_table_create))
+ table.append_ddl_listener('after-drop',
+ util.portable_instancemethod(
+ self._on_table_drop))
if self.metadata is None:
- table.metadata.append_ddl_listener(
- 'before-create',
- util.portable_instancemethod(self._on_metadata_create)
- )
- table.metadata.append_ddl_listener(
- 'after-drop',
- util.portable_instancemethod(self._on_metadata_drop)
- )
+ table.metadata.append_ddl_listener('before-create',
+ util.portable_instancemethod(self._on_metadata_create))
+ table.metadata.append_ddl_listener('after-drop',
+ util.portable_instancemethod(self._on_metadata_drop))
@property
def bind(self):
@@ -1386,40 +1442,42 @@ class Enum(String, SchemaType):
Keyword arguments which don't apply to a specific backend are ignored
by that backend.
- :param \*enums: string or unicode enumeration labels. If unicode labels
- are present, the `convert_unicode` flag is auto-enabled.
-
- :param convert_unicode: Enable unicode-aware bind parameter and result-set
- processing for this Enum's data. This is set automatically based on
- the presence of unicode label strings.
-
- :param metadata: Associate this type directly with a ``MetaData`` object.
- For types that exist on the target database as an independent schema
- construct (Postgresql), this type will be created and dropped within
- ``create_all()`` and ``drop_all()`` operations. If the type is not
- associated with any ``MetaData`` object, it will associate itself with
- each ``Table`` in which it is used, and will be created when any of
- those individual tables are created, after a check is performed for
- it's existence. The type is only dropped when ``drop_all()`` is called
- for that ``Table`` object's metadata, however.
-
- :param name: The name of this type. This is required for Postgresql and
- any future supported database which requires an explicitly named type,
- or an explicitly named constraint in order to generate the type and/or
- a table that uses it.
-
- :param native_enum: Use the database's native ENUM type when available.
- Defaults to True. When False, uses VARCHAR + check constraint
- for all backends.
-
- :param schema: Schemaname of this type. For types that exist on the target
- database as an independent schema construct (Postgresql), this
- parameter specifies the named schema in which the type is present.
-
- :param quote: Force quoting to be on or off on the type's name. If left as
- the default of `None`, the usual schema-level "case
- sensitive"/"reserved name" rules are used to determine if this type's
- name should be quoted.
+ :param \*enums: string or unicode enumeration labels. If unicode
+ labels are present, the `convert_unicode` flag is auto-enabled.
+
+ :param convert_unicode: Enable unicode-aware bind parameter and
+ result-set processing for this Enum's data. This is set
+ automatically based on the presence of unicode label strings.
+
+ :param metadata: Associate this type directly with a ``MetaData``
+ object. For types that exist on the target database as an
+ independent schema construct (Postgresql), this type will be
+ created and dropped within ``create_all()`` and ``drop_all()``
+ operations. If the type is not associated with any ``MetaData``
+ object, it will associate itself with each ``Table`` in which it is
+ used, and will be created when any of those individual tables are
+ created, after a check is performed for it's existence. The type is
+ only dropped when ``drop_all()`` is called for that ``Table``
+ object's metadata, however.
+
+ :param name: The name of this type. This is required for Postgresql
+ and any future supported database which requires an explicitly
+ named type, or an explicitly named constraint in order to generate
+ the type and/or a table that uses it.
+
+ :param native_enum: Use the database's native ENUM type when
+ available. Defaults to True. When False, uses VARCHAR + check
+ constraint for all backends.
+
+ :param schema: Schemaname of this type. For types that exist on the
+ target database as an independent schema construct (Postgresql),
+ this parameter specifies the named schema in which the type is
+ present.
+
+ :param quote: Force quoting to be on or off on the type's name. If
+ left as the default of `None`, the usual schema-level "case
+ sensitive"/"reserved name" rules are used to determine if this
+ type's name should be quoted.
"""
self.enums = enums
@@ -1455,7 +1513,8 @@ class Enum(String, SchemaType):
e = schema.CheckConstraint(
column.in_(self.enums),
name=self.name,
- _create_rule=util.portable_instancemethod(self._should_create_constraint)
+ _create_rule=util.portable_instancemethod(
+ self._should_create_constraint)
)
table.append_constraint(e)
@@ -1487,7 +1546,8 @@ class PickleType(MutableType, TypeDecorator):
impl = LargeBinary
- def __init__(self, protocol=pickle.HIGHEST_PROTOCOL, pickler=None, mutable=True, comparator=None):
+ def __init__(self, protocol=pickle.HIGHEST_PROTOCOL,
+ pickler=None, mutable=True, comparator=None):
"""
Construct a PickleType.
@@ -1548,7 +1608,8 @@ class PickleType(MutableType, TypeDecorator):
def copy_value(self, value):
if self.mutable:
- return self.pickler.loads(self.pickler.dumps(value, self.protocol))
+ return self.pickler.loads(
+ self.pickler.dumps(value, self.protocol))
else:
return value
@@ -1559,6 +1620,13 @@ class PickleType(MutableType, TypeDecorator):
return x == y
def is_mutable(self):
+ """Return True if the target Python type is 'mutable'.
+
+ When this method is overridden, :meth:`copy_value` should
+ also be supplied. The :class:`.MutableType` mixin
+ is recommended as a helper.
+
+ """
return self.mutable
@@ -1596,7 +1664,8 @@ class Boolean(TypeEngine, SchemaType):
e = schema.CheckConstraint(
column.in_([0, 1]),
name=self.name,
- _create_rule=util.portable_instancemethod(self._should_create_constraint)
+ _create_rule=util.portable_instancemethod(
+ self._should_create_constraint)
)
table.append_constraint(e)
@@ -1614,12 +1683,12 @@ class Interval(_DateAffinity, TypeDecorator):
value is stored as a date which is relative to the "epoch"
(Jan. 1, 1970).
- Note that the ``Interval`` type does not currently provide
- date arithmetic operations on platforms which do not support
- interval types natively. Such operations usually require
- transformation of both sides of the expression (such as, conversion
- of both sides into integer epoch values first) which currently
- is a manual procedure (such as via :attr:`~sqlalchemy.sql.expression.func`).
+ Note that the ``Interval`` type does not currently provide date arithmetic
+ operations on platforms which do not support interval types natively. Such
+ operations usually require transformation of both sides of the expression
+ (such as, conversion of both sides into integer epoch values first) which
+ currently is a manual procedure (such as via
+ :attr:`~sqlalchemy.sql.expression.func`).
"""
@@ -1842,6 +1911,9 @@ STRINGTYPE = String()
# using VARCHAR/NCHAR so that we dont get the genericized "String"
# type which usually resolves to TEXT/CLOB
+# NOTE: this dict is not meant to be public and will be underscored
+# in 0.7, see [ticket:1870].
+
type_map = {
str: String(),
# Py3K
diff --git a/lib/sqlalchemy/util.py b/lib/sqlalchemy/util.py
index 7eb0a522f..10931be5e 100644
--- a/lib/sqlalchemy/util.py
+++ b/lib/sqlalchemy/util.py
@@ -176,6 +176,32 @@ class frozendict(dict):
def __repr__(self):
return "frozendict(%s)" % dict.__repr__(self)
+
+# find or create a dict implementation that supports __missing__
+class _probe(dict):
+ def __missing__(self, key):
+ return 1
+
+try:
+ try:
+ _probe()['missing']
+ py25_dict = dict
+ except KeyError:
+ class py25_dict(dict):
+ def __getitem__(self, key):
+ try:
+ return dict.__getitem__(self, key)
+ except KeyError:
+ try:
+ missing = self.__missing__
+ except AttributeError:
+ raise KeyError(key)
+ else:
+ return missing(key)
+finally:
+ del _probe
+
+
def to_list(x, default=None):
if x is None:
return default
@@ -1242,16 +1268,30 @@ class UniqueAppender(object):
class ScopedRegistry(object):
"""A Registry that can store one or multiple instances of a single
- class on a per-thread scoped basis, or on a customized scope.
+ class on the basis of a "scope" function.
+
+ The object implements ``__call__`` as the "getter", so by
+ calling ``myregistry()`` the contained object is returned
+ for the current scope.
- createfunc
+ :param createfunc:
a callable that returns a new object to be placed in the registry
- scopefunc
+ :param scopefunc:
a callable that will return a key to store/retrieve an object.
"""
def __init__(self, createfunc, scopefunc):
+ """Construct a new :class:`.ScopedRegistry`.
+
+ :param createfunc: A creation function that will generate
+ a new value for the current scope, if none is present.
+
+ :param scopefunc: A function that returns a hashable
+ token representing the current scope (such as, current
+ thread identifier).
+
+ """
self.createfunc = createfunc
self.scopefunc = scopefunc
self.registry = {}
@@ -1264,18 +1304,28 @@ class ScopedRegistry(object):
return self.registry.setdefault(key, self.createfunc())
def has(self):
+ """Return True if an object is present in the current scope."""
+
return self.scopefunc() in self.registry
def set(self, obj):
+ """Set the value forthe current scope."""
+
self.registry[self.scopefunc()] = obj
def clear(self):
+ """Clear the current scope, if any."""
+
try:
del self.registry[self.scopefunc()]
except KeyError:
pass
class ThreadLocalRegistry(ScopedRegistry):
+ """A :class:`.ScopedRegistry` that uses a ``threading.local()``
+ variable for storage.
+
+ """
def __init__(self, createfunc):
self.createfunc = createfunc
self.registry = threading.local()
@@ -1434,6 +1484,7 @@ def function_named(fn, name):
fn.func_defaults, fn.func_closure)
return fn
+
class memoized_property(object):
"""A read-only @property that is only evaluated once."""
def __init__(self, fget, doc=None):
@@ -1478,6 +1529,24 @@ class memoized_instancemethod(object):
def reset_memoized(instance, name):
instance.__dict__.pop(name, None)
+
+class group_expirable_memoized_property(object):
+ """A family of @memoized_properties that can be expired in tandem."""
+
+ def __init__(self):
+ self.attributes = []
+
+ def expire_instance(self, instance):
+ """Expire all memoized properties for *instance*."""
+ stash = instance.__dict__
+ for attribute in self.attributes:
+ stash.pop(attribute, None)
+
+ def __call__(self, fn):
+ self.attributes.append(fn.__name__)
+ return memoized_property(fn)
+
+
class WeakIdentityMapping(weakref.WeakKeyDictionary):
"""A WeakKeyDictionary with an object identity index.