Update SQLAlchemy

This commit is contained in:
Ruud committed 2013-06-14 11:00:06 +02:00
1 parent 267ecfacab
commit 4aa6700ceb
121 files changed
+2321 -1028

No files matched your search

+5 -2
View File
@@ -1,5 +1,5 @@
# sqlalchemy/__init__.py # sqlalchemy/__init__.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -50,6 +50,8 @@ from sqlalchemy.sql import (
) )
from sqlalchemy.types import ( from sqlalchemy.types import (
BIGINT,
BINARY,
BLOB, BLOB,
BOOLEAN, BOOLEAN,
BigInteger, BigInteger,
@@ -87,6 +89,7 @@ from sqlalchemy.types import (
TypeDecorator, TypeDecorator,
Unicode, Unicode,
UnicodeText, UnicodeText,
VARBINARY,
VARCHAR, VARCHAR,
) )
@@ -117,7 +120,7 @@ from sqlalchemy.engine import create_engine, engine_from_config
__all__ = sorted(name for name, obj in locals().items() __all__ = sorted(name for name, obj in locals().items()
if not (name.startswith('_') or inspect.ismodule(obj))) if not (name.startswith('_') or inspect.ismodule(obj)))
__version__ = '0.7.6' __version__ = '0.7.10'
del inspect, sys del inspect, sys
+26 -7
View File
@@ -241,12 +241,13 @@ static PyObject *
BaseRowProxy_subscript(BaseRowProxy *self, PyObject *key) BaseRowProxy_subscript(BaseRowProxy *self, PyObject *key)
{ {
PyObject *processors, *values; PyObject *processors, *values;
PyObject *processor, *value; PyObject *processor, *value, *processed_value;
PyObject *row, *record, *result, *indexobject; PyObject *row, *record, *result, *indexobject;
PyObject *exc_module, *exception; PyObject *exc_module, *exception, *cstr_obj;
char *cstr_key; char *cstr_key;
long index; long index;
int key_fallback = 0; int key_fallback = 0;
int tuple_check = 0;
if (PyInt_CheckExact(key)) { if (PyInt_CheckExact(key)) {
index = PyInt_AS_LONG(key); index = PyInt_AS_LONG(key);
@@ -299,9 +300,16 @@ BaseRowProxy_subscript(BaseRowProxy *self, PyObject *key)
if (exception == NULL) if (exception == NULL)
return NULL; return NULL;
cstr_key = PyString_AsString(key); // wow. this seems quite excessive.
if (cstr_key == NULL) cstr_obj = PyObject_Str(key);
if (cstr_obj == NULL)
return NULL; return NULL;
cstr_key = PyString_AsString(cstr_obj);
if (cstr_key == NULL) {
Py_DECREF(cstr_obj);
return NULL;
}
Py_DECREF(cstr_obj);
PyErr_Format(exception, PyErr_Format(exception,
"Ambiguous column name '%.200s' in result set! " "Ambiguous column name '%.200s' in result set! "
@@ -319,17 +327,28 @@ BaseRowProxy_subscript(BaseRowProxy *self, PyObject *key)
return NULL; return NULL;
row = self->row; row = self->row;
if (PyTuple_CheckExact(row)) if (PyTuple_CheckExact(row)) {
value = PyTuple_GetItem(row, index); value = PyTuple_GetItem(row, index);
else tuple_check = 1;
}
else {
value = PySequence_GetItem(row, index); value = PySequence_GetItem(row, index);
tuple_check = 0;
}
if (value == NULL) if (value == NULL)
return NULL; return NULL;
if (processor != Py_None) { if (processor != Py_None) {
return PyObject_CallFunctionObjArgs(processor, value, NULL); processed_value = PyObject_CallFunctionObjArgs(processor, value, NULL);
if (!tuple_check) {
Py_DECREF(value);
}
return processed_value;
} else { } else {
if (tuple_check) {
Py_INCREF(value); Py_INCREF(value);
}
return value; return value;
} }
} }
+1 -1
View File
@@ -1,5 +1,5 @@
# connectors/__init__.py # connectors/__init__.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+1 -1
View File
@@ -1,5 +1,5 @@
# connectors/mxodbc.py # connectors/mxodbc.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+1
View File
@@ -63,6 +63,7 @@ class MySQLDBConnector(Connector):
util.coerce_kw_type(opts, 'compress', bool) util.coerce_kw_type(opts, 'compress', bool)
util.coerce_kw_type(opts, 'connect_timeout', int) util.coerce_kw_type(opts, 'connect_timeout', int)
util.coerce_kw_type(opts, 'read_timeout', int)
util.coerce_kw_type(opts, 'client_flag', int) util.coerce_kw_type(opts, 'client_flag', int)
util.coerce_kw_type(opts, 'local_infile', int) util.coerce_kw_type(opts, 'local_infile', int)
# Note: using either of the below will cause all strings to be returned # Note: using either of the below will cause all strings to be returned
+10 -2
View File
@@ -1,5 +1,5 @@
# connectors/pyodbc.py # connectors/pyodbc.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -37,6 +37,10 @@ class PyODBCConnector(Connector):
# if the libessqlsrv.so is detected # if the libessqlsrv.so is detected
easysoft = False easysoft = False
def __init__(self, supports_unicode_binds=None, **kw):
super(PyODBCConnector, self).__init__(**kw)
self._user_supports_unicode_binds = supports_unicode_binds
@classmethod @classmethod
def dbapi(cls): def dbapi(cls):
return __import__('pyodbc') return __import__('pyodbc')
@@ -119,8 +123,12 @@ class PyODBCConnector(Connector):
# have not tried pyodbc + python3.1 yet. # have not tried pyodbc + python3.1 yet.
# Py2K # Py2K
self.supports_unicode_statements = not self.freetds and not self.easysoft self.supports_unicode_statements = not self.freetds and not self.easysoft
if self._user_supports_unicode_binds is not None:
self.supports_unicode_binds = self._user_supports_unicode_binds
else:
self.supports_unicode_binds = (not self.freetds or self.supports_unicode_binds = (not self.freetds or
self.freetds_driver_version >= '0.91') and not self.easysoft self.freetds_driver_version >= '0.91'
) and not self.easysoft
# end Py2K # end Py2K
# run other initialization which asks for user name, etc. # run other initialization which asks for user name, etc.
+1 -1
View File
@@ -1,5 +1,5 @@
# connectors/zxJDBC.py # connectors/zxJDBC.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+1 -1
View File
@@ -1,5 +1,5 @@
# databases/__init__.py # databases/__init__.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+1 -1
View File
@@ -1,5 +1,5 @@
# dialects/__init__.py # dialects/__init__.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+3 -2
View File
@@ -9,9 +9,10 @@
""" """
Support for the Microsoft Access database. Support for the Microsoft Access database.
This dialect is *not* ported to SQLAlchemy 0.6 or 0.7. .. note::
This dialect is *not* tested on SQLAlchemy 0.6 or 0.7. The Access dialect is **non-functional as of SQLAlchemy 0.6**,
pending development efforts to bring it up-to-date.
""" """
+13 -9
View File
@@ -1,18 +1,22 @@
from sqlalchemy.dialects.drizzle import base, mysqldb from sqlalchemy.dialects.drizzle import base, mysqldb
# default dialect
base.dialect = mysqldb.dialect base.dialect = mysqldb.dialect
from sqlalchemy.dialects.drizzle.base import \ from sqlalchemy.dialects.drizzle.base import \
BIGINT, BINARY, BLOB, BOOLEAN, CHAR, DATE, DATETIME, \ BIGINT, BINARY, BLOB, \
DECIMAL, DOUBLE, ENUM, \ BOOLEAN, CHAR, DATE, \
FLOAT, INTEGER, \ DATETIME, DECIMAL, DOUBLE, \
NUMERIC, REAL, TEXT, TIME, TIMESTAMP, \ ENUM, FLOAT, INTEGER, \
VARBINARY, VARCHAR, dialect NUMERIC, REAL, TEXT, \
TIME, TIMESTAMP, VARBINARY, \
VARCHAR, dialect
__all__ = ( __all__ = (
'BIGINT', 'BINARY', 'BLOB', 'BOOLEAN', 'CHAR', 'DATE', 'DATETIME', 'DECIMAL', 'DOUBLE', 'BIGINT', 'BINARY', 'BLOB',
'BOOLEAN', 'CHAR', 'DATE',
'DATETIME', 'DECIMAL', 'DOUBLE',
'ENUM', 'FLOAT', 'INTEGER', 'ENUM', 'FLOAT', 'INTEGER',
'NUMERIC', 'SET', 'REAL', 'TEXT', 'TIME', 'TIMESTAMP', 'NUMERIC', 'REAL', 'TEXT',
'VARBINARY', 'VARCHAR', 'dialect' 'TIME', 'TIMESTAMP', 'VARBINARY',
'VARCHAR', 'dialect'
) )
+63 -136
View File
@@ -1,145 +1,44 @@
# drizzle/base.py # drizzle/base.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2010-2011 Monty Taylor <mordred@inaugust.com> # Copyright (C) 2010-2011 Monty Taylor <mordred@inaugust.com>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
"""Support for the Drizzle database. """Support for the Drizzle database.
Supported Versions and Features Drizzle is a variant of MySQL. Unlike MySQL, Drizzle's default storage engine
------------------------------- is InnoDB (transactions, foreign-keys) rather than MyISAM. For more
`Notable Differences <http://docs.drizzle.org/mysql_differences.html>`_, visit
the `Drizzle Documentation <http://docs.drizzle.org/index.html>`_.
SQLAlchemy supports the Drizzle database starting with 2010.08. The SQLAlchemy Drizzle dialect leans heavily on the MySQL dialect, so much of
with capabilities increasing with more modern servers. the :doc:`SQLAlchemy MySQL <mysql>` documentation is also relevant.
Most available DBAPI drivers are supported; see below.
===================================== ===============
Feature Minimum Version
===================================== ===============
sqlalchemy.orm 2010.08
Table Reflection 2010.08
DDL Generation 2010.08
utf8/Full Unicode Connections 2010.08
Transactions 2010.08
Two-Phase Transactions 2010.08
Nested Transactions 2010.08
===================================== ===============
See the official Drizzle documentation for detailed information about features
supported in any given server release.
Connecting Connecting
---------- ----------
See the API documentation on individual drivers for details on connecting. See the individual driver sections below for details on connecting.
Connection Timeouts
-------------------
Drizzle features an automatic connection close behavior, for connections that
have been idle for eight hours or more. To circumvent having this issue, use
the ``pool_recycle`` option which controls the maximum age of any connection::
engine = create_engine('drizzle+mysqldb://...', pool_recycle=3600)
Storage Engines
---------------
Drizzle defaults to the ``InnoDB`` storage engine, which is transactional.
Storage engines can be elected when creating tables in SQLAlchemy by supplying
a ``drizzle_engine='whatever'`` to the ``Table`` constructor. Any Drizzle table
creation option can be specified in this syntax::
Table('mytable', metadata,
Column('data', String(32)),
drizzle_engine='InnoDB',
)
Keys
----
Not all Drizzle storage engines support foreign keys. For ``BlitzDB`` and
similar engines, the information loaded by table reflection will not include
foreign keys. For these tables, you may supply a
:class:`~sqlalchemy.ForeignKeyConstraint` at reflection time::
Table('mytable', metadata,
ForeignKeyConstraint(['other_id'], ['othertable.other_id']),
autoload=True
)
When creating tables, SQLAlchemy will automatically set ``AUTO_INCREMENT`` on
an integer primary key column::
>>> t = Table('mytable', metadata,
... Column('mytable_id', Integer, primary_key=True)
... )
>>> t.create()
CREATE TABLE mytable (
id INTEGER NOT NULL AUTO_INCREMENT,
PRIMARY KEY (id)
)
You can disable this behavior by supplying ``autoincrement=False`` to the
:class:`~sqlalchemy.Column`. This flag can also be used to enable
auto-increment on a secondary column in a multi-column key for some storage
engines::
Table('mytable', metadata,
Column('gid', Integer, primary_key=True, autoincrement=False),
Column('id', Integer, primary_key=True)
)
Drizzle SQL Extensions
----------------------
Many of the Drizzle SQL extensions are handled through SQLAlchemy's generic
function and operator support::
table.select(table.c.password==func.md5('plaintext'))
table.select(table.c.username.op('regexp')('^[a-d]'))
And of course any valid Drizzle statement can be executed as a string as well.
Some limited direct support for Drizzle extensions to SQL is currently
available.
* SELECT pragma::
select(..., prefixes=['HIGH_PRIORITY', 'SQL_SMALL_RESULT'])
* UPDATE with LIMIT::
update(..., drizzle_limit=10)
""" """
import datetime, inspect, re, sys from sqlalchemy import exc
from sqlalchemy import log
from sqlalchemy import schema as sa_schema
from sqlalchemy import exc, log, sql, util
from sqlalchemy.sql import operators as sql_operators
from sqlalchemy.sql import functions as sql_functions
from sqlalchemy.sql import compiler
from array import array as _array
from sqlalchemy.engine import reflection
from sqlalchemy.engine import base as engine_base, default
from sqlalchemy import types as sqltypes from sqlalchemy import types as sqltypes
from sqlalchemy.engine import reflection
from sqlalchemy.dialects.mysql import base as mysql_dialect from sqlalchemy.dialects.mysql import base as mysql_dialect
from sqlalchemy.types import DATE, DATETIME, BOOLEAN, TIME, \ from sqlalchemy.types import DATE, DATETIME, BOOLEAN, TIME, \
BLOB, BINARY, VARBINARY BLOB, BINARY, VARBINARY
class _NumericType(object): class _NumericType(object):
"""Base for Drizzle numeric types.""" """Base for Drizzle numeric types."""
def __init__(self, **kw): def __init__(self, **kw):
super(_NumericType, self).__init__(**kw) super(_NumericType, self).__init__(**kw)
class _FloatType(_NumericType, sqltypes.Float): class _FloatType(_NumericType, sqltypes.Float):
def __init__(self, precision=None, scale=None, asdecimal=True, **kw): def __init__(self, precision=None, scale=None, asdecimal=True, **kw):
if isinstance(self, (REAL, DOUBLE)) and \ if isinstance(self, (REAL, DOUBLE)) and \
@@ -151,18 +50,17 @@ class _FloatType(_NumericType, sqltypes.Float):
"You must specify both precision and scale or omit " "You must specify both precision and scale or omit "
"both altogether.") "both altogether.")
super(_FloatType, self).__init__(precision=precision, asdecimal=asdecimal, **kw) super(_FloatType, self).__init__(precision=precision,
asdecimal=asdecimal, **kw)
self.scale = scale self.scale = scale
class _StringType(mysql_dialect._StringType): class _StringType(mysql_dialect._StringType):
"""Base for Drizzle string types.""" """Base for Drizzle string types."""
def __init__(self, collation=None, def __init__(self, collation=None, binary=False, **kw):
binary=False,
**kw):
kw['national'] = False kw['national'] = False
super(_StringType, self).__init__(collation=collation, super(_StringType, self).__init__(collation=collation, binary=binary,
binary=binary,
**kw) **kw)
@@ -180,7 +78,9 @@ class NUMERIC(_NumericType, sqltypes.NUMERIC):
:param scale: The number of digits after the decimal point. :param scale: The number of digits after the decimal point.
""" """
super(NUMERIC, self).__init__(precision=precision, scale=scale, asdecimal=asdecimal, **kw)
super(NUMERIC, self).__init__(precision=precision, scale=scale,
asdecimal=asdecimal, **kw)
class DECIMAL(_NumericType, sqltypes.DECIMAL): class DECIMAL(_NumericType, sqltypes.DECIMAL):
@@ -215,9 +115,11 @@ class DOUBLE(_FloatType):
:param scale: The number of digits after the decimal point. :param scale: The number of digits after the decimal point.
""" """
super(DOUBLE, self).__init__(precision=precision, scale=scale, super(DOUBLE, self).__init__(precision=precision, scale=scale,
asdecimal=asdecimal, **kw) asdecimal=asdecimal, **kw)
class REAL(_FloatType, sqltypes.REAL): class REAL(_FloatType, sqltypes.REAL):
"""Drizzle REAL type.""" """Drizzle REAL type."""
@@ -232,9 +134,11 @@ class REAL(_FloatType, sqltypes.REAL):
:param scale: The number of digits after the decimal point. :param scale: The number of digits after the decimal point.
""" """
super(REAL, self).__init__(precision=precision, scale=scale, super(REAL, self).__init__(precision=precision, scale=scale,
asdecimal=asdecimal, **kw) asdecimal=asdecimal, **kw)
class FLOAT(_FloatType, sqltypes.FLOAT): class FLOAT(_FloatType, sqltypes.FLOAT):
"""Drizzle FLOAT type.""" """Drizzle FLOAT type."""
@@ -249,42 +153,46 @@ class FLOAT(_FloatType, sqltypes.FLOAT):
:param scale: The number of digits after the decimal point. :param scale: The number of digits after the decimal point.
""" """
super(FLOAT, self).__init__(precision=precision, scale=scale, super(FLOAT, self).__init__(precision=precision, scale=scale,
asdecimal=asdecimal, **kw) asdecimal=asdecimal, **kw)
def bind_processor(self, dialect): def bind_processor(self, dialect):
return None return None
class INTEGER(sqltypes.INTEGER): class INTEGER(sqltypes.INTEGER):
"""Drizzle INTEGER type.""" """Drizzle INTEGER type."""
__visit_name__ = 'INTEGER' __visit_name__ = 'INTEGER'
def __init__(self, **kw): def __init__(self, **kw):
"""Construct an INTEGER. """Construct an INTEGER."""
"""
super(INTEGER, self).__init__(**kw) super(INTEGER, self).__init__(**kw)
class BIGINT(sqltypes.BIGINT): class BIGINT(sqltypes.BIGINT):
"""Drizzle BIGINTEGER type.""" """Drizzle BIGINTEGER type."""
__visit_name__ = 'BIGINT' __visit_name__ = 'BIGINT'
def __init__(self, **kw): def __init__(self, **kw):
"""Construct a BIGINTEGER. """Construct a BIGINTEGER."""
"""
super(BIGINT, self).__init__(**kw) super(BIGINT, self).__init__(**kw)
class _DrizzleTime(mysql_dialect._MSTime): class _DrizzleTime(mysql_dialect._MSTime):
"""Drizzle TIME type.""" """Drizzle TIME type."""
class TIMESTAMP(sqltypes.TIMESTAMP): class TIMESTAMP(sqltypes.TIMESTAMP):
"""Drizzle TIMESTAMP type.""" """Drizzle TIMESTAMP type."""
__visit_name__ = 'TIMESTAMP' __visit_name__ = 'TIMESTAMP'
class TEXT(_StringType, sqltypes.TEXT): class TEXT(_StringType, sqltypes.TEXT):
"""Drizzle TEXT type, for text up to 2^16 characters.""" """Drizzle TEXT type, for text up to 2^16 characters."""
@@ -306,8 +214,10 @@ class TEXT(_StringType, sqltypes.TEXT):
only the collation of character data. only the collation of character data.
""" """
super(TEXT, self).__init__(length=length, **kw) super(TEXT, self).__init__(length=length, **kw)
class VARCHAR(_StringType, sqltypes.VARCHAR): class VARCHAR(_StringType, sqltypes.VARCHAR):
"""Drizzle VARCHAR type, for variable-length character data.""" """Drizzle VARCHAR type, for variable-length character data."""
@@ -325,8 +235,10 @@ class VARCHAR(_StringType, sqltypes.VARCHAR):
only the collation of character data. only the collation of character data.
""" """
super(VARCHAR, self).__init__(length=length, **kwargs) super(VARCHAR, self).__init__(length=length, **kwargs)
class CHAR(_StringType, sqltypes.CHAR): class CHAR(_StringType, sqltypes.CHAR):
"""Drizzle CHAR type, for fixed-length character data.""" """Drizzle CHAR type, for fixed-length character data."""
@@ -345,8 +257,10 @@ class CHAR(_StringType, sqltypes.CHAR):
compatible with the national character set. compatible with the national character set.
""" """
super(CHAR, self).__init__(length=length, **kwargs) super(CHAR, self).__init__(length=length, **kwargs)
class ENUM(mysql_dialect.ENUM): class ENUM(mysql_dialect.ENUM):
"""Drizzle ENUM type.""" """Drizzle ENUM type."""
@@ -363,8 +277,9 @@ class ENUM(mysql_dialect.ENUM):
:param strict: Defaults to False: ensure that a given value is in this :param strict: Defaults to False: ensure that a given value is in this
ENUM's range of permissible values when inserting or updating rows. ENUM's range of permissible values when inserting or updating rows.
Note that Drizzle will not raise a fatal error if you attempt to store Note that Drizzle will not raise a fatal error if you attempt to
an out of range value- an alternate value will be stored instead. store an out of range value- an alternate value will be stored
instead.
(See Drizzle ENUM documentation.) (See Drizzle ENUM documentation.)
:param collation: Optional, a column-level collation for this string :param collation: Optional, a column-level collation for this string
@@ -390,12 +305,15 @@ class ENUM(mysql_dialect.ENUM):
literals for you. This is a transitional option. literals for you. This is a transitional option.
""" """
super(ENUM, self).__init__(*enums, **kw) super(ENUM, self).__init__(*enums, **kw)
class _DrizzleBoolean(sqltypes.Boolean): class _DrizzleBoolean(sqltypes.Boolean):
def get_dbapi_type(self, dbapi): def get_dbapi_type(self, dbapi):
return dbapi.NUMERIC return dbapi.NUMERIC
colspecs = { colspecs = {
sqltypes.Numeric: NUMERIC, sqltypes.Numeric: NUMERIC,
sqltypes.Float: FLOAT, sqltypes.Float: FLOAT,
@@ -404,6 +322,7 @@ colspecs = {
sqltypes.Boolean: _DrizzleBoolean, sqltypes.Boolean: _DrizzleBoolean,
} }
# All the types we have in Drizzle # All the types we have in Drizzle
ischema_names = { ischema_names = {
'BIGINT': BIGINT, 'BIGINT': BIGINT,
@@ -427,6 +346,7 @@ ischema_names = {
'VARCHAR': VARCHAR, 'VARCHAR': VARCHAR,
} }
class DrizzleCompiler(mysql_dialect.MySQLCompiler): class DrizzleCompiler(mysql_dialect.MySQLCompiler):
def visit_typeclause(self, typeclause): def visit_typeclause(self, typeclause):
@@ -447,6 +367,7 @@ class DrizzleCompiler(mysql_dialect.MySQLCompiler):
class DrizzleDDLCompiler(mysql_dialect.MySQLDDLCompiler): class DrizzleDDLCompiler(mysql_dialect.MySQLDDLCompiler):
pass pass
class DrizzleTypeCompiler(mysql_dialect.MySQLTypeCompiler): class DrizzleTypeCompiler(mysql_dialect.MySQLTypeCompiler):
def _extend_numeric(self, type_, spec): def _extend_numeric(self, type_, spec):
return spec return spec
@@ -492,11 +413,16 @@ class DrizzleTypeCompiler(mysql_dialect.MySQLTypeCompiler):
class DrizzleExecutionContext(mysql_dialect.MySQLExecutionContext): class DrizzleExecutionContext(mysql_dialect.MySQLExecutionContext):
pass pass
class DrizzleIdentifierPreparer(mysql_dialect.MySQLIdentifierPreparer): class DrizzleIdentifierPreparer(mysql_dialect.MySQLIdentifierPreparer):
pass pass
class DrizzleDialect(mysql_dialect.MySQLDialect): class DrizzleDialect(mysql_dialect.MySQLDialect):
"""Details of the Drizzle dialect. Not used directly in application code.""" """Details of the Drizzle dialect.
Not used directly in application code.
"""
name = 'drizzle' name = 'drizzle'
@@ -505,7 +431,6 @@ class DrizzleDialect(mysql_dialect.MySQLDialect):
supports_native_boolean = True supports_native_boolean = True
supports_views = False supports_views = False
default_paramstyle = 'format' default_paramstyle = 'format'
colspecs = colspecs colspecs = colspecs
@@ -516,8 +441,8 @@ class DrizzleDialect(mysql_dialect.MySQLDialect):
preparer = DrizzleIdentifierPreparer preparer = DrizzleIdentifierPreparer
def on_connect(self): def on_connect(self):
"""Force autocommit - Drizzle Bug#707842 doesn't set this """Force autocommit - Drizzle Bug#707842 doesn't set this properly"""
properly"""
def connect(conn): def connect(conn):
conn.autocommit(False) conn.autocommit(False)
return connect return connect
@@ -535,6 +460,7 @@ class DrizzleDialect(mysql_dialect.MySQLDialect):
@reflection.cache @reflection.cache
def get_table_names(self, connection, schema=None, **kw): def get_table_names(self, connection, schema=None, **kw):
"""Return a Unicode SHOW TABLES from a given schema.""" """Return a Unicode SHOW TABLES from a given schema."""
if schema is not None: if schema is not None:
current_schema = schema current_schema = schema
else: else:
@@ -554,8 +480,8 @@ class DrizzleDialect(mysql_dialect.MySQLDialect):
Cached per-connection. This value can not change without a server Cached per-connection. This value can not change without a server
restart. restart.
""" """
return 0 return 0
def _detect_collations(self, connection): def _detect_collations(self, connection):
@@ -566,7 +492,9 @@ class DrizzleDialect(mysql_dialect.MySQLDialect):
collations = {} collations = {}
charset = self._connection_charset charset = self._connection_charset
rs = connection.execute('SELECT CHARACTER_SET_NAME, COLLATION_NAME from data_dictionary.COLLATIONS') rs = connection.execute(
'SELECT CHARACTER_SET_NAME, COLLATION_NAME FROM'
' data_dictionary.COLLATIONS')
for row in self._compat_fetchall(rs, charset): for row in self._compat_fetchall(rs, charset):
collations[row[0]] = row[1] collations[row[0]] = row[1]
return collations return collations
@@ -575,8 +503,7 @@ class DrizzleDialect(mysql_dialect.MySQLDialect):
"""Detect and adjust for the ANSI_QUOTES sql mode.""" """Detect and adjust for the ANSI_QUOTES sql mode."""
self._server_ansiquotes = False self._server_ansiquotes = False
self._backslash_escapes = False self._backslash_escapes = False
log.class_logger(DrizzleDialect)
log.class_logger(DrizzleDialect)
+12 -28
View File
@@ -1,11 +1,9 @@
"""Support for the Drizzle database via the Drizzle-python adapter. """Support for the Drizzle database via the mysql-python adapter.
Drizzle-Python is available at: MySQL-Python is available at:
http://sourceforge.net/projects/mysql-python http://sourceforge.net/projects/mysql-python
At least version 1.2.1 or 1.2.2 should be used.
Connecting Connecting
----------- -----------
@@ -13,36 +11,21 @@ Connect string format::
drizzle+mysqldb://<user>:<password>@<host>[:<port>]/<dbname> drizzle+mysqldb://<user>:<password>@<host>[:<port>]/<dbname>
Unicode
-------
Drizzle accommodates Python ``unicode`` objects directly and
uses the ``utf8`` encoding in all cases.
Known Issues
-------------
Drizzle-python at least as of version 1.2.2 has a serious memory leak related
to unicode conversion, a feature which is disabled via ``use_unicode=0``.
The recommended connection form with SQLAlchemy is::
engine = create_engine('mysql://scott:tiger@localhost/test?charset=utf8&use_unicode=0', pool_recycle=3600)
""" """
from sqlalchemy.dialects.drizzle.base import (DrizzleDialect, from sqlalchemy.dialects.drizzle.base import (
DrizzleDialect,
DrizzleExecutionContext, DrizzleExecutionContext,
DrizzleCompiler, DrizzleIdentifierPreparer) DrizzleCompiler,
DrizzleIdentifierPreparer)
from sqlalchemy.connectors.mysqldb import ( from sqlalchemy.connectors.mysqldb import (
MySQLDBExecutionContext, MySQLDBExecutionContext,
MySQLDBCompiler, MySQLDBCompiler,
MySQLDBIdentifierPreparer, MySQLDBIdentifierPreparer,
MySQLDBConnector MySQLDBConnector)
)
class DrizzleExecutionContext_mysqldb(
MySQLDBExecutionContext, class DrizzleExecutionContext_mysqldb(MySQLDBExecutionContext,
DrizzleExecutionContext): DrizzleExecutionContext):
pass pass
@@ -51,11 +34,11 @@ class DrizzleCompiler_mysqldb(MySQLDBCompiler, DrizzleCompiler):
pass pass
class DrizzleIdentifierPreparer_mysqldb( class DrizzleIdentifierPreparer_mysqldb(MySQLDBIdentifierPreparer,
MySQLDBIdentifierPreparer,
DrizzleIdentifierPreparer): DrizzleIdentifierPreparer):
pass pass
class DrizzleDialect_mysqldb(MySQLDBConnector, DrizzleDialect): class DrizzleDialect_mysqldb(MySQLDBConnector, DrizzleDialect):
execution_ctx_cls = DrizzleExecutionContext_mysqldb execution_ctx_cls = DrizzleExecutionContext_mysqldb
statement_compiler = DrizzleCompiler_mysqldb statement_compiler = DrizzleCompiler_mysqldb
@@ -63,6 +46,7 @@ class DrizzleDialect_mysqldb(MySQLDBConnector, DrizzleDialect):
def _detect_charset(self, connection): def _detect_charset(self, connection):
"""Sniff out the character set in use for connection results.""" """Sniff out the character set in use for connection results."""
return 'utf8' return 'utf8'
@@ -1,5 +1,5 @@
# firebird/__init__.py # firebird/__init__.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+3 -3
View File
@@ -1,5 +1,5 @@
# firebird/base.py # firebird/base.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -198,7 +198,7 @@ class FBTypeCompiler(compiler.GenericTypeCompiler):
class FBCompiler(sql.compiler.SQLCompiler): class FBCompiler(sql.compiler.SQLCompiler):
"""Firebird specific idiosincrasies""" """Firebird specific idiosyncrasies"""
def visit_mod(self, binary, **kw): def visit_mod(self, binary, **kw):
# Firebird lacks a builtin modulo operator, but there is # Firebird lacks a builtin modulo operator, but there is
@@ -293,7 +293,7 @@ class FBCompiler(sql.compiler.SQLCompiler):
class FBDDLCompiler(sql.compiler.DDLCompiler): class FBDDLCompiler(sql.compiler.DDLCompiler):
"""Firebird syntactic idiosincrasies""" """Firebird syntactic idiosyncrasies"""
def visit_create_sequence(self, create): def visit_create_sequence(self, create):
"""Generate a ``CREATE GENERATOR`` statement for the sequence.""" """Generate a ``CREATE GENERATOR`` statement for the sequence."""
@@ -1,5 +1,5 @@
# firebird/kinterbasdb.py # firebird/kinterbasdb.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -1,5 +1,5 @@
# informix/__init__.py # informix/__init__.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+6 -3
View File
@@ -1,5 +1,5 @@
# informix/base.py # informix/base.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# coding: gbk # coding: gbk
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
@@ -7,8 +7,11 @@
"""Support for the Informix database. """Support for the Informix database.
This dialect is mostly functional as of SQLAlchemy 0.6.5. .. note::
The Informix dialect functions on current SQLAlchemy versions
but is not regularly tested, and may have many issues and
caveats not currently handled.
""" """
@@ -23,7 +26,7 @@ from sqlalchemy import types as sqltypes
RESERVED_WORDS = set( RESERVED_WORDS = set(
["abs", "absolute", "access", "access_method", "acos", "active", "add", ["abs", "absolute", "access", "access_method", "acos", "active", "add",
"address", "add_months", "admin", "after", "aggregate", "alignment", "address", "add_months", "admin", "after", "aggregate", "alignment",
"all", "allocate", "all_rows", "altere", "and", "ansi", "any", "append", "all", "allocate", "all_rows", "alter", "and", "ansi", "any", "append",
"array", "as", "asc", "ascii", "asin", "at", "atan", "atan2", "attach", "array", "as", "asc", "ascii", "asin", "at", "atan", "atan2", "attach",
"attributes", "audit", "authentication", "authid", "authorization", "attributes", "audit", "authentication", "authid", "authorization",
"authorized", "auto", "autofree", "auto_reprepare", "auto_stat_mode", "authorized", "auto", "autofree", "auto_reprepare", "auto_stat_mode",
@@ -1,5 +1,5 @@
# informix/informixdb.py # informix/informixdb.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+1 -1
View File
@@ -1,5 +1,5 @@
# maxdb/__init__.py # maxdb/__init__.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+4 -3
View File
@@ -1,14 +1,15 @@
# maxdb/base.py # maxdb/base.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
"""Support for the MaxDB database. """Support for the MaxDB database.
This dialect is *not* ported to SQLAlchemy 0.6 or 0.7. .. note::
This dialect is *not* tested on SQLAlchemy 0.6 or 0.7. The MaxDB dialect is **non-functional as of SQLAlchemy 0.6**,
pending development efforts to bring it up-to-date.
Overview Overview
-------- --------
+1 -1
View File
@@ -1,5 +1,5 @@
# maxdb/sapdb.py # maxdb/sapdb.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+1 -1
View File
@@ -1,5 +1,5 @@
# mssql/__init__.py # mssql/__init__.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+1 -1
View File
@@ -1,5 +1,5 @@
# mssql/adodbapi.py # mssql/adodbapi.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+42 -17
View File
@@ -1,5 +1,5 @@
# mssql/base.py # mssql/base.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -107,10 +107,10 @@ Compatibility Levels
-------------------- --------------------
MSSQL supports the notion of setting compatibility levels at the MSSQL supports the notion of setting compatibility levels at the
database level. This allows, for instance, to run a database that database level. This allows, for instance, to run a database that
is compatibile with SQL2000 while running on a SQL2005 database is compatible with SQL2000 while running on a SQL2005 database
server. ``server_version_info`` will always return the database server. ``server_version_info`` will always return the database
server version information (in this case SQL2005) and not the server version information (in this case SQL2005) and not the
compatibiility level information. Because of this, if running under compatibility level information. Because of this, if running under
a backwards compatibility mode SQAlchemy may attempt to use T-SQL a backwards compatibility mode SQAlchemy may attempt to use T-SQL
statements that are unable to be parsed by the database server. statements that are unable to be parsed by the database server.
@@ -161,10 +161,11 @@ http://msdn.microsoft.com/en-us/library/ms175095.aspx.
Scalar Select Comparisons Scalar Select Comparisons
------------------------- -------------------------
.. deprecated:: 0.8
The MSSQL dialect contains a legacy behavior whereby comparing The MSSQL dialect contains a legacy behavior whereby comparing
a scalar select to a value using the ``=`` or ``!=`` operator a scalar select to a value using the ``=`` or ``!=`` operator
will resolve to IN or NOT IN, respectively. This behavior is will resolve to IN or NOT IN, respectively. This behavior
deprecated and will be removed in 0.8 - the ``s.in_()``/``~s.in_()`` operators will be removed in 0.8 - the ``s.in_()``/``~s.in_()`` operators
should be used when IN/NOT IN are desired. should be used when IN/NOT IN are desired.
For the time being, the existing behavior prevents a comparison For the time being, the existing behavior prevents a comparison
@@ -689,18 +690,22 @@ class MSExecutionContext(default.DefaultExecutionContext):
not self.executemany not self.executemany
if self._enable_identity_insert: if self._enable_identity_insert:
self.cursor.execute("SET IDENTITY_INSERT %s ON" % self.root_connection._cursor_execute(self.cursor,
self.dialect.identifier_preparer.format_table(tbl)) "SET IDENTITY_INSERT %s ON" %
self.dialect.identifier_preparer.format_table(tbl),
())
def post_exec(self): def post_exec(self):
"""Disable IDENTITY_INSERT if enabled.""" """Disable IDENTITY_INSERT if enabled."""
conn = self.root_connection
if self._select_lastrowid: if self._select_lastrowid:
if self.dialect.use_scope_identity: if self.dialect.use_scope_identity:
self.cursor.execute( conn._cursor_execute(self.cursor,
"SELECT scope_identity() AS lastrowid", ()) "SELECT scope_identity() AS lastrowid", ())
else: else:
self.cursor.execute("SELECT @@identity AS lastrowid", ()) conn._cursor_execute(self.cursor,
"SELECT @@identity AS lastrowid", ())
# fetchall() ensures the cursor is consumed without closing it # fetchall() ensures the cursor is consumed without closing it
row = self.cursor.fetchall()[0] row = self.cursor.fetchall()[0]
self._lastrowid = int(row[0]) self._lastrowid = int(row[0])
@@ -710,10 +715,11 @@ class MSExecutionContext(default.DefaultExecutionContext):
self._result_proxy = base.FullyBufferedResultProxy(self) self._result_proxy = base.FullyBufferedResultProxy(self)
if self._enable_identity_insert: if self._enable_identity_insert:
self.cursor.execute( conn._cursor_execute(self.cursor,
"SET IDENTITY_INSERT %s OFF" % "SET IDENTITY_INSERT %s OFF" %
self.dialect.identifier_preparer. self.dialect.identifier_preparer.
format_table(self.compiled.statement.table) format_table(self.compiled.statement.table),
()
) )
def get_lastrowid(self): def get_lastrowid(self):
@@ -803,19 +809,20 @@ class MSSQLCompiler(compiler.SQLCompiler):
so tries to wrap it in a subquery with ``row_number()`` criterion. so tries to wrap it in a subquery with ``row_number()`` criterion.
""" """
if not getattr(select, '_mssql_visit', None) and select._offset: if select._offset and not getattr(select, '_mssql_visit', None):
# to use ROW_NUMBER(), an ORDER BY is required. # to use ROW_NUMBER(), an ORDER BY is required.
orderby = self.process(select._order_by_clause) if not select._order_by_clause.clauses:
if not orderby:
raise exc.CompileError('MSSQL requires an order_by when ' raise exc.CompileError('MSSQL requires an order_by when '
'using an offset.') 'using an offset.')
_offset = select._offset _offset = select._offset
_limit = select._limit _limit = select._limit
_order_by_clauses = select._order_by_clause.clauses
select = select._generate()
select._mssql_visit = True select._mssql_visit = True
select = select.column( select = select.column(
sql.literal_column("ROW_NUMBER() OVER (ORDER BY %s)" \ sql.func.ROW_NUMBER().over(order_by=_order_by_clauses)
% orderby).label("mssql_rn") .label("mssql_rn")
).order_by(None).alias() ).order_by(None).alias()
mssql_rn = sql.column('mssql_rn') mssql_rn = sql.column('mssql_rn')
@@ -875,7 +882,8 @@ class MSSQLCompiler(compiler.SQLCompiler):
if result_map is not None: if result_map is not None:
result_map[column.name.lower()] = \ result_map[column.name.lower()] = \
(column.name, (column, ), (column.name, (column, column.name,
column.key),
column.type) column.type)
return super(MSSQLCompiler, self).\ return super(MSSQLCompiler, self).\
@@ -980,6 +988,22 @@ class MSSQLCompiler(compiler.SQLCompiler):
else: else:
return "" return ""
def update_from_clause(self, update_stmt,
from_table, extra_froms,
from_hints,
**kw):
"""Render the UPDATE..FROM clause specific to MSSQL.
In MSSQL, if the UPDATE statement involves an alias of the table to
be updated, then the table itself must be added to the FROM list as
well. Otherwise, it is optional. Here, we add it regardless.
"""
return "FROM " + ', '.join(
t._compiler_dispatch(self, asfrom=True,
fromhints=from_hints, **kw)
for t in [from_table] + extra_froms)
class MSSQLStrictCompiler(MSSQLCompiler): class MSSQLStrictCompiler(MSSQLCompiler):
"""A subclass of MSSQLCompiler which disables the usage of bind """A subclass of MSSQLCompiler which disables the usage of bind
parameters where not allowed natively by MS-SQL. parameters where not allowed natively by MS-SQL.
@@ -1431,6 +1455,7 @@ class MSDialect(default.DefaultDialect):
# Primary key constraints # Primary key constraints
s = sql.select([C.c.column_name, TC.c.constraint_type], s = sql.select([C.c.column_name, TC.c.constraint_type],
sql.and_(TC.c.constraint_name == C.c.constraint_name, sql.and_(TC.c.constraint_name == C.c.constraint_name,
TC.c.table_schema == C.c.table_schema,
C.c.table_name == tablename, C.c.table_name == tablename,
C.c.table_schema == current_schema) C.c.table_schema == current_schema)
) )
@@ -1,5 +1,5 @@
# mssql/information_schema.py # mssql/information_schema.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -15,8 +15,10 @@ class CoerceUnicode(TypeDecorator):
impl = Unicode impl = Unicode
def process_bind_param(self, value, dialect): def process_bind_param(self, value, dialect):
# Py2K
if isinstance(value, str): if isinstance(value, str):
value = value.decode(dialect.encoding) value = value.decode(dialect.encoding)
# end Py2K
return value return value
schemata = Table("SCHEMATA", ischema, schemata = Table("SCHEMATA", ischema,
+2 -2
View File
@@ -1,5 +1,5 @@
# mssql/mxodbc.py # mssql/mxodbc.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -47,7 +47,7 @@ This behavior can be controlled via
:meth:`~sqlalchemy.sql.expression.Executable.execution_options` using the :meth:`~sqlalchemy.sql.expression.Executable.execution_options` using the
``native_odbc_execute`` flag with a value of ``True`` or ``False``, where a ``native_odbc_execute`` flag with a value of ``True`` or ``False``, where a
value of ``True`` will unconditionally use native bind parameters and a value value of ``True`` will unconditionally use native bind parameters and a value
of ``False`` will uncondtionally use string-escaped parameters. of ``False`` will unconditionally use string-escaped parameters.
""" """
+1 -1
View File
@@ -1,5 +1,5 @@
# mssql/pymssql.py # mssql/pymssql.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+31 -2
View File
@@ -1,5 +1,5 @@
# mssql/pyodbc.py # mssql/pyodbc.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -80,6 +80,34 @@ the python shell. For example::
>>> urllib.quote_plus('dsn=mydsn;Database=db') >>> urllib.quote_plus('dsn=mydsn;Database=db')
'dsn%3Dmydsn%3BDatabase%3Ddb' 'dsn%3Dmydsn%3BDatabase%3Ddb'
Unicode Binds
^^^^^^^^^^^^^
The current state of PyODBC on a unix backend with FreeTDS and/or
EasySoft is poor regarding unicode; different OS platforms and versions of UnixODBC
versus IODBC versus FreeTDS/EasySoft versus PyODBC itself dramatically
alter how strings are received. The PyODBC dialect attempts to use all the information
it knows to determine whether or not a Python unicode literal can be
passed directly to the PyODBC driver or not; while SQLAlchemy can encode
these to bytestrings first, some users have reported that PyODBC mis-handles
bytestrings for certain encodings and requires a Python unicode object,
while the author has observed widespread cases where a Python unicode
is completely misinterpreted by PyODBC, particularly when dealing with
the information schema tables used in table reflection, and the value
must first be encoded to a bytestring.
It is for this reason that whether or not unicode literals for bound
parameters be sent to PyODBC can be controlled using the
``supports_unicode_binds`` parameter to ``create_engine()``. When
left at its default of ``None``, the PyODBC dialect will use its
best guess as to whether or not the driver deals with unicode literals
well. When ``False``, unicode literals will be encoded first, and when
``True`` unicode literals will be passed straight through. This is an interim
flag that hopefully should not be needed when the unicode situation stabilizes
for unix + PyODBC.
.. versionadded:: 0.7.7
``supports_unicode_binds`` parameter to ``create_engine()``\ .
""" """
@@ -217,7 +245,8 @@ class MSDialect_pyodbc(PyODBCConnector, MSDialect):
def __init__(self, description_encoding='latin-1', **params): def __init__(self, description_encoding='latin-1', **params):
super(MSDialect_pyodbc, self).__init__(**params) super(MSDialect_pyodbc, self).__init__(**params)
self.description_encoding = description_encoding self.description_encoding = description_encoding
self.use_scope_identity = self.dbapi and \ self.use_scope_identity = self.use_scope_identity and \
self.dbapi and \
hasattr(self.dbapi.Cursor, 'nextset') hasattr(self.dbapi.Cursor, 'nextset')
self._need_decimal_fix = self.dbapi and \ self._need_decimal_fix = self.dbapi and \
self._dbapi_version() < (2, 1, 8) self._dbapi_version() < (2, 1, 8)
+1 -1
View File
@@ -1,5 +1,5 @@
# mssql/zxjdbc.py # mssql/zxjdbc.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+3 -2
View File
@@ -1,11 +1,12 @@
# mysql/__init__.py # mysql/__init__.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
from sqlalchemy.dialects.mysql import base, mysqldb, oursql, \ from sqlalchemy.dialects.mysql import base, mysqldb, oursql, \
pyodbc, zxjdbc, mysqlconnector, pymysql pyodbc, zxjdbc, mysqlconnector, pymysql,\
gaerdbms
# default dialect # default dialect
base.dialect = mysqldb.dialect base.dialect = mysqldb.dialect
+39 -29
View File
@@ -1,5 +1,5 @@
# mysql/base.py # mysql/base.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -9,29 +9,11 @@
Supported Versions and Features Supported Versions and Features
------------------------------- -------------------------------
SQLAlchemy supports 6 major MySQL versions: 3.23, 4.0, 4.1, 5.0, 5.1 and 6.0, SQLAlchemy supports MySQL starting with version 4.1 through modern releases.
with capabilities increasing with more modern servers. However, no heroic measures are taken to work around major missing
Versions 4.1 and higher support the basic SQL functionality that SQLAlchemy
uses in the ORM and SQL expressions. These versions pass the applicable tests
in the suite 100%. No heroic measures are taken to work around major missing
SQL features - if your server version does not support sub-selects, for SQL features - if your server version does not support sub-selects, for
example, they won't work in SQLAlchemy either. example, they won't work in SQLAlchemy either.
Most available DBAPI drivers are supported; see below.
===================================== ===============
Feature Minimum Version
===================================== ===============
sqlalchemy.orm 4.1.1
Table Reflection 3.23.x
DDL Generation 4.1.1
utf8/Full Unicode Connections 4.1.1
Transactions 3.23.15
Two-Phase Transactions 5.0.3
Nested Transactions 5.0.3
===================================== ===============
See the official MySQL documentation for detailed information about features See the official MySQL documentation for detailed information about features
supported in any given server release. supported in any given server release.
@@ -49,13 +31,16 @@ been idle for eight hours or more. To circumvent having this issue, use the
engine = create_engine('mysql+mysqldb://...', pool_recycle=3600) engine = create_engine('mysql+mysqldb://...', pool_recycle=3600)
.. _mysql_storage_engines:
Storage Engines Storage Engines
--------------- ---------------
Most MySQL server installations have a default table type of ``MyISAM``, a Most MySQL server installations have a default table type of ``MyISAM``, a
non-transactional table type. During a transaction, non-transactional storage non-transactional table type. During a transaction, non-transactional storage
engines do not participate and continue to store table changes in autocommit engines do not participate and continue to store table changes in autocommit
mode. For fully atomic transactions, all participating tables must use a mode. For fully atomic transactions as well as support for foreign key
constraints, all participating tables must use a
transactional engine such as ``InnoDB``, ``Falcon``, ``SolidDB``, `PBXT`, etc. transactional engine such as ``InnoDB``, ``Falcon``, ``SolidDB``, `PBXT`, etc.
Storage engines can be elected when creating tables in SQLAlchemy by supplying Storage engines can be elected when creating tables in SQLAlchemy by supplying
@@ -68,6 +53,10 @@ creation option can be specified in this syntax::
mysql_charset='utf8' mysql_charset='utf8'
) )
.. seealso::
`The InnoDB Storage Engine <http://dev.mysql.com/doc/refman/5.0/en/innodb-storage-engine.html>`_ - on the MySQL website.
Case Sensitivity and Table Reflection Case Sensitivity and Table Reflection
------------------------------------- -------------------------------------
@@ -99,7 +88,7 @@ every new connection. Valid values for this parameter are
isolation_level="READ UNCOMMITTED" isolation_level="READ UNCOMMITTED"
) )
(new in 0.7.6) .. versionadded:: 0.7.6
Keys Keys
---- ----
@@ -185,6 +174,24 @@ available.
update(..., mysql_limit=10) update(..., mysql_limit=10)
rowcount Support
----------------
SQLAlchemy standardizes the DBAPI ``cursor.rowcount`` attribute to be the
usual definition of "number of rows matched by an UPDATE or DELETE" statement.
This is in contradiction to the default setting on most MySQL DBAPI drivers,
which is "number of rows actually modified/deleted". For this reason, the
SQLAlchemy MySQL dialects always set the ``constants.CLIENT.FOUND_ROWS`` flag,
or whatever is equivalent for the DBAPI in use, on connect, unless the flag value
is overridden using DBAPI-specific options
(such as ``client_flag`` for the MySQL-Python driver, ``found_rows`` for the
OurSQL driver).
See also:
:attr:`.ResultProxy.rowcount`
CAST Support CAST Support
------------ ------------
@@ -1273,11 +1280,11 @@ class MySQLCompiler(compiler.SQLCompiler):
def visit_cast(self, cast, **kwargs): def visit_cast(self, cast, **kwargs):
# No cast until 4, no decimals until 5. # No cast until 4, no decimals until 5.
if not self.dialect._supports_cast: if not self.dialect._supports_cast:
return self.process(cast.clause) return self.process(cast.clause.self_group())
type_ = self.process(cast.typeclause) type_ = self.process(cast.typeclause)
if type_ is None: if type_ is None:
return self.process(cast.clause) return self.process(cast.clause.self_group())
return 'CAST(%s AS %s)' % (self.process(cast.clause), type_) return 'CAST(%s AS %s)' % (self.process(cast.clause), type_)
@@ -1395,8 +1402,12 @@ class MySQLDDLCompiler(compiler.DDLCompiler):
auto_inc_column is not list(table.primary_key)[0]: auto_inc_column is not list(table.primary_key)[0]:
if constraint_string: if constraint_string:
constraint_string += ", \n\t" constraint_string += ", \n\t"
constraint_string += "KEY `idx_autoinc_%s`(`%s`)" % (auto_inc_column.name, \ constraint_string += "KEY %s (%s)" % (
self.preparer.format_column(auto_inc_column)) self.preparer.quote(
"idx_autoinc_%s" % auto_inc_column.name, None
),
self.preparer.format_column(auto_inc_column)
)
return constraint_string return constraint_string
@@ -2017,7 +2028,6 @@ class MySQLDialect(default.DefaultDialect):
@reflection.cache @reflection.cache
def get_view_names(self, connection, schema=None, **kw): def get_view_names(self, connection, schema=None, **kw):
charset = self._connection_charset
if self.server_version_info < (5, 0, 2): if self.server_version_info < (5, 0, 2):
raise NotImplementedError raise NotImplementedError
if schema is None: if schema is None:
@@ -2028,7 +2038,7 @@ class MySQLDialect(default.DefaultDialect):
rp = connection.execute("SHOW FULL TABLES FROM %s" % rp = connection.execute("SHOW FULL TABLES FROM %s" %
self.identifier_preparer.quote_identifier(schema)) self.identifier_preparer.quote_identifier(schema))
return [row[0] for row in self._compat_fetchall(rp, charset=charset)\ return [row[0] for row in self._compat_fetchall(rp, charset=charset)\
if row[1] == 'VIEW'] if row[1] in ('VIEW', 'SYSTEM VIEW')]
@reflection.cache @reflection.cache
def get_table_options(self, connection, table_name, schema=None, **kw): def get_table_options(self, connection, table_name, schema=None, **kw):
@@ -0,0 +1,84 @@
# mysql/gaerdbms.py
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
"""Support for Google Cloud SQL on Google App Engine.
This dialect is based primarily on the :mod:`.mysql.mysqldb` dialect with minimal
changes.
.. versionadded:: 0.7.8
Connecting
----------
Connect string format::
mysql+gaerdbms:///<dbname>
E.g.::
create_engine('mysql+gaerdbms:///mydb',
connect_args={"instance":"instancename"})
Pooling
-------
Google App Engine connections appear to be randomly recycled,
so the dialect does not pool connections. The :class:`.NullPool`
implementation is installed within the :class:`.Engine` by
default.
"""
from sqlalchemy.dialects.mysql.mysqldb import MySQLDialect_mysqldb
from sqlalchemy.pool import NullPool
import re
class MySQLDialect_gaerdbms(MySQLDialect_mysqldb):
@classmethod
def dbapi(cls):
# from django:
# http://code.google.com/p/googleappengine/source/
# browse/trunk/python/google/storage/speckle/
# python/django/backend/base.py#118
# see also [ticket:2649]
# see also http://stackoverflow.com/q/14224679/34549
from google.appengine.api import apiproxy_stub_map
if apiproxy_stub_map.apiproxy.GetStub('rdbms'):
from google.storage.speckle.python.api import rdbms_apiproxy
return rdbms_apiproxy
else:
from google.storage.speckle.python.api import rdbms_googleapi
return rdbms_googleapi
@classmethod
def get_pool_class(cls, url):
# Cloud SQL connections die at any moment
return NullPool
def create_connect_args(self, url):
opts = url.translate_connect_args()
# 'dsn' and 'instance' are because we are skipping
# the traditional google.api.rdbms wrapper
opts['dsn'] = ''
opts['instance'] = url.query['instance']
return [], opts
def _extract_error_code(self, exception):
match = re.compile(r"^(\d+):").match(str(exception))
# The rdbms api will wrap then re-raise some types of errors
# making this regex return no matches.
if match:
code = match.group(1)
else:
code = None
if code:
return int(code)
dialect = MySQLDialect_gaerdbms
@@ -1,5 +1,5 @@
# mysql/mysqlconnector.py # mysql/mysqlconnector.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -105,15 +105,11 @@ class MySQLDialect_mysqlconnector(MySQLDialect):
def _get_server_version_info(self, connection): def _get_server_version_info(self, connection):
dbapi_con = connection.connection dbapi_con = connection.connection
from mysql.connector.constants import ClientFlag
dbapi_con.set_client_flag(ClientFlag.FOUND_ROWS)
version = dbapi_con.get_server_version() version = dbapi_con.get_server_version()
return tuple(version) return tuple(version)
def _detect_charset(self, connection): def _detect_charset(self, connection):
return connection.connection.get_characterset_info() return connection.connection.charset
def _extract_error_code(self, exception): def _extract_error_code(self, exception):
return exception.errno return exception.errno
+1 -1
View File
@@ -1,5 +1,5 @@
# mysql/mysqldb.py # mysql/mysqldb.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+2 -1
View File
@@ -1,5 +1,5 @@
# mysql/oursql.py # mysql/oursql.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -208,6 +208,7 @@ class MySQLDialect_oursql(MySQLDialect):
util.coerce_kw_type(opts, 'port', int) util.coerce_kw_type(opts, 'port', int)
util.coerce_kw_type(opts, 'compress', bool) util.coerce_kw_type(opts, 'compress', bool)
util.coerce_kw_type(opts, 'autoping', bool) util.coerce_kw_type(opts, 'autoping', bool)
util.coerce_kw_type(opts, 'raise_on_warnings', bool)
util.coerce_kw_type(opts, 'default_charset', bool) util.coerce_kw_type(opts, 'default_charset', bool)
if opts.pop('default_charset', False): if opts.pop('default_charset', False):
+1 -1
View File
@@ -1,5 +1,5 @@
# mysql/pymysql.py # mysql/pymysql.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+1 -1
View File
@@ -1,5 +1,5 @@
# mysql/pyodbc.py # mysql/pyodbc.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+1 -1
View File
@@ -1,5 +1,5 @@
# mysql/zxjdbc.py # mysql/zxjdbc.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+2 -2
View File
@@ -1,5 +1,5 @@
# oracle/__init__.py # oracle/__init__.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -12,7 +12,7 @@ from sqlalchemy.dialects.oracle.base import \
VARCHAR, NVARCHAR, CHAR, DATE, DATETIME, NUMBER,\ VARCHAR, NVARCHAR, CHAR, DATE, DATETIME, NUMBER,\
BLOB, BFILE, CLOB, NCLOB, TIMESTAMP, RAW,\ BLOB, BFILE, CLOB, NCLOB, TIMESTAMP, RAW,\
FLOAT, DOUBLE_PRECISION, LONG, dialect, INTERVAL,\ FLOAT, DOUBLE_PRECISION, LONG, dialect, INTERVAL,\
VARCHAR2, NVARCHAR2 VARCHAR2, NVARCHAR2, ROWID
__all__ = ( __all__ = (
+7 -5
View File
@@ -1,5 +1,5 @@
# oracle/base.py # oracle/base.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -63,10 +63,12 @@ used on the SQLAlchemy side.
Unicode Unicode
------- -------
SQLAlchemy 0.6 uses the "native unicode" mode provided as of cx_oracle 5. cx_oracle 5.0.2 .. versionchanged:: 0.6
or greater is recommended for support of NCLOB. If not using cx_oracle 5, the NLS_LANG SQLAlchemy uses the "native unicode" mode provided as of cx_oracle 5.
environment variable needs to be set in order for the oracle client library to use cx_oracle 5.0.2 or greater is recommended for support of NCLOB.
proper encoding, such as "AMERICAN_AMERICA.UTF8". If not using cx_oracle 5, the NLS_LANG environment variable needs
to be set in order for the oracle client library to use proper encoding,
such as "AMERICAN_AMERICA.UTF8".
Also note that Oracle supports unicode data through the NVARCHAR and NCLOB data types. Also note that Oracle supports unicode data through the NVARCHAR and NCLOB data types.
When using the SQLAlchemy Unicode and UnicodeText types, these DDL types will be used When using the SQLAlchemy Unicode and UnicodeText types, these DDL types will be used
+75 -19
View File
@@ -1,5 +1,5 @@
# oracle/cx_oracle.py # oracle/cx_oracle.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -71,8 +71,40 @@ To disable this processing, pass ``auto_convert_lobs=False`` to :func:`create_en
Two Phase Transaction Support Two Phase Transaction Support
----------------------------- -----------------------------
Two Phase transactions are implemented using XA transactions. Success has been reported Two Phase transactions are implemented using XA transactions, and are known
with this feature but it should be regarded as experimental. to work in a rudimental fashion with recent versions of cx_Oracle
as of SQLAlchemy 0.8.0b2, 0.7.10. However, the mechanism is not yet
considered to be robust and should still be regarded as experimental.
In particular, the cx_Oracle DBAPI as recently as 5.1.2 has a bug regarding
two phase which prevents
a particular DBAPI connection from being consistently usable in both
prepared transactions as well as traditional DBAPI usage patterns; therefore
once a particular connection is used via :meth:`.Connection.begin_prepared`,
all subsequent usages of the underlying DBAPI connection must be within
the context of prepared transactions.
The default behavior of :class:`.Engine` is to maintain a pool of DBAPI
connections. Therefore, due to the above glitch, a DBAPI connection that has
been used in a two-phase operation, and is then returned to the pool, will
not be usable in a non-two-phase context. To avoid this situation,
the application can make one of several choices:
* Disable connection pooling using :class:`.NullPool`
* Ensure that the particular :class:`.Engine` in use is only used
for two-phase operations. A :class:`.Engine` bound to an ORM
:class:`.Session` which includes ``twophase=True`` will consistently
use the two-phase transaction style.
* For ad-hoc two-phase operations without disabling pooling, the DBAPI
connection in use can be evicted from the connection pool using the
:class:`.Connection.detach` method.
.. versionchanged:: 0.8.0b2,0.7.10
Support for cx_oracle prepared transactions has been implemented
and tested.
Precision Numerics Precision Numerics
------------------ ------------------
@@ -98,7 +130,8 @@ to :func:`.create_engine`::
engine = create_engine("oracle+cx_oracle://dsn", engine = create_engine("oracle+cx_oracle://dsn",
coerce_to_decimal=False) coerce_to_decimal=False)
The ``coerce_to_decimal`` flag is new in 0.7.6. .. versionadded:: 0.7.6
Add the ``coerce_to_decimal`` flag.
Another alternative to performance is to use the Another alternative to performance is to use the
`cdecimal <http://pypi.python.org/pypi/cdecimal/>`_ library; `cdecimal <http://pypi.python.org/pypi/cdecimal/>`_ library;
@@ -128,21 +161,23 @@ environment variable. Upon first connection, the dialect runs a
test to determine the current "decimal" character, which can be test to determine the current "decimal" character, which can be
a comma "," for european locales. From that point forward the a comma "," for european locales. From that point forward the
outputtypehandler uses that character to represent a decimal outputtypehandler uses that character to represent a decimal
point (this behavior is new in version 0.6.6). Note that point. Note that cx_oracle 5.0.3 or greater is required
cx_oracle 5.0.3 or greater is required when dealing with when dealing with numerics with locale settings that don't use
numerics with locale settings that don't use a period "." as the a period "." as the decimal character.
decimal character.
.. versionchanged:: 0.6.6
The outputtypehandler uses a comma "," character to represent
a decimal point.
.. _OCI: http://www.oracle.com/technetwork/database/features/oci/index.html .. _OCI: http://www.oracle.com/technetwork/database/features/oci/index.html
""" """
from sqlalchemy.dialects.oracle.base import OracleCompiler, OracleDialect, \ from sqlalchemy.dialects.oracle.base import OracleCompiler, OracleDialect, \
RESERVED_WORDS, OracleExecutionContext OracleExecutionContext
from sqlalchemy.dialects.oracle import base as oracle from sqlalchemy.dialects.oracle import base as oracle
from sqlalchemy.engine import base from sqlalchemy.engine import base
from sqlalchemy import types as sqltypes, util, exc, processors from sqlalchemy import types as sqltypes, util, exc, processors
from datetime import datetime
import random import random
import collections import collections
from sqlalchemy.util.compat import decimal from sqlalchemy.util.compat import decimal
@@ -248,6 +283,13 @@ class _OracleText(_LOBMixin, sqltypes.Text):
def get_dbapi_type(self, dbapi): def get_dbapi_type(self, dbapi):
return dbapi.CLOB return dbapi.CLOB
class _OracleLong(oracle.LONG):
# a raw LONG is a text type, but does *not*
# get the LobMixin with cx_oracle.
def get_dbapi_type(self, dbapi):
return dbapi.LONG_STRING
class _OracleString(_NativeUnicodeMixin, sqltypes.String): class _OracleString(_NativeUnicodeMixin, sqltypes.String):
pass pass
@@ -296,13 +338,13 @@ class _OracleRowid(oracle.ROWID):
return dbapi.ROWID return dbapi.ROWID
class OracleCompiler_cx_oracle(OracleCompiler): class OracleCompiler_cx_oracle(OracleCompiler):
def bindparam_string(self, name): def bindparam_string(self, name, **kw):
if self.preparer._bindparam_requires_quotes(name): if self.preparer._bindparam_requires_quotes(name):
quoted_name = '"%s"' % name quoted_name = '"%s"' % name
self._quoted_bind_names[name] = quoted_name self._quoted_bind_names[name] = quoted_name
return OracleCompiler.bindparam_string(self, quoted_name) return OracleCompiler.bindparam_string(self, quoted_name, **kw)
else: else:
return OracleCompiler.bindparam_string(self, name) return OracleCompiler.bindparam_string(self, name, **kw)
class OracleExecutionContext_cx_oracle(OracleExecutionContext): class OracleExecutionContext_cx_oracle(OracleExecutionContext):
@@ -333,7 +375,7 @@ class OracleExecutionContext_cx_oracle(OracleExecutionContext):
# on String, including that outparams/RETURNING # on String, including that outparams/RETURNING
# breaks for varchars # breaks for varchars
self.set_input_sizes(quoted_bind_names, self.set_input_sizes(quoted_bind_names,
exclude_types=self.dialect._cx_oracle_string_types exclude_types=self.dialect._cx_oracle_exclude_setinputsizes
) )
# if a single execute, check for outparams # if a single execute, check for outparams
@@ -467,6 +509,11 @@ class OracleDialect_cx_oracle(OracleDialect):
sqltypes.String : _OracleString, sqltypes.String : _OracleString,
sqltypes.UnicodeText : _OracleUnicodeText, sqltypes.UnicodeText : _OracleUnicodeText,
sqltypes.CHAR : _OracleChar, sqltypes.CHAR : _OracleChar,
# a raw LONG is a text type, but does *not*
# get the LobMixin with cx_oracle.
oracle.LONG: _OracleLong,
sqltypes.Integer : _OracleInteger, # this is only needed for OUT parameters. sqltypes.Integer : _OracleInteger, # this is only needed for OUT parameters.
# it would be nice if we could not use it otherwise. # it would be nice if we could not use it otherwise.
oracle.RAW: _OracleRaw, oracle.RAW: _OracleRaw,
@@ -503,6 +550,7 @@ class OracleDialect_cx_oracle(OracleDialect):
getattr(self.dbapi, name, None) for name in names getattr(self.dbapi, name, None) for name in names
]).difference([None]) ]).difference([None])
self._cx_oracle_exclude_setinputsizes = types("STRING", "UNICODE")
self._cx_oracle_string_types = types("STRING", "UNICODE", "NCLOB", "CLOB") self._cx_oracle_string_types = types("STRING", "UNICODE", "NCLOB", "CLOB")
self._cx_oracle_unicode_types = types("UNICODE", "NCLOB") self._cx_oracle_unicode_types = types("UNICODE", "NCLOB")
self._cx_oracle_binary_types = types("BFILE", "CLOB", "NCLOB", "BLOB") self._cx_oracle_binary_types = types("BFILE", "CLOB", "NCLOB", "BLOB")
@@ -557,7 +605,7 @@ class OracleDialect_cx_oracle(OracleDialect):
} }
@classmethod @classmethod
def dbapi(cls): def dbapi(cls):
import cx_Oracle cx_Oracle = __import__('cx_Oracle')
return cx_Oracle return cx_Oracle
def initialize(self, connection): def initialize(self, connection):
@@ -739,15 +787,23 @@ class OracleDialect_cx_oracle(OracleDialect):
connection.connection.begin(*xid) connection.connection.begin(*xid)
def do_prepare_twophase(self, connection, xid): def do_prepare_twophase(self, connection, xid):
connection.connection.prepare() result = connection.connection.prepare()
connection.info['cx_oracle_prepared'] = result
def do_rollback_twophase(self, connection, xid, is_prepared=True, recover=False): def do_rollback_twophase(self, connection, xid, is_prepared=True,
recover=False):
self.do_rollback(connection.connection) self.do_rollback(connection.connection)
def do_commit_twophase(self, connection, xid, is_prepared=True, recover=False): def do_commit_twophase(self, connection, xid, is_prepared=True,
recover=False):
if not is_prepared:
self.do_commit(connection.connection)
else:
oci_prepared = connection.info['cx_oracle_prepared']
if oci_prepared:
self.do_commit(connection.connection) self.do_commit(connection.connection)
def do_recover_twophase(self, connection): def do_recover_twophase(self, connection):
pass connection.info.pop('cx_oracle_prepared', None)
dialect = OracleDialect_cx_oracle dialect = OracleDialect_cx_oracle
+1 -1
View File
@@ -1,5 +1,5 @@
# oracle/zxjdbc.py # oracle/zxjdbc.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+1 -1
View File
@@ -1,5 +1,5 @@
# dialects/postgres.py # dialects/postgres.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -1,5 +1,5 @@
# postgresql/__init__.py # postgresql/__init__.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+42 -19
View File
@@ -1,5 +1,5 @@
# postgresql/base.py # postgresql/base.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -82,7 +82,8 @@ to inspect the actual value using::
SHOW search_path; SHOW search_path;
Prior to version 0.7.3, cross-schema foreign keys when the schemas .. versionchanged:: 0.7.3
Prior to this version, cross-schema foreign keys when the schemas
were also in the ``search_path`` could make an incorrect assumption were also in the ``search_path`` could make an incorrect assumption
if the schemas were explicitly stated on each :class:`.Table`. if the schemas were explicitly stated on each :class:`.Table`.
@@ -137,7 +138,7 @@ Operator Classes
PostgreSQL allows the specification of an *operator class* for each column of PostgreSQL allows the specification of an *operator class* for each column of
an index (see http://www.postgresql.org/docs/8.3/interactive/indexes-opclass.html). an index (see http://www.postgresql.org/docs/8.3/interactive/indexes-opclass.html).
The :class:`.Index` construct allows these to be specified via the ``postgresql_ops`` The :class:`.Index` construct allows these to be specified via the ``postgresql_ops``
keyword argument (new as of SQLAlchemy 0.7.2):: keyword argument::
Index('my_index', my_table.c.id, my_table.c.data, Index('my_index', my_table.c.id, my_table.c.data,
postgresql_ops={ postgresql_ops={
@@ -145,6 +146,9 @@ keyword argument (new as of SQLAlchemy 0.7.2)::
'id': 'int4_ops' 'id': 'int4_ops'
}) })
.. versionadded:: 0.7.2
``postgresql_ops`` keyword argument to :class:`.Index` construct.
Note that the keys in the ``postgresql_ops`` dictionary are the "key" name of Note that the keys in the ``postgresql_ops`` dictionary are the "key" name of
the :class:`.Column`, i.e. the name used to access it from the ``.c`` collection the :class:`.Column`, i.e. the name used to access it from the ``.c`` collection
of :class:`.Table`, which can be configured to be different than the actual of :class:`.Table`, which can be configured to be different than the actual
@@ -348,11 +352,12 @@ class ARRAY(sqltypes.MutableType, sqltypes.Concatenable, sqltypes.TypeEngine):
class should be considered mutable - this enables class should be considered mutable - this enables
"mutable types" mode in the ORM. Be sure to read the "mutable types" mode in the ORM. Be sure to read the
notes for :class:`.MutableType` regarding ORM notes for :class:`.MutableType` regarding ORM
performance implications (default changed from ``True`` in performance implications.
0.7.0).
.. note:: .. versionchanged:: 0.7.0
Default changed from ``True``\ .
.. versionchanged:: 0.7
This functionality is now superseded by the This functionality is now superseded by the
``sqlalchemy.ext.mutable`` extension described in ``sqlalchemy.ext.mutable`` extension described in
:ref:`mutable_toplevel`. :ref:`mutable_toplevel`.
@@ -362,7 +367,9 @@ class ARRAY(sqltypes.MutableType, sqltypes.Concatenable, sqltypes.TypeEngine):
as psycopg2 return lists by default. When tuples are as psycopg2 return lists by default. When tuples are
returned, the results are hashable. This flag can only returned, the results are hashable. This flag can only
be set to ``True`` when ``mutable`` is set to be set to ``True`` when ``mutable`` is set to
``False``. (new in 0.6.5) ``False``.
.. versionadded:: 0.6.5
""" """
if isinstance(item_type, ARRAY): if isinstance(item_type, ARRAY):
@@ -489,7 +496,8 @@ class ENUM(sqltypes.Enum):
the :meth:`~.postgresql.ENUM.create` and the :meth:`~.postgresql.ENUM.create` and
:meth:`~.postgresql.ENUM.drop` methods can :meth:`~.postgresql.ENUM.drop` methods can
be used to emit SQL to a target bind. be used to emit SQL to a target bind.
(new in 0.7.4)
.. versionadded:: 0.7.4
""" """
self.create_type = kw.pop("create_type", True) self.create_type = kw.pop("create_type", True)
@@ -675,6 +683,10 @@ class PGCompiler(compiler.SQLCompiler):
def for_update_clause(self, select): def for_update_clause(self, select):
if select.for_update == 'nowait': if select.for_update == 'nowait':
return " FOR UPDATE NOWAIT" return " FOR UPDATE NOWAIT"
elif select.for_update == 'read':
return " FOR SHARE"
elif select.for_update == 'read_nowait':
return " FOR SHARE NOWAIT"
else: else:
return super(PGCompiler, self).for_update_clause(select) return super(PGCompiler, self).for_update_clause(select)
@@ -1040,7 +1052,7 @@ class PGDialect(default.DefaultDialect):
if is_prepared: if is_prepared:
if recover: if recover:
#FIXME: ugly hack to get out of transaction #FIXME: ugly hack to get out of transaction
# context when commiting recoverable transactions # context when committing recoverable transactions
# Must find out a way how to make the dbapi not # Must find out a way how to make the dbapi not
# open a transaction. # open a transaction.
connection.execute("ROLLBACK") connection.execute("ROLLBACK")
@@ -1439,6 +1451,9 @@ class PGDialect(default.DefaultDialect):
table_oid = self.get_table_oid(connection, table_name, schema, table_oid = self.get_table_oid(connection, table_name, schema,
info_cache=kw.get('info_cache')) info_cache=kw.get('info_cache'))
if self.server_version_info < (8, 4):
# unnest() and generate_subscripts() both introduced in
# version 8.4
PK_SQL = """ PK_SQL = """
SELECT a.attname SELECT a.attname
FROM FROM
@@ -1447,10 +1462,20 @@ class PGDialect(default.DefaultDialect):
join pg_attribute a join pg_attribute a
on t.oid=a.attrelid and a.attnum=ANY(ix.indkey) on t.oid=a.attrelid and a.attnum=ANY(ix.indkey)
WHERE WHERE
t.oid = :table_oid and t.oid = :table_oid and ix.indisprimary = 't'
ix.indisprimary = 't' ORDER BY a.attnum
ORDER BY """
a.attnum else:
PK_SQL = """
SELECT a.attname
FROM pg_attribute a JOIN (
SELECT unnest(ix.indkey) attnum,
generate_subscripts(ix.indkey, 1) ord
FROM pg_index ix
WHERE ix.indrelid = :table_oid AND ix.indisprimary
) k ON a.attnum=k.attnum
WHERE a.attrelid = :table_oid
ORDER BY k.ord
""" """
t = sql.text(PK_SQL, typemap={'attname': sqltypes.Unicode}) t = sql.text(PK_SQL, typemap={'attname': sqltypes.Unicode})
c = connection.execute(t, table_oid=table_oid) c = connection.execute(t, table_oid=table_oid)
@@ -1610,7 +1635,6 @@ class PGDialect(default.DefaultDialect):
e.enumlabel as "label" e.enumlabel as "label"
FROM pg_catalog.pg_type t FROM pg_catalog.pg_type t
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
LEFT JOIN pg_catalog.pg_constraint r ON t.oid = r.contypid
LEFT JOIN pg_catalog.pg_enum e ON t.oid = e.enumtypid LEFT JOIN pg_catalog.pg_enum e ON t.oid = e.enumtypid
WHERE t.typtype = 'e' WHERE t.typtype = 'e'
ORDER BY "name", e.oid -- e.oid gives us label order ORDER BY "name", e.oid -- e.oid gives us label order
@@ -1625,8 +1649,8 @@ class PGDialect(default.DefaultDialect):
for enum in c.fetchall(): for enum in c.fetchall():
if enum['visible']: if enum['visible']:
# 'visible' just means whether or not the enum is in a # 'visible' just means whether or not the enum is in a
# schema that's on the search path -- or not overriden by # schema that's on the search path -- or not overridden by
# a schema with higher presedence. If it's not visible, # a schema with higher precedence. If it's not visible,
# it will be prefixed with the schema-name when it's used. # it will be prefixed with the schema-name when it's used.
name = enum['name'] name = enum['name']
else: else:
@@ -1652,7 +1676,6 @@ class PGDialect(default.DefaultDialect):
n.nspname as "schema" n.nspname as "schema"
FROM pg_catalog.pg_type t FROM pg_catalog.pg_type t
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
LEFT JOIN pg_catalog.pg_constraint r ON t.oid = r.contypid
WHERE t.typtype = 'd' WHERE t.typtype = 'd'
""" """
@@ -1665,8 +1688,8 @@ class PGDialect(default.DefaultDialect):
attype = re.search('([^\(]+)', domain['attype']).group(1) attype = re.search('([^\(]+)', domain['attype']).group(1)
if domain['visible']: if domain['visible']:
# 'visible' just means whether or not the domain is in a # 'visible' just means whether or not the domain is in a
# schema that's on the search path -- or not overriden by # schema that's on the search path -- or not overridden by
# a schema with higher presedence. If it's not visible, # a schema with higher precedence. If it's not visible,
# it will be prefixed with the schema-name when it's used. # it will be prefixed with the schema-name when it's used.
name = domain['name'] name = domain['name']
else: else:
@@ -1,5 +1,5 @@
# postgresql/pg8000.py # postgresql/pg8000.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -1,5 +1,5 @@
# postgresql/psycopg2.py # postgresql/psycopg2.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -96,8 +96,9 @@ on all new connections based on the value passed to
engine = create_engine("postgresql://user:pass@host/dbname", client_encoding='utf8') engine = create_engine("postgresql://user:pass@host/dbname", client_encoding='utf8')
This overrides the encoding specified in the Postgresql client configuration. This overrides the encoding specified in the Postgresql client configuration.
The psycopg2-specific ``client_encoding`` parameter to :func:`.create_engine` is new as of
SQLAlchemy 0.7.3. .. versionadded:: 0.7.3
The psycopg2-specific ``client_encoding`` parameter to :func:`.create_engine`.
SQLAlchemy can also be instructed to skip the usage of the psycopg2 SQLAlchemy can also be instructed to skip the usage of the psycopg2
``UNICODE`` extension and to instead utilize it's own unicode encode/decode ``UNICODE`` extension and to instead utilize it's own unicode encode/decode
@@ -371,7 +372,8 @@ class PGDialect_psycopg2(PGDialect):
# these error messages from libpq: interfaces/libpq/fe-misc.c. # these error messages from libpq: interfaces/libpq/fe-misc.c.
# TODO: these are sent through gettext in libpq and we can't # TODO: these are sent through gettext in libpq and we can't
# check within other locales - consider using connection.closed # check within other locales - consider using connection.closed
return 'closed the connection' in str(e) or \ return 'terminating connection' in str(e) or \
'closed the connection' in str(e) or \
'connection not open' in str(e) or \ 'connection not open' in str(e) or \
'could not receive data from server' in str(e) 'could not receive data from server' in str(e)
elif isinstance(e, self.dbapi.InterfaceError): elif isinstance(e, self.dbapi.InterfaceError):
@@ -1,5 +1,5 @@
# postgresql/pypostgresql.py # postgresql/pypostgresql.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -40,7 +40,7 @@ class PGDialect_pypostgresql(PGDialect):
default_paramstyle = 'pyformat' default_paramstyle = 'pyformat'
# requires trunk version to support sane rowcounts # requires trunk version to support sane rowcounts
# TODO: use dbapi version information to set this flag appropariately # TODO: use dbapi version information to set this flag appropriately
supports_sane_rowcount = True supports_sane_rowcount = True
supports_sane_multi_rowcount = False supports_sane_multi_rowcount = False
@@ -1,5 +1,5 @@
# postgresql/zxjdbc.py # postgresql/zxjdbc.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+1 -1
View File
@@ -1,5 +1,5 @@
# sqlite/__init__.py # sqlite/__init__.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+134 -35
View File
@@ -1,5 +1,5 @@
# sqlite/base.py # sqlite/base.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -53,6 +53,74 @@ connection. Valid values for this parameter are ``SERIALIZABLE`` and
See the section :ref:`pysqlite_serializable` for an important workaround See the section :ref:`pysqlite_serializable` for an important workaround
when using serializable isolation with Pysqlite. when using serializable isolation with Pysqlite.
Database Locking Behavior / Concurrency
---------------------------------------
Note that SQLite is not designed for a high level of concurrency. The database
itself, being a file, is locked completely during write operations and within
transactions, meaning exactly one connection has exclusive access to the database
during this period - all other connections will be blocked during this time.
The Python DBAPI specification also calls for a connection model that is always
in a transaction; there is no BEGIN method, only commit and rollback. This implies
that a SQLite DBAPI driver would technically allow only serialized access to a
particular database file at all times. The pysqlite driver attempts to ameliorate this by
deferring the actual BEGIN statement until the first DML (INSERT, UPDATE, or
DELETE) is received within a transaction. While this breaks serializable isolation,
it at least delays the exclusive locking inherent in SQLite's design.
SQLAlchemy's default mode of usage with the ORM is known
as "autocommit=False", which means the moment the :class:`.Session` begins to be
used, a transaction is begun. As the :class:`.Session` is used, the autoflush
feature, also on by default, will flush out pending changes to the database
before each query. The effect of this is that a :class:`.Session` used in its
default mode will often emit DML early on, long before the transaction is actually
committed. This again will have the effect of serializing access to the SQLite
database. If highly concurrent reads are desired against the SQLite database,
it is advised that the autoflush feature be disabled, and potentially even
that autocommit be re-enabled, which has the effect of each SQL statement and
flush committing changes immediately.
For more information on SQLite's lack of concurrency by design, please
see `Situations Where Another RDBMS May Work Better - High Concurrency <http://www.sqlite.org/whentouse.html>`_
near the bottom of the page.
.. _sqlite_foreign_keys:
Foreign Key Support
-------------------
SQLite supports FOREIGN KEY syntax when emitting CREATE statements for tables,
however by default these constraints have no effect on the operation
of the table.
Constraint checking on SQLite has three prerequisites:
* At least version 3.6.19 of SQLite must be in use
* The SQLite libary must be compiled *without* the SQLITE_OMIT_FOREIGN_KEY
or SQLITE_OMIT_TRIGGER symbols enabled.
* The ``PRAGMA foreign_keys = ON`` statement must be emitted on all connections
before use.
SQLAlchemy allows for the ``PRAGMA`` statement to be emitted automatically
for new connections through the usage of events::
from sqlalchemy.engine import Engine
from sqlalchemy import event
@event.listens_for(Engine, "connect")
def set_sqlite_pragma(dbapi_connection, connection_record):
cursor = dbapi_connection.cursor()
cursor.execute("PRAGMA foreign_keys=ON")
cursor.close()
.. seealso::
`SQLite Foreign Key Support <http://www.sqlite.org/foreignkeys.html>`_ -
on the SQLite web site.
:ref:`event_toplevel` - SQLAlchemy event API.
""" """
import datetime, re import datetime, re
@@ -103,13 +171,13 @@ class DATETIME(_DateTimeMixin, sqltypes.DateTime):
regexp=re.compile("(\d+)/(\d+)/(\d+) (\d+)-(\d+)-(\d+)(?:-(\d+))?") regexp=re.compile("(\d+)/(\d+)/(\d+) (\d+)-(\d+)-(\d+)(?:-(\d+))?")
) )
:param storage_format: format string which will be appled to the :param storage_format: format string which will be applied to the
tuple ``(value.year, value.month, value.day, value.hour, tuple ``(value.year, value.month, value.day, value.hour,
value.minute, value.second, value.microsecond)``, given a value.minute, value.second, value.microsecond)``, given a
Python datetime.datetime() object. Python datetime.datetime() object.
:param regexp: regular expression which will be applied to :param regexp: regular expression which will be applied to
incoming result rows. The resulting match object is appled to incoming result rows. The resulting match object is applied to
the Python datetime() constructor via ``*map(int, the Python datetime() constructor via ``*map(int,
match_obj.groups(0))``. match_obj.groups(0))``.
""" """
@@ -164,12 +232,12 @@ class DATE(_DateTimeMixin, sqltypes.Date):
regexp=re.compile("(\d+)/(\d+)/(\d+)") regexp=re.compile("(\d+)/(\d+)/(\d+)")
) )
:param storage_format: format string which will be appled to the :param storage_format: format string which will be applied to the
tuple ``(value.year, value.month, value.day)``, tuple ``(value.year, value.month, value.day)``,
given a Python datetime.date() object. given a Python datetime.date() object.
:param regexp: regular expression which will be applied to :param regexp: regular expression which will be applied to
incoming result rows. The resulting match object is appled to incoming result rows. The resulting match object is applied to
the Python date() constructor via ``*map(int, the Python date() constructor via ``*map(int,
match_obj.groups(0))``. match_obj.groups(0))``.
@@ -221,12 +289,12 @@ class TIME(_DateTimeMixin, sqltypes.Time):
regexp=re.compile("(\d+)-(\d+)-(\d+)-(?:-(\d+))?") regexp=re.compile("(\d+)-(\d+)-(\d+)-(?:-(\d+))?")
) )
:param storage_format: format string which will be appled :param storage_format: format string which will be applied
to the tuple ``(value.hour, value.minute, value.second, to the tuple ``(value.hour, value.minute, value.second,
value.microsecond)``, given a Python datetime.time() object. value.microsecond)``, given a Python datetime.time() object.
:param regexp: regular expression which will be applied to :param regexp: regular expression which will be applied to
incoming result rows. The resulting match object is appled to incoming result rows. The resulting match object is applied to
the Python time() constructor via ``*map(int, the Python time() constructor via ``*map(int,
match_obj.groups(0))``. match_obj.groups(0))``.
@@ -302,6 +370,9 @@ class SQLiteCompiler(compiler.SQLCompiler):
def visit_now_func(self, fn, **kw): def visit_now_func(self, fn, **kw):
return "CURRENT_TIMESTAMP" return "CURRENT_TIMESTAMP"
def visit_localtimestamp_func(self, func, **kw):
return 'DATETIME(CURRENT_TIMESTAMP, "localtime")'
def visit_true(self, expr, **kw): def visit_true(self, expr, **kw):
return '1' return '1'
@@ -441,6 +512,22 @@ class SQLiteIdentifierPreparer(compiler.IdentifierPreparer):
result = self.quote_schema(index.table.schema, index.table.quote_schema) + "." + result result = self.quote_schema(index.table.schema, index.table.quote_schema) + "." + result
return result return result
class SQLiteExecutionContext(default.DefaultExecutionContext):
@util.memoized_property
def _preserve_raw_colnames(self):
return self.execution_options.get("sqlite_raw_colnames", False)
def _translate_colname(self, colname):
# adjust for dotted column names. SQLite
# in the case of UNION may store col names as
# "tablename.colname"
# in cursor.description
if not self._preserve_raw_colnames and "." in colname:
return colname.split(".")[1], colname
else:
return colname, None
class SQLiteDialect(default.DefaultDialect): class SQLiteDialect(default.DefaultDialect):
name = 'sqlite' name = 'sqlite'
supports_alter = False supports_alter = False
@@ -451,6 +538,7 @@ class SQLiteDialect(default.DefaultDialect):
supports_cast = True supports_cast = True
default_paramstyle = 'qmark' default_paramstyle = 'qmark'
execution_ctx_cls = SQLiteExecutionContext
statement_compiler = SQLiteCompiler statement_compiler = SQLiteCompiler
ddl_compiler = SQLiteDDLCompiler ddl_compiler = SQLiteDDLCompiler
type_compiler = SQLiteTypeCompiler type_compiler = SQLiteTypeCompiler
@@ -462,6 +550,8 @@ class SQLiteDialect(default.DefaultDialect):
supports_cast = True supports_cast = True
supports_default_values = True supports_default_values = True
_broken_fk_pragma_quotes = False
def __init__(self, isolation_level=None, native_datetime=False, **kwargs): def __init__(self, isolation_level=None, native_datetime=False, **kwargs):
default.DefaultDialect.__init__(self, **kwargs) default.DefaultDialect.__init__(self, **kwargs)
self.isolation_level = isolation_level self.isolation_level = isolation_level
@@ -478,6 +568,12 @@ class SQLiteDialect(default.DefaultDialect):
self.supports_cast = \ self.supports_cast = \
self.dbapi.sqlite_version_info >= (3, 2, 3) self.dbapi.sqlite_version_info >= (3, 2, 3)
# see http://www.sqlalchemy.org/trac/ticket/2568
# as well as http://www.sqlite.org/src/info/600482d161
self._broken_fk_pragma_quotes = \
self.dbapi.sqlite_version_info < (3, 6, 14)
_isolation_lookup = { _isolation_lookup = {
'READ UNCOMMITTED':1, 'READ UNCOMMITTED':1,
'SERIALIZABLE':0 'SERIALIZABLE':0
@@ -525,16 +621,6 @@ class SQLiteDialect(default.DefaultDialect):
else: else:
return None return None
def _translate_colname(self, colname):
# adjust for dotted column names. SQLite
# in the case of UNION may store col names as
# "tablename.colname"
# in cursor.description
if "." in colname:
return colname.split(".")[1], colname
else:
return colname, None
@reflection.cache @reflection.cache
def get_table_names(self, connection, schema=None, **kw): def get_table_names(self, connection, schema=None, **kw):
if schema is not None: if schema is not None:
@@ -633,16 +719,21 @@ class SQLiteDialect(default.DefaultDialect):
c = _pragma_cursor( c = _pragma_cursor(
connection.execute("%stable_info(%s)" % connection.execute("%stable_info(%s)" %
(pragma, qtable))) (pragma, qtable)))
found_table = False
rows = c.fetchall()
columns = [] columns = []
while True: for row in rows:
row = c.fetchone() (name, type_, nullable, default, primary_key) = \
if row is None:
break
(name, type_, nullable, default, has_default, primary_key) = \
(row[1], row[2].upper(), not row[3], (row[1], row[2].upper(), not row[3],
row[4], row[4] is not None, row[5]) row[4], row[5])
name = re.sub(r'^\"|\"$', '', name)
columns.append(self._get_column_info(name, type_, nullable,
default, primary_key))
return columns
def _get_column_info(self, name, type_, nullable,
default, primary_key):
match = re.match(r'(\w+)(\(.*?\))?', type_) match = re.match(r'(\w+)(\(.*?\))?', type_)
if match: if match:
coltype = match.group(1) coltype = match.group(1)
@@ -660,15 +751,17 @@ class SQLiteDialect(default.DefaultDialect):
(coltype, name)) (coltype, name))
coltype = sqltypes.NullType() coltype = sqltypes.NullType()
columns.append({ if default is not None:
default = unicode(default)
return {
'name': name, 'name': name,
'type': coltype, 'type': coltype,
'nullable': nullable, 'nullable': nullable,
'default': default, 'default': default,
'autoincrement': default is None, 'autoincrement': default is None,
'primary_key': primary_key 'primary_key': primary_key
}) }
return columns
@reflection.cache @reflection.cache
def get_primary_keys(self, connection, table_name, schema=None, **kw): def get_primary_keys(self, connection, table_name, schema=None, **kw):
@@ -687,7 +780,8 @@ class SQLiteDialect(default.DefaultDialect):
else: else:
pragma = "PRAGMA " pragma = "PRAGMA "
qtable = quote(table_name) qtable = quote(table_name)
c = _pragma_cursor(connection.execute("%sforeign_key_list(%s)" % (pragma, qtable))) statement = "%sforeign_key_list(%s)" % (pragma, qtable)
c = _pragma_cursor(connection.execute(statement))
fkeys = [] fkeys = []
fks = {} fks = {}
while True: while True:
@@ -695,13 +789,19 @@ class SQLiteDialect(default.DefaultDialect):
if row is None: if row is None:
break break
(numerical_id, rtbl, lcol, rcol) = (row[0], row[2], row[3], row[4]) (numerical_id, rtbl, lcol, rcol) = (row[0], row[2], row[3], row[4])
self._parse_fk(fks, fkeys, numerical_id, rtbl, lcol, rcol)
return fkeys
def _parse_fk(self, fks, fkeys, numerical_id, rtbl, lcol, rcol):
# sqlite won't return rcol if the table # sqlite won't return rcol if the table
# was created with REFERENCES <tablename>, no col # was created with REFERENCES <tablename>, no col
if rcol is None: if rcol is None:
rcol = lcol rcol = lcol
rtbl = re.sub(r'^\"|\"$', '', rtbl)
lcol = re.sub(r'^\"|\"$', '', lcol) if self._broken_fk_pragma_quotes:
rcol = re.sub(r'^\"|\"$', '', rcol) rtbl = re.sub(r'^[\"\[`\']|[\"\]`\']$', '', rtbl)
try: try:
fk = fks[numerical_id] fk = fks[numerical_id]
except KeyError: except KeyError:
@@ -715,13 +815,11 @@ class SQLiteDialect(default.DefaultDialect):
fkeys.append(fk) fkeys.append(fk)
fks[numerical_id] = fk fks[numerical_id] = fk
# look up the table based on the given table's engine, not 'self',
# since it could be a ProxyEngine
if lcol not in fk['constrained_columns']: if lcol not in fk['constrained_columns']:
fk['constrained_columns'].append(lcol) fk['constrained_columns'].append(lcol)
if rcol not in fk['referred_columns']: if rcol not in fk['referred_columns']:
fk['referred_columns'].append(rcol) fk['referred_columns'].append(rcol)
return fkeys return fk
@reflection.cache @reflection.cache
def get_indexes(self, connection, table_name, schema=None, **kw): def get_indexes(self, connection, table_name, schema=None, **kw):
@@ -762,4 +860,5 @@ def _pragma_cursor(cursor):
if cursor.closed: if cursor.closed:
cursor.fetchone = lambda: None cursor.fetchone = lambda: None
cursor.fetchall = lambda: []
return cursor return cursor
+14 -11
View File
@@ -1,5 +1,5 @@
# sqlite/pysqlite.py # sqlite/pysqlite.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -97,13 +97,21 @@ Threading/Pooling Behavior
--------------------------- ---------------------------
Pysqlite's default behavior is to prohibit the usage of a single connection Pysqlite's default behavior is to prohibit the usage of a single connection
in more than one thread. This is controlled by the ``check_same_thread`` in more than one thread. This is originally intended to work with older versions
Pysqlite flag. This default is intended to work with older versions
of SQLite that did not support multithreaded operation under of SQLite that did not support multithreaded operation under
various circumstances. In particular, older SQLite versions various circumstances. In particular, older SQLite versions
did not allow a ``:memory:`` database to be used in multiple threads did not allow a ``:memory:`` database to be used in multiple threads
under any circumstances. under any circumstances.
Pysqlite does include a now-undocumented flag known as
``check_same_thread`` which will disable this check, however note that pysqlite
connections are still not safe to use in concurrently in multiple threads.
In particular, any statement execution calls would need to be externally
mutexed, as Pysqlite does not provide for thread-safe propagation of error
messages among other things. So while even ``:memory:`` databases can be
shared among threads in modern SQLite, Pysqlite doesn't provide enough
thread-safety to make this usage worth it.
SQLAlchemy sets up pooling to work with Pysqlite's default behavior: SQLAlchemy sets up pooling to work with Pysqlite's default behavior:
* When a ``:memory:`` SQLite database is specified, the dialect by default will use * When a ``:memory:`` SQLite database is specified, the dialect by default will use
@@ -118,16 +126,11 @@ SQLAlchemy sets up pooling to work with Pysqlite's default behavior:
prevents a connection from being used again in a different thread and works prevents a connection from being used again in a different thread and works
best with SQLite's coarse-grained file locking. best with SQLite's coarse-grained file locking.
.. note:: .. versionchanged:: 0.7
Default selection of :class:`.NullPool` for SQLite file-based databases.
The default selection of :class:`.NullPool` for SQLite file-based databases Previous versions select :class:`.SingletonThreadPool` by
is new in SQLAlchemy 0.7. Previous versions
select :class:`.SingletonThreadPool` by
default for all SQLite databases. default for all SQLite databases.
Modern versions of SQLite no longer have the threading restrictions, and assuming
the sqlite3/pysqlite library was built with SQLite's default threading mode
of "Serialized", even ``:memory:`` databases can be shared among threads.
Using a Memory Database in Multiple Threads Using a Memory Database in Multiple Threads
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+1 -1
View File
@@ -1,5 +1,5 @@
# sybase/__init__.py # sybase/__init__.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+6 -2
View File
@@ -10,8 +10,12 @@
"""Support for Sybase Adaptive Server Enterprise (ASE). """Support for Sybase Adaptive Server Enterprise (ASE).
Note that this dialect is no longer specific to Sybase iAnywhere. .. note::
ASE is the primary support platform.
The Sybase dialect functions on current SQLAlchemy versions
but is not regularly tested, and may have many issues and
caveats not currently handled. In particular, the table
and database reflection features are not implemented.
""" """
+1 -1
View File
@@ -1,5 +1,5 @@
# sybase/mxodbc.py # sybase/mxodbc.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+1 -1
View File
@@ -1,5 +1,5 @@
# sybase/pyodbc.py # sybase/pyodbc.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+1 -1
View File
@@ -52,7 +52,7 @@ class SybaseExecutionContext_pysybase(SybaseExecutionContext):
class SybaseSQLCompiler_pysybase(SybaseSQLCompiler): class SybaseSQLCompiler_pysybase(SybaseSQLCompiler):
def bindparam_string(self, name): def bindparam_string(self, name, **kw):
return "@" + name return "@" + name
class SybaseDialect_pysybase(SybaseDialect): class SybaseDialect_pysybase(SybaseDialect):
+15 -4
View File
@@ -1,5 +1,5 @@
# engine/__init__.py # engine/__init__.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -310,15 +310,26 @@ def create_engine(*args, **kwargs):
behavior of the pool, which is whether ``rollback()``, behavior of the pool, which is whether ``rollback()``,
``commit()``, or nothing is called upon connections ``commit()``, or nothing is called upon connections
being returned to the pool. See the docstring for being returned to the pool. See the docstring for
``reset_on_return`` at :class:`.Pool`. (new as of 0.7.6) ``reset_on_return`` at :class:`.Pool`.
.. versionadded:: 0.7.6
:param pool_timeout=30: number of seconds to wait before giving :param pool_timeout=30: number of seconds to wait before giving
up on getting a connection from the pool. This is only used up on getting a connection from the pool. This is only used
with :class:`~sqlalchemy.pool.QueuePool`. with :class:`~sqlalchemy.pool.QueuePool`.
:param strategy='plain': selects alternate engine implementations. :param strategy='plain': selects alternate engine implementations.
Currently available is the ``threadlocal`` Currently available are:
strategy, which is described in :ref:`threadlocal_strategy`.
* the ``threadlocal`` strategy, which is described in
:ref:`threadlocal_strategy`;
* the ``mock`` strategy, which dispatches all statement
execution to a function passed as the argument ``executor``.
See `example in the FAQ <http://www.sqlalchemy.org/trac/wiki/FAQ#HowcanIgettheCREATETABLEDROPTABLEoutputasastring>`_.
:param executor=None: a function taking arguments
``(sql, *multiparams, **params)``, to which the ``mock`` strategy will
dispatch all statement execution. Used only by ``strategy='mock'``.
""" """
+79 -35
View File
@@ -1,5 +1,5 @@
# engine/base.py # engine/base.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -663,13 +663,10 @@ class ExecutionContext(object):
raise NotImplementedError() raise NotImplementedError()
def get_rowcount(self): def get_rowcount(self):
"""Return the number of rows produced (by a SELECT query) """Return the DBAPI ``cursor.rowcount`` value, or in some
or affected (by an INSERT/UPDATE/DELETE statement). cases an interpreted value.
Note that this row count may not be properly implemented See :attr:`.ResultProxy.rowcount` for details on this.
in some dialects; this is indicated by the
``supports_sane_rowcount`` and ``supports_sane_multi_rowcount``
dialect attributes.
""" """
@@ -734,7 +731,7 @@ class Compiled(object):
def construct_params(self, params=None): def construct_params(self, params=None):
"""Return the bind params for this compiled object. """Return the bind params for this compiled object.
:param params: a dict of string/object pairs whos values will :param params: a dict of string/object pairs whose values will
override bind values compiled in to the override bind values compiled in to the
statement. statement.
""" """
@@ -978,10 +975,10 @@ class Connection(Connectable):
:ref:`postgresql_toplevel` - see those dialect's documentation :ref:`postgresql_toplevel` - see those dialect's documentation
for further info. for further info.
Note that this option necessarily affects the underying Note that this option necessarily affects the underlying
DBAPI connection for the lifespan of the originating DBAPI connection for the lifespan of the originating
:class:`.Connection`, and is not per-execution. This :class:`.Connection`, and is not per-execution. This
setting is not removed until the underying DBAPI connection setting is not removed until the underlying DBAPI connection
is returned to the connection pool, i.e. is returned to the connection pool, i.e.
the :meth:`.Connection.close` method is called. the :meth:`.Connection.close` method is called.
@@ -995,7 +992,9 @@ class Connection(Connectable):
containing percent signs (and possibly other characters) containing percent signs (and possibly other characters)
that is neutral regarding whether it's executed by the DBAPI that is neutral regarding whether it's executed by the DBAPI
or piped into a script that's later invoked by or piped into a script that's later invoked by
command line tools. New in 0.7.6. command line tools.
.. versionadded:: 0.7.6
:param stream_results: Available on: Connection, statement. :param stream_results: Available on: Connection, statement.
Indicate to the dialect that results should be Indicate to the dialect that results should be
@@ -1074,7 +1073,7 @@ class Connection(Connectable):
"""Returns self. """Returns self.
This ``Connectable`` interface method returns self, allowing This ``Connectable`` interface method returns self, allowing
Connections to be used interchangably with Engines in most Connections to be used interchangeably with Engines in most
situations that require a bind. situations that require a bind.
""" """
@@ -1084,7 +1083,7 @@ class Connection(Connectable):
"""Returns self. """Returns self.
This ``Connectable`` interface method returns self, allowing This ``Connectable`` interface method returns self, allowing
Connections to be used interchangably with Engines in most Connections to be used interchangeably with Engines in most
situations that require a bind. situations that require a bind.
""" """
@@ -1123,7 +1122,7 @@ class Connection(Connectable):
def detach(self): def detach(self):
"""Detach the underlying DB-API connection from its connection pool. """Detach the underlying DB-API connection from its connection pool.
This Connection instance will remain useable. When closed, This Connection instance will remain usable. When closed,
the DB-API connection will be literally closed and not the DB-API connection will be literally closed and not
returned to its pool. The pool will typically lazily create a returned to its pool. The pool will typically lazily create a
new connection to replace the detached connection. new connection to replace the detached connection.
@@ -1471,7 +1470,8 @@ class Connection(Connectable):
elif len(multiparams) == 1: elif len(multiparams) == 1:
zero = multiparams[0] zero = multiparams[0]
if isinstance(zero, (list, tuple)): if isinstance(zero, (list, tuple)):
if not zero or hasattr(zero[0], '__iter__'): if not zero or hasattr(zero[0], '__iter__') and \
not hasattr(zero[0], 'strip'):
return zero return zero
else: else:
return [zero] return [zero]
@@ -1480,7 +1480,8 @@ class Connection(Connectable):
else: else:
return [[zero]] return [[zero]]
else: else:
if hasattr(multiparams[0], '__iter__'): if hasattr(multiparams[0], '__iter__') and \
not hasattr(multiparams[0], 'strip'):
return multiparams return multiparams
else: else:
return [multiparams] return [multiparams]
@@ -1714,7 +1715,6 @@ class Connection(Connectable):
# create a resultproxy, get rowcount/implicit RETURNING # create a resultproxy, get rowcount/implicit RETURNING
# rows, close cursor if no further results pending # rows, close cursor if no further results pending
result = context.get_result_proxy() result = context.get_result_proxy()
if context.isinsert: if context.isinsert:
if context._is_implicit_returning: if context._is_implicit_returning:
context._fetch_implicit_returning(result) context._fetch_implicit_returning(result)
@@ -1806,10 +1806,19 @@ class Connection(Connectable):
(statement is not None and context is None) (statement is not None and context is None)
if should_wrap and context: if should_wrap and context:
if self._has_events:
self.engine.dispatch.dbapi_error(self,
cursor,
statement,
parameters,
context,
e)
context.handle_dbapi_exception(e) context.handle_dbapi_exception(e)
is_disconnect = isinstance(e, self.dialect.dbapi.Error) and \ is_disconnect = isinstance(e, self.dialect.dbapi.Error) and \
self.dialect.is_disconnect(e, self.__connection, cursor) self.dialect.is_disconnect(e, self.__connection, cursor)
if is_disconnect: if is_disconnect:
self.invalidate(e) self.invalidate(e)
self.engine.dispose() self.engine.dispose()
@@ -2130,7 +2139,7 @@ class Engine(Connectable, log.Identified):
:class:`~sqlalchemy.engine.base.Dialect` together to provide a source :class:`~sqlalchemy.engine.base.Dialect` together to provide a source
of database connectivity and behavior. of database connectivity and behavior.
An :class:`.Engine` object is instantiated publically using the An :class:`.Engine` object is instantiated publicly using the
:func:`~sqlalchemy.create_engine` function. :func:`~sqlalchemy.create_engine` function.
See also: See also:
@@ -2178,7 +2187,7 @@ class Engine(Connectable, log.Identified):
The given keys/values in \**opt are added to the The given keys/values in \**opt are added to the
default execution options that will be used for default execution options that will be used for
all connections. The initial contents of this dictionary all connections. The initial contents of this dictionary
can be sent via the ``execution_options`` paramter can be sent via the ``execution_options`` parameter
to :func:`.create_engine`. to :func:`.create_engine`.
See :meth:`.Connection.execution_options` for more See :meth:`.Connection.execution_options` for more
@@ -2232,7 +2241,7 @@ class Engine(Connectable, log.Identified):
""" """
self.pool.dispose() self.pool.dispose()
self.pool = self.pool.recreate() self.pool = self.pool._replace()
@util.deprecated("0.7", "Use the create() method on the given schema " @util.deprecated("0.7", "Use the create() method on the given schema "
"object directly, i.e. :meth:`.Table.create`, " "object directly, i.e. :meth:`.Table.create`, "
@@ -2334,7 +2343,7 @@ class Engine(Connectable, log.Identified):
close the :class:`.Connection` when that :class:`.ResultProxy` close the :class:`.Connection` when that :class:`.ResultProxy`
has exhausted all result rows. has exhausted all result rows.
New in 0.7.6. .. versionadded:: 0.7.6
See also: See also:
@@ -2346,7 +2355,11 @@ class Engine(Connectable, log.Identified):
""" """
conn = self.contextual_connect(close_with_result=close_with_result) conn = self.contextual_connect(close_with_result=close_with_result)
try:
trans = conn.begin() trans = conn.begin()
except:
conn.close()
raise
return Engine._trans_ctx(conn, trans, close_with_result) return Engine._trans_ctx(conn, trans, close_with_result)
def transaction(self, callable_, *args, **kwargs): def transaction(self, callable_, *args, **kwargs):
@@ -2710,7 +2723,7 @@ class ResultMetaData(object):
context = parent.context context = parent.context
dialect = context.dialect dialect = context.dialect
typemap = dialect.dbapi_type_map typemap = dialect.dbapi_type_map
translate_colname = dialect._translate_colname translate_colname = context._translate_colname
# high precedence key values. # high precedence key values.
primary_keymap = {} primary_keymap = {}
@@ -2907,18 +2920,41 @@ class ResultProxy(object):
def rowcount(self): def rowcount(self):
"""Return the 'rowcount' for this result. """Return the 'rowcount' for this result.
The 'rowcount' reports the number of rows affected The 'rowcount' reports the number of rows *matched*
by an UPDATE or DELETE statement. It has *no* other by the WHERE criterion of an UPDATE or DELETE statement.
uses and is not intended to provide the number of rows
present from a SELECT.
Note that this row count may not be properly implemented in some .. note::
dialects; this is indicated by
:meth:`~sqlalchemy.engine.base.ResultProxy.supports_sane_rowcount()` Notes regarding :attr:`.ResultProxy.rowcount`:
and
:meth:`~sqlalchemy.engine.base.ResultProxy.supports_sane_multi_rowcount()`.
``rowcount()`` also may not work at this time for a statement that * This attribute returns the number of rows *matched*,
uses ``returning()``. which is not necessarily the same as the number of rows
that were actually *modified* - an UPDATE statement, for example,
may have no net change on a given row if the SET values
given are the same as those present in the row already.
Such a row would be matched but not modified.
On backends that feature both styles, such as MySQL,
rowcount is configured by default to return the match
count in all cases.
* :attr:`.ResultProxy.rowcount` is *only* useful in conjunction
with an UPDATE or DELETE statement. Contrary to what the Python
DBAPI says, it does *not* return the
number of rows available from the results of a SELECT statement
as DBAPIs cannot support this functionality when rows are
unbuffered.
* :attr:`.ResultProxy.rowcount` may not be fully implemented by
all dialects. In particular, most DBAPIs do not support an
aggregate rowcount result from an executemany call.
The :meth:`.ResultProxy.supports_sane_rowcount` and
:meth:`.ResultProxy.supports_sane_multi_rowcount` methods
will report from the dialect if each usage is known to be
supported.
* Statements that use RETURNING may not return a correct
rowcount.
""" """
try: try:
@@ -3098,12 +3134,20 @@ class ResultProxy(object):
return self.context.prefetch_cols return self.context.prefetch_cols
def supports_sane_rowcount(self): def supports_sane_rowcount(self):
"""Return ``supports_sane_rowcount`` from the dialect.""" """Return ``supports_sane_rowcount`` from the dialect.
See :attr:`.ResultProxy.rowcount` for background.
"""
return self.dialect.supports_sane_rowcount return self.dialect.supports_sane_rowcount
def supports_sane_multi_rowcount(self): def supports_sane_multi_rowcount(self):
"""Return ``supports_sane_multi_rowcount`` from the dialect.""" """Return ``supports_sane_multi_rowcount`` from the dialect.
See :attr:`.ResultProxy.rowcount` for background.
"""
return self.dialect.supports_sane_multi_rowcount return self.dialect.supports_sane_multi_rowcount
+9 -7
View File
@@ -1,5 +1,5 @@
# engine/default.py # engine/default.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -96,10 +96,6 @@ class DefaultDialect(base.Dialect):
# and denormalize_name() must be provided. # and denormalize_name() must be provided.
requires_name_normalize = False requires_name_normalize = False
# a hook for SQLite's translation of
# result column names
_translate_colname = None
reflection_options = () reflection_options = ()
def __init__(self, convert_unicode=False, assert_unicode=False, def __init__(self, convert_unicode=False, assert_unicode=False,
@@ -264,7 +260,7 @@ class DefaultDialect(base.Dialect):
return insp.reflecttable(table, include_columns, exclude_columns) return insp.reflecttable(table, include_columns, exclude_columns)
def get_pk_constraint(self, conn, table_name, schema=None, **kw): def get_pk_constraint(self, conn, table_name, schema=None, **kw):
"""Compatiblity method, adapts the result of get_primary_keys() """Compatibility method, adapts the result of get_primary_keys()
for those dialects which don't implement get_pk_constraint(). for those dialects which don't implement get_pk_constraint().
""" """
@@ -341,7 +337,7 @@ class DefaultDialect(base.Dialect):
return False return False
def reset_isolation_level(self, dbapi_conn): def reset_isolation_level(self, dbapi_conn):
# default_isolation_level is read from the first conenction # default_isolation_level is read from the first connection
# after the initial set of 'isolation_level', if any, so is # after the initial set of 'isolation_level', if any, so is
# the configured default of this dialect. # the configured default of this dialect.
self.set_isolation_level(dbapi_conn, self.default_isolation_level) self.set_isolation_level(dbapi_conn, self.default_isolation_level)
@@ -355,9 +351,15 @@ class DefaultExecutionContext(base.ExecutionContext):
result_map = None result_map = None
compiled = None compiled = None
statement = None statement = None
postfetch_cols = None
prefetch_cols = None
_is_implicit_returning = False _is_implicit_returning = False
_is_explicit_returning = False _is_explicit_returning = False
# a hook for SQLite's translation of
# result column names
_translate_colname = None
@classmethod @classmethod
def _init_ddl(cls, dialect, connection, dbapi_connection, compiled_ddl): def _init_ddl(cls, dialect, connection, dbapi_connection, compiled_ddl):
"""Initialize execution context for a DDLElement construct.""" """Initialize execution context for a DDLElement construct."""
+1 -1
View File
@@ -1,5 +1,5 @@
# engine/reflection.py # engine/reflection.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+4 -4
View File
@@ -1,5 +1,5 @@
# engine/strategies.py # engine/strategies.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -24,7 +24,7 @@ strategies = {}
class EngineStrategy(object): class EngineStrategy(object):
"""An adaptor that processes input arguements and produces an Engine. """An adaptor that processes input arguments and produces an Engine.
Provides a ``create`` method that receives input arguments and Provides a ``create`` method that receives input arguments and
produces an instance of base.Engine or a subclass. produces an instance of base.Engine or a subclass.
@@ -41,7 +41,7 @@ class EngineStrategy(object):
class DefaultEngineStrategy(EngineStrategy): class DefaultEngineStrategy(EngineStrategy):
"""Base class for built-in stratgies.""" """Base class for built-in strategies."""
def create(self, name_or_url, **kwargs): def create(self, name_or_url, **kwargs):
# create url.URL object # create url.URL object
@@ -180,7 +180,7 @@ PlainEngineStrategy()
class ThreadLocalEngineStrategy(DefaultEngineStrategy): class ThreadLocalEngineStrategy(DefaultEngineStrategy):
"""Strategy for configuring an Engine with thredlocal behavior.""" """Strategy for configuring an Engine with threadlocal behavior."""
name = 'threadlocal' name = 'threadlocal'
engine_cls = threadlocal.TLEngine engine_cls = threadlocal.TLEngine
+1 -1
View File
@@ -1,5 +1,5 @@
# engine/threadlocal.py # engine/threadlocal.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+1 -1
View File
@@ -1,5 +1,5 @@
# engine/url.py # engine/url.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+109 -13
View File
@@ -1,5 +1,5 @@
# sqlalchemy/event.py # sqlalchemy/event.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -7,6 +7,7 @@
"""Base event API.""" """Base event API."""
from sqlalchemy import util, exc from sqlalchemy import util, exc
import weakref
CANCEL = util.symbol('CANCEL') CANCEL = util.symbol('CANCEL')
NO_RETVAL = util.symbol('NO_RETVAL') NO_RETVAL = util.symbol('NO_RETVAL')
@@ -69,7 +70,7 @@ def remove(target, identifier, fn):
""" """
for evt_cls in _registrars[identifier]: for evt_cls in _registrars[identifier]:
for tgt in evt_cls._accept_with(target): for tgt in evt_cls._accept_with(target):
tgt.dispatch._remove(identifier, tgt, fn, *args, **kw) tgt.dispatch._remove(identifier, tgt, fn)
return return
_registrars = util.defaultdict(list) _registrars = util.defaultdict(list)
@@ -120,7 +121,8 @@ class _Dispatch(object):
object.""" object."""
for ls in _event_descriptors(other): for ls in _event_descriptors(other):
getattr(self, ls.name)._update(ls, only_propagate=only_propagate) getattr(self, ls.name).\
for_modify(self)._update(ls, only_propagate=only_propagate)
def _event_descriptors(target): def _event_descriptors(target):
return [getattr(target, k) for k in dir(target) if _is_event_name(k)] return [getattr(target, k) for k in dir(target) if _is_event_name(k)]
@@ -180,9 +182,11 @@ class Events(object):
@classmethod @classmethod
def _listen(cls, target, identifier, fn, propagate=False, insert=False): def _listen(cls, target, identifier, fn, propagate=False, insert=False):
if insert: if insert:
getattr(target.dispatch, identifier).insert(fn, target, propagate) getattr(target.dispatch, identifier).\
for_modify(target.dispatch).insert(fn, target, propagate)
else: else:
getattr(target.dispatch, identifier).append(fn, target, propagate) getattr(target.dispatch, identifier).\
for_modify(target.dispatch).append(fn, target, propagate)
@classmethod @classmethod
def _remove(cls, target, identifier, fn): def _remove(cls, target, identifier, fn):
@@ -200,7 +204,12 @@ class _DispatchDescriptor(object):
def __init__(self, fn): def __init__(self, fn):
self.__name__ = fn.__name__ self.__name__ = fn.__name__
self.__doc__ = fn.__doc__ self.__doc__ = fn.__doc__
self._clslevel = util.defaultdict(list) self._clslevel = weakref.WeakKeyDictionary()
self._empty_listeners = weakref.WeakKeyDictionary()
def _contains(self, cls, evt):
return cls in self._clslevel and \
evt in self._clslevel[cls]
def insert(self, obj, target, propagate): def insert(self, obj, target, propagate):
assert isinstance(target, type), \ assert isinstance(target, type), \
@@ -212,6 +221,8 @@ class _DispatchDescriptor(object):
if cls is not target and cls not in self._clslevel: if cls is not target and cls not in self._clslevel:
self.update_subclass(cls) self.update_subclass(cls)
else: else:
if cls not in self._clslevel:
self._clslevel[cls] = []
self._clslevel[cls].insert(0, obj) self._clslevel[cls].insert(0, obj)
def append(self, obj, target, propagate): def append(self, obj, target, propagate):
@@ -225,9 +236,13 @@ class _DispatchDescriptor(object):
if cls is not target and cls not in self._clslevel: if cls is not target and cls not in self._clslevel:
self.update_subclass(cls) self.update_subclass(cls)
else: else:
if cls not in self._clslevel:
self._clslevel[cls] = []
self._clslevel[cls].append(obj) self._clslevel[cls].append(obj)
def update_subclass(self, target): def update_subclass(self, target):
if target not in self._clslevel:
self._clslevel[target] = []
clslevel = self._clslevel[target] clslevel = self._clslevel[target]
for cls in target.__mro__[1:]: for cls in target.__mro__[1:]:
if cls in self._clslevel: if cls in self._clslevel:
@@ -242,6 +257,7 @@ class _DispatchDescriptor(object):
while stack: while stack:
cls = stack.pop(0) cls = stack.pop(0)
stack.extend(cls.__subclasses__()) stack.extend(cls.__subclasses__())
if cls in self._clslevel:
self._clslevel[cls].remove(obj) self._clslevel[cls].remove(obj)
def clear(self): def clear(self):
@@ -250,18 +266,91 @@ class _DispatchDescriptor(object):
for dispatcher in self._clslevel.values(): for dispatcher in self._clslevel.values():
dispatcher[:] = [] dispatcher[:] = []
def for_modify(self, obj):
"""Return an event collection which can be modified.
For _DispatchDescriptor at the class level of
a dispatcher, this returns self.
"""
return self
def __get__(self, obj, cls): def __get__(self, obj, cls):
if obj is None: if obj is None:
return self return self
obj.__dict__[self.__name__] = result = \ elif obj._parent_cls in self._empty_listeners:
_ListenerCollection(self, obj._parent_cls) ret = self._empty_listeners[obj._parent_cls]
else:
self._empty_listeners[obj._parent_cls] = ret = \
_EmptyListener(self, obj._parent_cls)
# assigning it to __dict__ means
# memoized for fast re-access. but more memory.
obj.__dict__[self.__name__] = ret
return ret
class _EmptyListener(object):
"""Serves as a class-level interface to the events
served by a _DispatchDescriptor, when there are no
instance-level events present.
Is replaced by _ListenerCollection when instance-level
events are added.
"""
def __init__(self, parent, target_cls):
if target_cls not in parent._clslevel:
parent.update_subclass(target_cls)
self.parent = parent
self.parent_listeners = parent._clslevel[target_cls]
self.name = parent.__name__
self.propagate = frozenset()
self.listeners = ()
def for_modify(self, obj):
"""Return an event collection which can be modified.
For _EmptyListener at the instance level of
a dispatcher, this generates a new
_ListenerCollection, applies it to the instance,
and returns it.
"""
obj.__dict__[self.name] = result = _ListenerCollection(
self.parent, obj._parent_cls)
return result return result
def _needs_modify(self, *args, **kw):
raise NotImplementedError("need to call for_modify()")
exec_once = insert = append = remove = clear = _needs_modify
def __call__(self, *args, **kw):
"""Execute this event."""
for fn in self.parent_listeners:
fn(*args, **kw)
def __len__(self):
return len(self.parent_listeners)
def __iter__(self):
return iter(self.parent_listeners)
def __getitem__(self, index):
return (self.parent_listeners)[index]
def __nonzero__(self):
return bool(self.parent_listeners)
class _ListenerCollection(object): class _ListenerCollection(object):
"""Instance-level attributes on instances of :class:`._Dispatch`. """Instance-level attributes on instances of :class:`._Dispatch`.
Represents a collection of listeners. Represents a collection of listeners.
As of 0.7.9, _ListenerCollection is only first
created via the _EmptyListener.for_modify() method.
""" """
_exec_once = False _exec_once = False
@@ -274,6 +363,15 @@ class _ListenerCollection(object):
self.listeners = [] self.listeners = []
self.propagate = set() self.propagate = set()
def for_modify(self, obj):
"""Return an event collection which can be modified.
For _ListenerCollection at the instance level of
a dispatcher, this returns self.
"""
return self
def exec_once(self, *args, **kw): def exec_once(self, *args, **kw):
"""Execute this event, but only if it has not been """Execute this event, but only if it has not been
executed already for this collection.""" executed already for this collection."""
@@ -294,11 +392,9 @@ class _ListenerCollection(object):
# but this allows class-level listeners to be added # but this allows class-level listeners to be added
# at any point. # at any point.
# #
# alternatively, _DispatchDescriptor could notify # In the absense of instance-level listeners,
# all _ListenerCollection objects, but then we move # we stay with the _EmptyListener object when called
# to a higher memory model, i.e.weakrefs to all _ListenerCollection # at the instance level.
# objects, the _DispatchDescriptor collection repeated
# for all instances.
def __len__(self): def __len__(self):
return len(self.parent_listeners + self.listeners) return len(self.parent_listeners + self.listeners)
+31 -1
View File
@@ -1,5 +1,5 @@
# sqlalchemy/events.py # sqlalchemy/events.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -401,6 +401,36 @@ class ConnectionEvents(event.Events):
parameters, context, executemany): parameters, context, executemany):
"""Intercept low-level cursor execute() events.""" """Intercept low-level cursor execute() events."""
def dbapi_error(self, conn, cursor, statement, parameters,
context, exception):
"""Intercept a raw DBAPI error.
This event is called with the DBAPI exception instance
received from the DBAPI itself, *before* SQLAlchemy wraps the
exception with it's own exception wrappers, and before any
other operations are performed on the DBAPI cursor; the
existing transaction remains in effect as well as any state
on the cursor.
The use case here is to inject low-level exception handling
into an :class:`.Engine`, typically for logging and
debugging purposes. In general, user code should **not** modify
any state or throw any exceptions here as this will
interfere with SQLAlchemy's cleanup and error handling
routines.
Subsequent to this hook, SQLAlchemy may attempt any
number of operations on the connection/cursor, including
closing the cursor, rolling back of the transaction in the
case of connectionless execution, and disposing of the entire
connection pool if a "disconnect" was detected. The
exception is then wrapped in a SQLAlchemy DBAPI exception
wrapper and re-thrown.
.. versionadded:: 0.7.7
"""
def begin(self, conn): def begin(self, conn):
"""Intercept begin() events.""" """Intercept begin() events."""
+1 -1
View File
@@ -1,5 +1,5 @@
# sqlalchemy/exc.py # sqlalchemy/exc.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+1 -1
View File
@@ -1,5 +1,5 @@
# ext/__init__.py # ext/__init__.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+4 -4
View File
@@ -1,5 +1,5 @@
# ext/associationproxy.py # ext/associationproxy.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -141,7 +141,7 @@ class AssociationProxy(object):
"""The 'remote' :class:`.MapperProperty` referenced by this """The 'remote' :class:`.MapperProperty` referenced by this
:class:`.AssociationProxy`. :class:`.AssociationProxy`.
New in 0.7.3. .. versionadded:: 0.7.3
See also: See also:
@@ -157,7 +157,7 @@ class AssociationProxy(object):
"""The 'local' :class:`.MapperProperty` referenced by this """The 'local' :class:`.MapperProperty` referenced by this
:class:`.AssociationProxy`. :class:`.AssociationProxy`.
New in 0.7.3. .. versionadded:: 0.7.3
See also: See also:
@@ -177,7 +177,7 @@ class AssociationProxy(object):
sess.query(Parent).join(*Parent.proxied.attr) sess.query(Parent).join(*Parent.proxied.attr)
New in 0.7.3. .. versionadded:: 0.7.3
See also: See also:
+1 -1
View File
@@ -1,5 +1,5 @@
# ext/compiler.py # ext/compiler.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+32 -34
View File
@@ -1,5 +1,5 @@
# ext/declarative.py # ext/declarative.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -86,16 +86,6 @@ CREATE statements for all tables::
engine = create_engine('sqlite://') engine = create_engine('sqlite://')
Base.metadata.create_all(engine) Base.metadata.create_all(engine)
The usual techniques of associating :class:`.MetaData:` with :class:`.Engine`
apply, such as assigning to the ``bind`` attribute::
Base.metadata.bind = create_engine('sqlite://')
To associate the engine with the :func:`declarative_base` at time
of construction, the ``bind`` argument is accepted::
Base = declarative_base(bind=create_engine('sqlite://'))
:func:`declarative_base` can also receive a pre-existing :func:`declarative_base` can also receive a pre-existing
:class:`.MetaData` object, which allows a :class:`.MetaData` object, which allows a
declarative setup to be associated with an already declarative setup to be associated with an already
@@ -400,8 +390,8 @@ table::
Note that above, the ``Engineer.id`` attribute, since it shares the Note that above, the ``Engineer.id`` attribute, since it shares the
same attribute name as the ``Person.id`` attribute, will in fact same attribute name as the ``Person.id`` attribute, will in fact
represent the ``people.id`` and ``engineers.id`` columns together, and represent the ``people.id`` and ``engineers.id`` columns together,
will render inside a query as ``"people.id"``. with the "Engineer.id" column taking precedence if queried directly.
To provide the ``Engineer`` class with an attribute that represents To provide the ``Engineer`` class with an attribute that represents
only the ``engineers.id`` column, give it a different attribute name:: only the ``engineers.id`` column, give it a different attribute name::
@@ -412,6 +402,11 @@ only the ``engineers.id`` column, give it a different attribute name::
primary_key=True) primary_key=True)
primary_language = Column(String(50)) primary_language = Column(String(50))
.. versionchanged:: 0.7 joined table inheritance favors the subclass
column over that of the superclass, such as querying above
for ``Engineer.id``. Prior to 0.7 this was the reverse.
Single Table Inheritance Single Table Inheritance
~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~
@@ -506,10 +501,12 @@ before the class is built::
Using the Concrete Helpers Using the Concrete Helpers
^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
New helper classes released in 0.7.3 provides a simpler pattern for concrete inheritance. Helper classes provides a simpler pattern for concrete inheritance.
With these objects, the ``__declare_last__`` helper is used to configure the "polymorphic" With these objects, the ``__declare_last__`` helper is used to configure the "polymorphic"
loader for the mapper after all subclasses have been declared. loader for the mapper after all subclasses have been declared.
.. versionadded:: 0.7.3
An abstract base can be declared using the :class:`.AbstractConcreteBase` class:: An abstract base can be declared using the :class:`.AbstractConcreteBase` class::
from sqlalchemy.ext.declarative import AbstractConcreteBase from sqlalchemy.ext.declarative import AbstractConcreteBase
@@ -671,8 +668,7 @@ keys, as a :class:`.ForeignKey` itself contains references to columns
which can't be properly recreated at this level. For columns that which can't be properly recreated at this level. For columns that
have foreign keys, as well as for the variety of mapper-level constructs have foreign keys, as well as for the variety of mapper-level constructs
that require destination-explicit context, the that require destination-explicit context, the
:func:`~.declared_attr` decorator (renamed from ``sqlalchemy.util.classproperty`` in 0.6.5) :func:`~.declared_attr` decorator is provided so that
is provided so that
patterns common to many classes can be defined as callables:: patterns common to many classes can be defined as callables::
from sqlalchemy.ext.declarative import declared_attr from sqlalchemy.ext.declarative import declared_attr
@@ -691,6 +687,9 @@ point at which the ``User`` class is constructed, and the declarative
extension can use the resulting :class:`.Column` object as returned by extension can use the resulting :class:`.Column` object as returned by
the method without the need to copy it. the method without the need to copy it.
.. versionchanged:: > 0.6.5
Rename 0.6.5 ``sqlalchemy.util.classproperty`` into :func:`~.declared_attr`.
Columns generated by :func:`~.declared_attr` can also be Columns generated by :func:`~.declared_attr` can also be
referenced by ``__mapper_args__`` to a limited degree, currently referenced by ``__mapper_args__`` to a limited degree, currently
by ``polymorphic_on`` and ``version_id_col``, by specifying the by ``polymorphic_on`` and ``version_id_col``, by specifying the
@@ -713,7 +712,7 @@ Mixing in Relationships
Relationships created by :func:`~sqlalchemy.orm.relationship` are provided Relationships created by :func:`~sqlalchemy.orm.relationship` are provided
with declarative mixin classes exclusively using the with declarative mixin classes exclusively using the
:func:`.declared_attr` approach, eliminating any ambiguity :class:`.declared_attr` approach, eliminating any ambiguity
which could arise when copying a relationship and its possibly column-bound which could arise when copying a relationship and its possibly column-bound
contents. Below is an example which combines a foreign key column and a contents. Below is an example which combines a foreign key column and a
relationship so that two classes ``Foo`` and ``Bar`` can both be configured to relationship so that two classes ``Foo`` and ``Bar`` can both be configured to
@@ -744,7 +743,7 @@ reference a common target class via many-to-one::
primaryjoin, order_by etc. expressions should use the string forms primaryjoin, order_by etc. expressions should use the string forms
for these arguments, so that they are evaluated as late as possible. for these arguments, so that they are evaluated as late as possible.
To reference the mixin class in these expressions, use the given ``cls`` To reference the mixin class in these expressions, use the given ``cls``
to get it's name:: to get its name::
class RefTargetMixin(object): class RefTargetMixin(object):
@declared_attr @declared_attr
@@ -764,7 +763,7 @@ Like :func:`~sqlalchemy.orm.relationship`, all
:class:`~sqlalchemy.orm.interfaces.MapperProperty` subclasses such as :class:`~sqlalchemy.orm.interfaces.MapperProperty` subclasses such as
:func:`~sqlalchemy.orm.deferred`, :func:`~sqlalchemy.orm.column_property`, :func:`~sqlalchemy.orm.deferred`, :func:`~sqlalchemy.orm.column_property`,
etc. ultimately involve references to columns, and therefore, when etc. ultimately involve references to columns, and therefore, when
used with declarative mixins, have the :func:`.declared_attr` used with declarative mixins, have the :class:`.declared_attr`
requirement so that no reliance on copying is needed:: requirement so that no reliance on copying is needed::
class SomethingMixin(object): class SomethingMixin(object):
@@ -879,7 +878,7 @@ In the case of ``__table_args__`` or ``__mapper_args__``
specified with declarative mixins, you may want to combine specified with declarative mixins, you may want to combine
some parameters from several mixins with those you wish to some parameters from several mixins with those you wish to
define on the class iteself. The define on the class iteself. The
:func:`.declared_attr` decorator can be used :class:`.declared_attr` decorator can be used
here to create user-defined collation routines that pull here to create user-defined collation routines that pull
from multiple collections:: from multiple collections::
@@ -928,7 +927,7 @@ Special Directives
``__declare_last__()`` ``__declare_last__()``
~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~
The ``__declare_last__()`` hook, introduced in 0.7.3, allows definition of The ``__declare_last__()`` hook allows definition of
a class level function that is automatically called by the :meth:`.MapperEvents.after_configured` a class level function that is automatically called by the :meth:`.MapperEvents.after_configured`
event, which occurs after mappings are assumed to be completed and the 'configure' step event, which occurs after mappings are assumed to be completed and the 'configure' step
has finished:: has finished::
@@ -939,12 +938,14 @@ has finished::
"" ""
# do something with mappings # do something with mappings
.. versionadded:: 0.7.3
.. _declarative_abstract: .. _declarative_abstract:
``__abstract__`` ``__abstract__``
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
``__abstract__`` is introduced in 0.7.3 and causes declarative to skip the production ``__abstract__`` causes declarative to skip the production
of a table or mapper for the class entirely. A class can be added within a hierarchy of a table or mapper for the class entirely. A class can be added within a hierarchy
in the same way as mixin (see :ref:`declarative_mixins`), allowing subclasses to extend in the same way as mixin (see :ref:`declarative_mixins`), allowing subclasses to extend
just from the special class:: just from the special class::
@@ -982,6 +983,8 @@ The tables themselves can then be created perhaps within distinct databases::
DefaultBase.metadata.create_all(some_engine) DefaultBase.metadata.create_all(some_engine)
OtherBase.metadata_create_all(some_other_engine) OtherBase.metadata_create_all(some_other_engine)
.. versionadded:: 0.7.3
Class Constructor Class Constructor
================= =================
@@ -1322,11 +1325,9 @@ def _as_declarative(cls, classname, dict_):
if k in inherited_mapper._props: if k in inherited_mapper._props:
p = inherited_mapper._props[k] p = inherited_mapper._props[k]
if isinstance(p, ColumnProperty): if isinstance(p, ColumnProperty):
# note here we place the superclass column # note here we place the subclass column
# first. this corresponds to the # first. See [ticket:1892] for background.
# append() in mapper._configure_property(). our_stuff[k] = [col] + p.columns
# change this ordering when we do [ticket:1892]
our_stuff[k] = p.columns + [col]
cls.__mapper__ = mapper_cls(cls, cls.__mapper__ = mapper_cls(cls,
@@ -1336,11 +1337,9 @@ def _as_declarative(cls, classname, dict_):
class DeclarativeMeta(type): class DeclarativeMeta(type):
def __init__(cls, classname, bases, dict_): def __init__(cls, classname, bases, dict_):
if '_decl_class_registry' in cls.__dict__: if '_decl_class_registry' not in cls.__dict__:
return type.__init__(cls, classname, bases, dict_)
else:
_as_declarative(cls, classname, cls.__dict__) _as_declarative(cls, classname, cls.__dict__)
return type.__init__(cls, classname, bases, dict_) type.__init__(cls, classname, bases, dict_)
def __setattr__(cls, key, value): def __setattr__(cls, key, value):
if '__mapper__' in cls.__dict__: if '__mapper__' in cls.__dict__:
@@ -1493,9 +1492,8 @@ class declared_attr(property):
"""Mark a class-level method as representing the definition of """Mark a class-level method as representing the definition of
a mapped property or special declarative member name. a mapped property or special declarative member name.
.. note:: .. versionchanged:: 0.6.{2,3,4}
``@declared_attr`` is available as
@declared_attr is available as
``sqlalchemy.util.classproperty`` for SQLAlchemy versions ``sqlalchemy.util.classproperty`` for SQLAlchemy versions
0.6.2, 0.6.3, 0.6.4. 0.6.2, 0.6.3, 0.6.4.
+2 -2
View File
@@ -1,5 +1,5 @@
# ext/horizontal_shard.py # ext/horizontal_shard.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -10,7 +10,7 @@ Defines a rudimental 'horizontal sharding' system which allows a Session to
distribute queries and persistence operations across multiple databases. distribute queries and persistence operations across multiple databases.
For a usage example, see the :ref:`examples_sharding` example included in For a usage example, see the :ref:`examples_sharding` example included in
the source distrbution. the source distribution.
""" """
+79 -14
View File
@@ -1,5 +1,5 @@
# ext/hybrid.py # ext/hybrid.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -179,9 +179,16 @@ The ``length(self, value)`` method is now called upon set::
Working with Relationships Working with Relationships
-------------------------- --------------------------
There's no essential difference when creating hybrids that work with related objects as There's no essential difference when creating hybrids that work with
opposed to column-based data. The need for distinct expressions tends to be greater. related objects as opposed to column-based data. The need for distinct
Consider the following declarative mapping which relates a ``User`` to a ``SavingsAccount``:: expressions tends to be greater. Two variants of we'll illustrate
are the "join-dependent" hybrid, and the "correlated subquery" hybrid.
Join-Dependent Relationship Hybrid
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Consider the following declarative
mapping which relates a ``User`` to a ``SavingsAccount``::
from sqlalchemy import Column, Integer, ForeignKey, Numeric, String from sqlalchemy import Column, Integer, ForeignKey, Numeric, String
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
@@ -222,30 +229,88 @@ Consider the following declarative mapping which relates a ``User`` to a ``Savin
def balance(cls): def balance(cls):
return SavingsAccount.balance return SavingsAccount.balance
The above hybrid property ``balance`` works with the first ``SavingsAccount`` entry in the list of The above hybrid property ``balance`` works with the first
accounts for this user. The in-Python getter/setter methods can treat ``accounts`` as a Python ``SavingsAccount`` entry in the list of accounts for this user. The
in-Python getter/setter methods can treat ``accounts`` as a Python
list available on ``self``. list available on ``self``.
However, at the expression level, we can't travel along relationships to column attributes However, at the expression level, it's expected that the ``User`` class will be used
directly since SQLAlchemy is explicit about joins. So here, it's expected that the ``User`` class will be in an appropriate context such that an appropriate join to
used in an appropriate context such that an appropriate join to ``SavingsAccount`` will be present:: ``SavingsAccount`` will be present::
>>> print Session().query(User, User.balance).join(User.accounts).filter(User.balance > 5000) >>> print Session().query(User, User.balance).\\
SELECT "user".id AS user_id, "user".name AS user_name, account.balance AS account_balance ... join(User.accounts).filter(User.balance > 5000)
SELECT "user".id AS user_id, "user".name AS user_name,
account.balance AS account_balance
FROM "user" JOIN account ON "user".id = account.user_id FROM "user" JOIN account ON "user".id = account.user_id
WHERE account.balance > :balance_1 WHERE account.balance > :balance_1
Note however, that while the instance level accessors need to worry about whether ``self.accounts`` Note however, that while the instance level accessors need to worry
is even present, this issue expresses itself differently at the SQL expression level, where we basically about whether ``self.accounts`` is even present, this issue expresses
itself differently at the SQL expression level, where we basically
would use an outer join:: would use an outer join::
>>> from sqlalchemy import or_ >>> from sqlalchemy import or_
>>> print (Session().query(User, User.balance).outerjoin(User.accounts). >>> print (Session().query(User, User.balance).outerjoin(User.accounts).
... filter(or_(User.balance < 5000, User.balance == None))) ... filter(or_(User.balance < 5000, User.balance == None)))
SELECT "user".id AS user_id, "user".name AS user_name, account.balance AS account_balance SELECT "user".id AS user_id, "user".name AS user_name,
account.balance AS account_balance
FROM "user" LEFT OUTER JOIN account ON "user".id = account.user_id FROM "user" LEFT OUTER JOIN account ON "user".id = account.user_id
WHERE account.balance < :balance_1 OR account.balance IS NULL WHERE account.balance < :balance_1 OR account.balance IS NULL
Correlated Subquery Relationship Hybrid
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
We can, of course, forego being dependent on the enclosing query's usage
of joins in favor of the correlated
subquery, which can portably be packed into a single colunn expression.
A correlated subquery is more portable, but often performs more poorly
at the SQL level.
Using the same technique illustrated at :ref:`mapper_column_property_sql_expressions`,
we can adjust our ``SavingsAccount`` example to aggregate the balances for
*all* accounts, and use a correlated subquery for the column expression::
from sqlalchemy import Column, Integer, ForeignKey, Numeric, String
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy import select, func
Base = declarative_base()
class SavingsAccount(Base):
__tablename__ = 'account'
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('user.id'), nullable=False)
balance = Column(Numeric(15, 5))
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String(100), nullable=False)
accounts = relationship("SavingsAccount", backref="owner")
@hybrid_property
def balance(self):
return sum(acc.balance for acc in self.accounts)
@balance.expression
def balance(cls):
return select([func.sum(SavingsAccount.balance)]).\\
where(SavingsAccount.user_id==cls.id).\\
label('total_balance')
The above recipe will give us the ``balance`` column which renders
a correlated SELECT::
>>> print s.query(User).filter(User.balance > 400)
SELECT "user".id AS user_id, "user".name AS user_name
FROM "user"
WHERE (SELECT sum(account.balance) AS sum_1
FROM account
WHERE account.user_id = "user".id) > :param_1
.. _hybrid_custom_comparators: .. _hybrid_custom_comparators:
Building Custom Comparators Building Custom Comparators
+59 -26
View File
@@ -1,5 +1,5 @@
# ext/mutable.py # ext/mutable.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -180,7 +180,7 @@ not picklable, due to the fact that they contain weakrefs and function
callbacks. In our case, this is a good thing, since if this dictionary were callbacks. In our case, this is a good thing, since if this dictionary were
picklable, it could lead to an excessively large pickle size for our value picklable, it could lead to an excessively large pickle size for our value
objects that are pickled by themselves outside of the context of the parent. objects that are pickled by themselves outside of the context of the parent.
The developer responsiblity here is only to provide a ``__getstate__`` method The developer responsibility here is only to provide a ``__getstate__`` method
that excludes the :meth:`~.MutableBase._parents` collection from the pickle that excludes the :meth:`~.MutableBase._parents` collection from the pickle
stream:: stream::
@@ -217,7 +217,8 @@ be assigned an object value which represents information "composed" from one
or more columns from the underlying mapped table. The usual example is that of or more columns from the underlying mapped table. The usual example is that of
a geometric "point", and is introduced in :ref:`mapper_composite`. a geometric "point", and is introduced in :ref:`mapper_composite`.
As of SQLAlchemy 0.7, the internals of :func:`.orm.composite` have been .. versionchanged:: 0.7
The internals of :func:`.orm.composite` have been
greatly simplified and in-place mutation detection is no longer enabled by greatly simplified and in-place mutation detection is no longer enabled by
default; instead, the user-defined value must detect changes on its own and default; instead, the user-defined value must detect changes on its own and
propagate them to all owning parents. The :mod:`sqlalchemy.ext.mutable` propagate them to all owning parents. The :mod:`sqlalchemy.ext.mutable`
@@ -300,6 +301,31 @@ will flag the attribute as "dirty" on the parent object::
>>> assert v1 in sess.dirty >>> assert v1 in sess.dirty
True True
Coercing Mutable Composites
---------------------------
The :meth:`.MutableBase.coerce` method is also supported on composite types.
In the case of :class:`.MutableComposite`, the :meth:`.MutableBase.coerce`
method is only called for attribute set operations, not load operations.
Overriding the :meth:`.MutableBase.coerce` method is essentially equivalent
to using a :func:`.validates` validation routine for all attributes which
make use of the custom composite type::
class Point(MutableComposite):
# other Point methods
# ...
def coerce(cls, key, value):
if isinstance(value, tuple):
value = Point(*value)
elif not isinstance(value, Point):
raise ValueError("tuple or Point expected")
return value
.. versionadded:: 0.7.10,0.8.0b2
Support for the :meth:`.MutableBase.coerce` method in conjunction with
objects of type :class:`.MutableComposite`.
Supporting Pickling Supporting Pickling
-------------------- --------------------
@@ -327,7 +353,7 @@ pickling process of the parent's object-relational state so that the
""" """
from sqlalchemy.orm.attributes import flag_modified from sqlalchemy.orm.attributes import flag_modified
from sqlalchemy import event, types from sqlalchemy import event, types
from sqlalchemy.orm import mapper, object_mapper from sqlalchemy.orm import mapper, object_mapper, Mapper
from sqlalchemy.util import memoized_property from sqlalchemy.util import memoized_property
import weakref import weakref
@@ -348,9 +374,27 @@ class MutableBase(object):
@classmethod @classmethod
def coerce(cls, key, value): def coerce(cls, key, value):
"""Given a value, coerce it into this type. """Given a value, coerce it into the target type.
Can be overridden by custom subclasses to coerce incoming
data into a particular type.
By default, raises ``ValueError``.
This method is called in different scenarios depending on if
the parent class is of type :class:`.Mutable` or of type
:class:`.MutableComposite`. In the case of the former, it is called
for both attribute-set operations as well as during ORM loading
operations. For the latter, it is only called during attribute-set
operations; the mechanics of the :func:`.composite` construct
handle coercion during load operations.
:param key: string name of the ORM-mapped attribute being set.
:param value: the incoming value.
:return: the method should return the coerced value, or raise
``ValueError`` if the coercion cannot be completed.
By default raises ValueError.
""" """
if value is None: if value is None:
return None return None
@@ -511,11 +555,6 @@ class Mutable(MutableBase):
return sqltype return sqltype
class _MutableCompositeMeta(type):
def __init__(cls, classname, bases, dict_):
cls._setup_listeners()
return type.__init__(cls, classname, bases, dict_)
class MutableComposite(MutableBase): class MutableComposite(MutableBase):
"""Mixin that defines transparent propagation of change """Mixin that defines transparent propagation of change
events on a SQLAlchemy "composite" object to its events on a SQLAlchemy "composite" object to its
@@ -532,7 +571,6 @@ class MutableComposite(MutableBase):
in memory usage. in memory usage.
""" """
__metaclass__ = _MutableCompositeMeta
def changed(self): def changed(self):
"""Subclasses should call this method whenever change events occur.""" """Subclasses should call this method whenever change events occur."""
@@ -545,19 +583,14 @@ class MutableComposite(MutableBase):
prop._attribute_keys): prop._attribute_keys):
setattr(parent, attr_name, value) setattr(parent, attr_name, value)
@classmethod
def _setup_listeners(cls):
"""Associate this wrapper with all future mapped composites
of the given type.
This is a convenience method that calls ``associate_with_attribute`` automatically. def _setup_composite_listener():
def _listen_for_type(mapper, class_):
"""
def listen_for_type(mapper, class_):
for prop in mapper.iterate_properties: for prop in mapper.iterate_properties:
if hasattr(prop, 'composite_class') and issubclass(prop.composite_class, cls): if (hasattr(prop, 'composite_class') and
cls._listen_on_attribute(getattr(class_, prop.key), False, class_) issubclass(prop.composite_class, MutableComposite)):
prop.composite_class._listen_on_attribute(
event.listen(mapper, 'mapper_configured', listen_for_type) getattr(class_, prop.key), False, class_)
if not Mapper.dispatch.mapper_configured._contains(Mapper, _listen_for_type):
event.listen(Mapper, 'mapper_configured', _listen_for_type)
_setup_composite_listener()
+119 -74
View File
@@ -1,64 +1,77 @@
# ext/orderinglist.py # ext/orderinglist.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
"""A custom list that manages index/position information for its children. """A custom list that manages index/position information for contained
elements.
:author: Jason Kirtland :author: Jason Kirtland
``orderinglist`` is a helper for mutable ordered relationships. It will intercept ``orderinglist`` is a helper for mutable ordered relationships. It will
list operations performed on a relationship collection and automatically intercept list operations performed on a :func:`.relationship`-managed
synchronize changes in list position with an attribute on the related objects. collection and
(See :ref:`advdatamapping_entitycollections` for more information on the general pattern.) automatically synchronize changes in list position onto a target scalar
attribute.
Example: Two tables that store slides in a presentation. Each slide Example: A ``slide`` table, where each row refers to zero or more entries
has a number of bullet points, displayed in order by the 'position' in a related ``bullet`` table. The bullets within a slide are
column on the bullets table. These bullets can be inserted and re-ordered displayed in order based on the value of the ``position`` column in the
by your end users, and you need to update the 'position' column of all ``bullet`` table. As entries are reordered in memory, the value of the
affected rows when changes are made. ``position`` attribute should be updated to reflect the new sort order::
.. sourcecode:: python+sql
slides_table = Table('Slides', metadata, Base = declarative_base()
Column('id', Integer, primary_key=True),
Column('name', String))
bullets_table = Table('Bullets', metadata, class Slide(Base):
Column('id', Integer, primary_key=True), __tablename__ = 'slide'
Column('slide_id', Integer, ForeignKey('Slides.id')),
Column('position', Integer),
Column('text', String))
class Slide(object): id = Column(Integer, primary_key=True)
pass name = Column(String)
class Bullet(object):
pass
mapper(Slide, slides_table, properties={ bullets = relationship("Bullet", order_by="Bullet.position")
'bullets': relationship(Bullet, order_by=[bullets_table.c.position])
})
mapper(Bullet, bullets_table)
The standard relationship mapping will produce a list-like attribute on each Slide class Bullet(Base):
containing all related Bullets, but coping with changes in ordering is totally __tablename__ = 'bullet'
your responsibility. If you insert a Bullet into that list, there is no id = Column(Integer, primary_key=True)
magic- it won't have a position attribute unless you assign it it one, and slide_id = Column(Integer, ForeignKey('slide.id'))
you'll need to manually renumber all the subsequent Bullets in the list to position = Column(Integer)
accommodate the insert. text = Column(String)
An ``orderinglist`` can automate this and manage the 'position' attribute on all The standard relationship mapping will produce a list-like attribute on each
related bullets for you. ``Slide`` containing all related ``Bullet`` objects,
but coping with changes in ordering is not handled automatically.
When appending a ``Bullet`` into ``Slide.bullets``, the ``Bullet.position``
attribute will remain unset until manually assigned. When the ``Bullet``
is inserted into the middle of the list, the following ``Bullet`` objects
will also need to be renumbered.
.. sourcecode:: python+sql The :class:`.OrderingList` object automates this task, managing the
``position`` attribute on all ``Bullet`` objects in the collection. It is
constructed using the :func:`.ordering_list` factory::
mapper(Slide, slides_table, properties={ from sqlalchemy.ext.orderinglist import ordering_list
'bullets': relationship(Bullet,
collection_class=ordering_list('position'), Base = declarative_base()
order_by=[bullets_table.c.position])
}) class Slide(Base):
mapper(Bullet, bullets_table) __tablename__ = 'slide'
id = Column(Integer, primary_key=True)
name = Column(String)
bullets = relationship("Bullet", order_by="Bullet.position",
collection_class=ordering_list('position'))
class Bullet(Base):
__tablename__ = 'bullet'
id = Column(Integer, primary_key=True)
slide_id = Column(Integer, ForeignKey('slide.id'))
position = Column(Integer)
text = Column(String)
With the above mapping the ``Bullet.position`` attribute is managed::
s = Slide() s = Slide()
s.bullets.append(Bullet()) s.bullets.append(Bullet())
@@ -69,32 +82,29 @@ related bullets for you.
s.bullets[2].position s.bullets[2].position
>>> 2 >>> 2
Use the ``ordering_list`` function to set up the ``collection_class`` on relationships The :class:`.OrderingList` construct only works with **changes** to a collection,
(as in the mapper example above). This implementation depends on the list and not the initial load from the database, and requires that the list be
starting in the proper order, so be SURE to put an order_by on your relationship. sorted when loaded. Therefore, be sure to
specify ``order_by`` on the :func:`.relationship` against the target ordering
attribute, so that the ordering is correct when first loaded.
.. warning:: .. warning::
``ordering_list`` only provides limited functionality when a primary :class:`.OrderingList` only provides limited functionality when a primary
key column or unique column is the target of the sort. Since changing the order of key column or unique column is the target of the sort. Since changing the
entries often means that two rows must trade values, this is not possible when order of entries often means that two rows must trade values, this is not
the value is constrained by a primary key or unique constraint, since one of the rows possible when the value is constrained by a primary key or unique
would temporarily have to point to a third available value so that the other row constraint, since one of the rows would temporarily have to point to a
could take its old value. ``ordering_list`` doesn't do any of this for you, third available value so that the other row could take its old
value. :class:`.OrderingList` doesn't do any of this for you,
nor does SQLAlchemy itself. nor does SQLAlchemy itself.
``ordering_list`` takes the name of the related object's ordering attribute as :func:`.ordering_list` takes the name of the related object's ordering attribute as
an argument. By default, the zero-based integer index of the object's an argument. By default, the zero-based integer index of the object's
position in the ``ordering_list`` is synchronized with the ordering attribute: position in the :func:`.ordering_list` is synchronized with the ordering attribute:
index 0 will get position 0, index 1 position 1, etc. To start numbering at 1 index 0 will get position 0, index 1 position 1, etc. To start numbering at 1
or some other integer, provide ``count_from=1``. or some other integer, provide ``count_from=1``.
Ordering values are not limited to incrementing integers. Almost any scheme
can implemented by supplying a custom ``ordering_func`` that maps a Python list
index to any value you require.
""" """
from sqlalchemy.orm.collections import collection from sqlalchemy.orm.collections import collection
@@ -104,38 +114,55 @@ __all__ = [ 'ordering_list' ]
def ordering_list(attr, count_from=None, **kw): def ordering_list(attr, count_from=None, **kw):
"""Prepares an OrderingList factory for use in mapper definitions. """Prepares an :class:`OrderingList` factory for use in mapper definitions.
Returns an object suitable for use as an argument to a Mapper relationship's Returns an object suitable for use as an argument to a Mapper
``collection_class`` option. Arguments are: relationship's ``collection_class`` option. e.g.::
attr from sqlalchemy.ext.orderinglist import ordering_list
class Slide(Base):
__tablename__ = 'slide'
id = Column(Integer, primary_key=True)
name = Column(String)
bullets = relationship("Bullet", order_by="Bullet.position",
collection_class=ordering_list('position'))
:param attr:
Name of the mapped attribute to use for storage and retrieval of Name of the mapped attribute to use for storage and retrieval of
ordering information ordering information
count_from (optional) :param count_from:
Set up an integer-based ordering, starting at ``count_from``. For Set up an integer-based ordering, starting at ``count_from``. For
example, ``ordering_list('pos', count_from=1)`` would create a 1-based example, ``ordering_list('pos', count_from=1)`` would create a 1-based
list in SQL, storing the value in the 'pos' column. Ignored if list in SQL, storing the value in the 'pos' column. Ignored if
``ordering_func`` is supplied. ``ordering_func`` is supplied.
Passes along any keyword arguments to ``OrderingList`` constructor. Additional arguments are passed to the :class:`.OrderingList` constructor.
""" """
kw = _unsugar_count_from(count_from=count_from, **kw) kw = _unsugar_count_from(count_from=count_from, **kw)
return lambda: OrderingList(attr, **kw) return lambda: OrderingList(attr, **kw)
# Ordering utility functions # Ordering utility functions
def count_from_0(index, collection): def count_from_0(index, collection):
"""Numbering function: consecutive integers starting at 0.""" """Numbering function: consecutive integers starting at 0."""
return index return index
def count_from_1(index, collection): def count_from_1(index, collection):
"""Numbering function: consecutive integers starting at 1.""" """Numbering function: consecutive integers starting at 1."""
return index + 1 return index + 1
def count_from_n_factory(start): def count_from_n_factory(start):
"""Numbering function: consecutive integers starting at arbitrary start.""" """Numbering function: consecutive integers starting at arbitrary start."""
@@ -147,8 +174,9 @@ def count_from_n_factory(start):
pass pass
return f return f
def _unsugar_count_from(**kw): def _unsugar_count_from(**kw):
"""Builds counting functions from keywrod arguments. """Builds counting functions from keyword arguments.
Keyword argument filter, prepares a simple ``ordering_func`` from a Keyword argument filter, prepares a simple ``ordering_func`` from a
``count_from`` argument, otherwise passes ``ordering_func`` on unchanged. ``count_from`` argument, otherwise passes ``ordering_func`` on unchanged.
@@ -164,12 +192,13 @@ def _unsugar_count_from(**kw):
kw['ordering_func'] = count_from_n_factory(count_from) kw['ordering_func'] = count_from_n_factory(count_from)
return kw return kw
class OrderingList(list): class OrderingList(list):
"""A custom list that manages position information for its children. """A custom list that manages position information for its children.
See the module and __init__ documentation for more details. The The :class:`.OrderingList` object is normally set up using the
``ordering_list`` factory function is used to configure ``OrderingList`` :func:`.ordering_list` factory function, used in conjunction with
collections in ``mapper`` relationship definitions. the :func:`.relationship` function.
""" """
@@ -188,9 +217,10 @@ class OrderingList(list):
Name of the attribute that stores the object's order in the Name of the attribute that stores the object's order in the
relationship. relationship.
:param ordering_func: Optional. A function that maps the position in the Python list to a :param ordering_func: Optional. A function that maps the position in
value to store in the ``ordering_attr``. Values returned are the Python list to a value to store in the
usually (but need not be!) integers. ``ordering_attr``. Values returned are usually (but need not be!)
integers.
An ``ordering_func`` is called with two positional parameters: the An ``ordering_func`` is called with two positional parameters: the
index of the element in the list, and the list itself. index of the element in the list, and the list itself.
@@ -215,7 +245,7 @@ class OrderingList(list):
making changes, any of whom happen to load this collection even in making changes, any of whom happen to load this collection even in
passing, all of the sessions would try to "clean up" the numbering passing, all of the sessions would try to "clean up" the numbering
in their commits, possibly causing all but one to fail with a in their commits, possibly causing all but one to fail with a
concurrent modification error. Spooky action at a distance. concurrent modification error.
Recommend leaving this with the default of False, and just call Recommend leaving this with the default of False, and just call
``reorder()`` if you're doing ``append()`` operations with ``reorder()`` if you're doing ``append()`` operations with
@@ -314,9 +344,24 @@ class OrderingList(list):
self._reorder() self._reorder()
# end Py2K # end Py2K
def __reduce__(self):
return _reconstitute, (self.__class__, self.__dict__, list(self))
for func_name, func in locals().items(): for func_name, func in locals().items():
if (util.callable(func) and func.func_name == func_name and if (util.callable(func) and func.func_name == func_name and
not func.__doc__ and hasattr(list, func_name)): not func.__doc__ and hasattr(list, func_name)):
func.__doc__ = getattr(list, func_name).__doc__ func.__doc__ = getattr(list, func_name).__doc__
del func_name, func del func_name, func
def _reconstitute(cls, dict_, items):
""" Reconstitute an :class:`.OrderingList`.
This is the adjoint to :meth:`.OrderingList.__reduce__`. It is used for
unpickling :class:`.OrderingList` objects.
"""
obj = cls.__new__(cls)
obj.__dict__.update(dict_)
list.extend(obj, items)
return obj
+1 -1
View File
@@ -1,5 +1,5 @@
# ext/serializer.py # ext/serializer.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+15 -17
View File
@@ -1,22 +1,17 @@
# ext/sqlsoup.py # ext/sqlsoup.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
""" """
.. note:: .. versionchanged:: 0.8
SQLSoup is now its own project. Documentation SQLSoup is now its own project. Documentation
and project status are available at: and project status are available at:
http://pypi.python.org/pypi/sqlsoup and
http://pypi.python.org/pypi/sqlsoup http://readthedocs.org/docs/sqlsoup\ .
SQLSoup will no longer be included with SQLAlchemy.
http://readthedocs.org/docs/sqlsoup
SQLSoup will no longer be included with SQLAlchemy as of
version 0.8.
Introduction Introduction
@@ -256,8 +251,8 @@ accepts in normal mapper definition:
Advanced Use Advanced Use
============ ============
Sessions, Transations and Application Integration Sessions, Transactions and Application Integration
------------------------------------------------- ---------------------------------------------------
.. note:: .. note::
@@ -599,7 +594,7 @@ class SqlSoup(object):
self.session.flush() self.session.flush()
def rollback(self): def rollback(self):
"""Rollback the current transction. """Rollback the current transaction.
See :meth:`.Session.rollback`. See :meth:`.Session.rollback`.
@@ -642,7 +637,7 @@ class SqlSoup(object):
This is the "master" method that can be used to create any This is the "master" method that can be used to create any
configuration. configuration.
(new in 0.6.6) .. versionadded:: 0.6.6
:param attrname: String attribute name which will be :param attrname: String attribute name which will be
established as an attribute on this :class:.`.SqlSoup` established as an attribute on this :class:.`.SqlSoup`
@@ -723,8 +718,9 @@ class SqlSoup(object):
def map(self, selectable, base=None, **mapper_args): def map(self, selectable, base=None, **mapper_args):
"""Map a selectable directly. """Map a selectable directly.
.. versionchanged:: 0.6.6
The class and its mapping are not cached and will The class and its mapping are not cached and will
be discarded once dereferenced (as of 0.6.6). be discarded once dereferenced.
:param selectable: an :func:`.expression.select` construct. :param selectable: an :func:`.expression.select` construct.
:param base: a Python class which will be used as the :param base: a Python class which will be used as the
@@ -749,8 +745,9 @@ class SqlSoup(object):
"""Map a selectable directly, wrapping the """Map a selectable directly, wrapping the
selectable in a subquery with labels. selectable in a subquery with labels.
.. versionchanged:: 0.6.6
The class and its mapping are not cached and will The class and its mapping are not cached and will
be discarded once dereferenced (as of 0.6.6). be discarded once dereferenced.
:param selectable: an :func:`.expression.select` construct. :param selectable: an :func:`.expression.select` construct.
:param base: a Python class which will be used as the :param base: a Python class which will be used as the
@@ -773,8 +770,9 @@ class SqlSoup(object):
base=None, **mapper_args): base=None, **mapper_args):
"""Create an :func:`.expression.join` and map to it. """Create an :func:`.expression.join` and map to it.
.. versionchanged:: 0.6.6
The class and its mapping are not cached and will The class and its mapping are not cached and will
be discarded once dereferenced (as of 0.6.6). be discarded once dereferenced.
:param left: a mapped class or table object. :param left: a mapped class or table object.
:param right: a mapped class or table object. :param right: a mapped class or table object.
+1 -1
View File
@@ -1,5 +1,5 @@
# sqlalchemy/interfaces.py # sqlalchemy/interfaces.py
# Copyright (C) 2007-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2007-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2007 Jason Kirtland jek@discorporate.us # Copyright (C) 2007 Jason Kirtland jek@discorporate.us
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
+46 -37
View File
@@ -1,5 +1,5 @@
# orm/__init__.py # orm/__init__.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -180,10 +180,8 @@ def create_session(bind=None, **kwargs):
def relationship(argument, secondary=None, **kwargs): def relationship(argument, secondary=None, **kwargs):
"""Provide a relationship of a primary Mapper to a secondary Mapper. """Provide a relationship of a primary Mapper to a secondary Mapper.
.. note:: .. versionchanged:: 0.6
:func:`relationship` is historically known as :func:`relation`.
:func:`relationship` is historically known as
:func:`relation` prior to version 0.6.
This corresponds to a parent-child or associative table relationship. The This corresponds to a parent-child or associative table relationship. The
constructed class is an instance of :class:`.RelationshipProperty`. constructed class is an instance of :class:`.RelationshipProperty`.
@@ -299,7 +297,9 @@ def relationship(argument, secondary=None, **kwargs):
* ``delete-orphan`` - if an item of the child's type is * ``delete-orphan`` - if an item of the child's type is
detached from its parent, mark it for deletion. detached from its parent, mark it for deletion.
As of version 0.7, this option does not prevent
.. versionchanged:: 0.7
This option does not prevent
a new instance of the child object from being persisted a new instance of the child object from being persisted
without a parent to start with; to constrain against without a parent to start with; to constrain against
that case, ensure the child's foreign key column(s) that case, ensure the child's foreign key column(s)
@@ -390,12 +390,14 @@ def relationship(argument, secondary=None, **kwargs):
:param innerjoin=False: :param innerjoin=False:
when ``True``, joined eager loads will use an inner join to join when ``True``, joined eager loads will use an inner join to join
against related tables instead of an outer join. The purpose against related tables instead of an outer join. The purpose
of this option is strictly one of performance, as inner joins of this option is generally one of performance, as inner joins
generally perform better than outer joins. This flag can generally perform better than outer joins. Another reason can be
be set to ``True`` when the relationship references an object the use of ``with_lockmode``, which does not support outer joins.
via many-to-one using local foreign keys that are not nullable,
or when the reference is one-to-one or a collection that is This flag can be set to ``True`` when the relationship references an
guaranteed to have one or at least one entry. object via many-to-one using local foreign keys that are not nullable,
or when the reference is one-to-one or a collection that is guaranteed
to have one or at least one entry.
:param join_depth: :param join_depth:
when non-``None``, an integer value indicating how many levels when non-``None``, an integer value indicating how many levels
@@ -417,7 +419,9 @@ def relationship(argument, secondary=None, **kwargs):
* ``immediate`` - items should be loaded as the parents are loaded, * ``immediate`` - items should be loaded as the parents are loaded,
using a separate SELECT statement, or identity map fetch for using a separate SELECT statement, or identity map fetch for
simple many-to-one references. (new as of 0.6.5) simple many-to-one references.
.. versionadded:: 0.6.5
* ``joined`` - items should be loaded "eagerly" in the same query as * ``joined`` - items should be loaded "eagerly" in the same query as
that of the parent, using a JOIN or LEFT OUTER JOIN. Whether that of the parent, using a JOIN or LEFT OUTER JOIN. Whether
@@ -441,7 +445,7 @@ def relationship(argument, secondary=None, **kwargs):
* True - a synonym for 'select' * True - a synonym for 'select'
* False - a synonyn for 'joined' * False - a synonym for 'joined'
* None - a synonym for 'noload' * None - a synonym for 'noload'
@@ -668,7 +672,9 @@ def column_property(*cols, **kw):
flag is available for applications that make use of flag is available for applications that make use of
:func:`.attributes.get_history` or :meth:`.Session.is_modified` :func:`.attributes.get_history` or :meth:`.Session.is_modified`
which also need to know which also need to know
the "previous" value of the attribute. (new in 0.6.6) the "previous" value of the attribute.
.. versionadded:: 0.6.6
:param comparator_factory: a class which extends :param comparator_factory: a class which extends
:class:`.ColumnProperty.Comparator` which provides custom SQL clause :class:`.ColumnProperty.Comparator` which provides custom SQL clause
@@ -700,8 +706,8 @@ def column_property(*cols, **kw):
Note however that the :class:`.Session` with default expiration Note however that the :class:`.Session` with default expiration
settings still expires settings still expires
all attributes after a :meth:`.Session.commit` call, however. all attributes after a :meth:`.Session.commit` call, however.
New in 0.7.3.
.. versionadded:: 0.7.3
:param extension: :param extension:
an an
@@ -719,7 +725,7 @@ def column_property(*cols, **kw):
def composite(class_, *cols, **kwargs): def composite(class_, *cols, **kwargs):
"""Return a composite column-based property for use with a Mapper. """Return a composite column-based property for use with a Mapper.
See the mapping documention section :ref:`mapper_composite` for a full See the mapping documentation section :ref:`mapper_composite` for a full
usage example. usage example.
:param class\_: :param class\_:
@@ -732,8 +738,10 @@ def composite(class_, *cols, **kwargs):
When ``True``, indicates that the "previous" value for a When ``True``, indicates that the "previous" value for a
scalar attribute should be loaded when replaced, if not scalar attribute should be loaded when replaced, if not
already loaded. See the same flag on :func:`.column_property`. already loaded. See the same flag on :func:`.column_property`.
(This flag becomes meaningful specifically for
:func:`.composite` in 0.7 - previously it was a placeholder). .. versionchanged:: 0.7
This flag specifically becomes meaningful
- previously it was a placeholder.
:param group: :param group:
A group name for this property when marked as deferred. A group name for this property when marked as deferred.
@@ -973,7 +981,7 @@ def mapper(class_, local_table=None, *args, **params):
"polymorphic_identity":"employee" "polymorphic_identity":"employee"
} }
As of SQLAlchemy 0.7.4, it may also be specified It may also be specified
as a SQL expression, as in this example where we as a SQL expression, as in this example where we
use the :func:`.case` construct to provide a conditional use the :func:`.case` construct to provide a conditional
approach:: approach::
@@ -992,7 +1000,7 @@ def mapper(class_, local_table=None, *args, **params):
"polymorphic_identity":"employee" "polymorphic_identity":"employee"
} }
Also as of 0.7.4, it may also refer to any attribute It may also refer to any attribute
configured with :func:`.column_property`, or to the configured with :func:`.column_property`, or to the
string name of one:: string name of one::
@@ -1013,6 +1021,11 @@ def mapper(class_, local_table=None, *args, **params):
"polymorphic_identity":"employee" "polymorphic_identity":"employee"
} }
.. versionchanged:: 0.7.4
``polymorphic_on`` may be specified as a SQL expression,
or refer to any attribute configured with
:func:`.column_property`, or to the string name of one.
When setting ``polymorphic_on`` to reference an When setting ``polymorphic_on`` to reference an
attribute or expression that's not present in the attribute or expression that's not present in the
locally mapped :class:`.Table`, yet the value locally mapped :class:`.Table`, yet the value
@@ -1119,11 +1132,10 @@ def synonym(name, map_column=False, descriptor=None,
comparator_factory=None, doc=None): comparator_factory=None, doc=None):
"""Denote an attribute name as a synonym to a mapped property. """Denote an attribute name as a synonym to a mapped property.
.. note:: .. versionchanged:: 0.7
:func:`.synonym` is superseded by the :mod:`~sqlalchemy.ext.hybrid`
:func:`.synonym` is superseded as of 0.7 by extension. See the documentation for hybrids
the :mod:`~sqlalchemy.ext.hybrid` extension. See at :ref:`hybrids_toplevel`.
the documentation for hybrids at :ref:`hybrids_toplevel`.
Used with the ``properties`` dictionary sent to Used with the ``properties`` dictionary sent to
:func:`~sqlalchemy.orm.mapper`:: :func:`~sqlalchemy.orm.mapper`::
@@ -1163,9 +1175,8 @@ def comparable_property(comparator_factory, descriptor=None):
"""Provides a method of applying a :class:`.PropComparator` """Provides a method of applying a :class:`.PropComparator`
to any Python descriptor attribute. to any Python descriptor attribute.
.. note:: .. versionchanged:: 0.7
:func:`.comparable_property` is superseded by
:func:`.comparable_property` is superseded as of 0.7 by
the :mod:`~sqlalchemy.ext.hybrid` extension. See the example the :mod:`~sqlalchemy.ext.hybrid` extension. See the example
at :ref:`hybrid_custom_comparators`. at :ref:`hybrid_custom_comparators`.
@@ -1218,7 +1229,7 @@ def comparable_property(comparator_factory, descriptor=None):
Optional when used in a ``properties={}`` declaration. The Python Optional when used in a ``properties={}`` declaration. The Python
descriptor or property to layer comparison behavior on top of. descriptor or property to layer comparison behavior on top of.
The like-named descriptor will be automatically retreived from the The like-named descriptor will be automatically retrieved from the
mapped class if left blank in a ``properties`` declaration. mapped class if left blank in a ``properties`` declaration.
""" """
@@ -1267,8 +1278,7 @@ def joinedload(*keys, **kw):
"""Return a ``MapperOption`` that will convert the property of the given """Return a ``MapperOption`` that will convert the property of the given
name or series of mapped attributes into an joined eager load. name or series of mapped attributes into an joined eager load.
.. note:: .. versionchanged:: 0.6beta3
This function is known as :func:`eagerload` in all versions This function is known as :func:`eagerload` in all versions
of SQLAlchemy prior to version 0.6beta3, including the 0.5 and 0.4 of SQLAlchemy prior to version 0.6beta3, including the 0.5 and 0.4
series. :func:`eagerload` will remain available for the foreseeable series. :func:`eagerload` will remain available for the foreseeable
@@ -1278,7 +1288,7 @@ def joinedload(*keys, **kw):
examples:: examples::
# joined-load the "orders" colleciton on "User" # joined-load the "orders" collection on "User"
query(User).options(joinedload(User.orders)) query(User).options(joinedload(User.orders))
# joined-load the "keywords" collection on each "Item", # joined-load the "keywords" collection on each "Item",
@@ -1327,8 +1337,7 @@ def joinedload_all(*keys, **kw):
given dot-separated path or series of mapped attributes given dot-separated path or series of mapped attributes
into an joined eager load. into an joined eager load.
.. note:: .. versionchanged:: 0.6beta3
This function is known as :func:`eagerload_all` in all versions This function is known as :func:`eagerload_all` in all versions
of SQLAlchemy prior to version 0.6beta3, including the 0.5 and 0.4 of SQLAlchemy prior to version 0.6beta3, including the 0.5 and 0.4
series. :func:`eagerload_all` will remain available for the series. :func:`eagerload_all` will remain available for the
@@ -1381,7 +1390,7 @@ def subqueryload(*keys):
examples:: examples::
# subquery-load the "orders" colleciton on "User" # subquery-load the "orders" collection on "User"
query(User).options(subqueryload(User.orders)) query(User).options(subqueryload(User.orders))
# subquery-load the "keywords" collection on each "Item", # subquery-load the "keywords" collection on each "Item",
@@ -1478,7 +1487,7 @@ def immediateload(*keys):
See also: :func:`lazyload`, :func:`eagerload`, :func:`subqueryload` See also: :func:`lazyload`, :func:`eagerload`, :func:`subqueryload`
New as of verison 0.6.5. .. versionadded:: 0.6.5
""" """
return strategies.EagerLazyOption(keys, lazy='immediate') return strategies.EagerLazyOption(keys, lazy='immediate')
+27 -7
View File
@@ -1,5 +1,5 @@
# orm/attributes.py # orm/attributes.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -413,7 +413,7 @@ class AttributeImpl(object):
Only applies to object-based attributes. Only applies to object-based attributes.
This is an inlining of existing functionality This is an inlining of existing functionality
which roughly correponds to: which roughly corresponds to:
get_state_history( get_state_history(
state, state,
@@ -653,7 +653,7 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl):
if current is not None: if current is not None:
ret = [(instance_state(current), current)] ret = [(instance_state(current), current)]
else: else:
ret = [] ret = [(None, None)]
if self.key in state.committed_state: if self.key in state.committed_state:
original = state.committed_state[self.key] original = state.committed_state[self.key]
@@ -973,6 +973,14 @@ def backref_listeners(attribute, key, uselist):
# use easily recognizable names for stack traces # use easily recognizable names for stack traces
parent_token = attribute.impl.parent_token
def _acceptable_key_err(child_state, initiator):
raise ValueError(
"Object %s not associated with attribute of "
"type %s" % (mapperutil.state_str(child_state),
manager_of_class(initiator.class_)[initiator.key]))
def emit_backref_from_scalar_set_event(state, child, oldchild, initiator): def emit_backref_from_scalar_set_event(state, child, oldchild, initiator):
if oldchild is child: if oldchild is child:
return child return child
@@ -991,7 +999,11 @@ def backref_listeners(attribute, key, uselist):
if child is not None: if child is not None:
child_state, child_dict = instance_state(child),\ child_state, child_dict = instance_state(child),\
instance_dict(child) instance_dict(child)
child_state.manager[key].impl.append( child_impl = child_state.manager[key].impl
if initiator.parent_token is not parent_token and \
initiator.parent_token is not child_impl.parent_token:
_acceptable_key_err(state, initiator)
child_impl.append(
child_state, child_state,
child_dict, child_dict,
state.obj(), state.obj(),
@@ -1002,7 +1014,11 @@ def backref_listeners(attribute, key, uselist):
def emit_backref_from_collection_append_event(state, child, initiator): def emit_backref_from_collection_append_event(state, child, initiator):
child_state, child_dict = instance_state(child), \ child_state, child_dict = instance_state(child), \
instance_dict(child) instance_dict(child)
child_state.manager[key].impl.append( child_impl = child_state.manager[key].impl
if initiator.parent_token is not parent_token and \
initiator.parent_token is not child_impl.parent_token:
_acceptable_key_err(state, initiator)
child_impl.append(
child_state, child_state,
child_dict, child_dict,
state.obj(), state.obj(),
@@ -1014,7 +1030,11 @@ def backref_listeners(attribute, key, uselist):
if child is not None: if child is not None:
child_state, child_dict = instance_state(child),\ child_state, child_dict = instance_state(child),\
instance_dict(child) instance_dict(child)
child_state.manager[key].impl.pop( child_impl = child_state.manager[key].impl
# can't think of a path that would produce an initiator
# mismatch here, as it would require an existing collection
# mismatch.
child_impl.pop(
child_state, child_state,
child_dict, child_dict,
state.obj(), state.obj(),
@@ -1126,7 +1146,7 @@ class History(tuple):
return cls((), (), ()) return cls((), (), ())
else: else:
return cls((), [current], ()) return cls((), [current], ())
# dont let ClauseElement expressions here trip things up # don't let ClauseElement expressions here trip things up
elif attribute.is_equal(current, original) is True: elif attribute.is_equal(current, original) is True:
return cls((), [current], ()) return cls((), [current], ())
else: else:
+86 -9
View File
@@ -1,5 +1,5 @@
# orm/collections.py # orm/collections.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -111,22 +111,57 @@ import weakref
from sqlalchemy.sql import expression from sqlalchemy.sql import expression
from sqlalchemy import schema, util, exc as sa_exc from sqlalchemy import schema, util, exc as sa_exc
__all__ = ['collection', 'collection_adapter', __all__ = ['collection', 'collection_adapter',
'mapped_collection', 'column_mapped_collection', 'mapped_collection', 'column_mapped_collection',
'attribute_mapped_collection'] 'attribute_mapped_collection']
__instrumentation_mutex = util.threading.Lock() __instrumentation_mutex = util.threading.Lock()
class _PlainColumnGetter(object):
"""Plain column getter, stores collection of Column objects
directly.
Serializes to a :class:`._SerializableColumnGetterV2`
which has more expensive __call__() performance
and some rare caveats.
"""
def __init__(self, cols):
self.cols = cols
self.composite = len(cols) > 1
def __reduce__(self):
return _SerializableColumnGetterV2._reduce_from_cols(self.cols)
def _cols(self, mapper):
return self.cols
def __call__(self, value):
state = instance_state(value)
m = _state_mapper(state)
key = [
m._get_state_attr_by_column(state, state.dict, col)
for col in self._cols(m)
]
if self.composite:
return tuple(key)
else:
return key[0]
class _SerializableColumnGetter(object): class _SerializableColumnGetter(object):
"""Column-based getter used in version 0.7.6 only.
Remains here for pickle compatibility with 0.7.6.
"""
def __init__(self, colkeys): def __init__(self, colkeys):
self.colkeys = colkeys self.colkeys = colkeys
self.composite = len(colkeys) > 1 self.composite = len(colkeys) > 1
def __reduce__(self): def __reduce__(self):
return _SerializableColumnGetter, (self.colkeys,) return _SerializableColumnGetter, (self.colkeys,)
def __call__(self, value): def __call__(self, value):
state = instance_state(value) state = instance_state(value)
m = _state_mapper(state) m = _state_mapper(state)
@@ -139,6 +174,48 @@ class _SerializableColumnGetter(object):
else: else:
return key[0] return key[0]
class _SerializableColumnGetterV2(_PlainColumnGetter):
"""Updated serializable getter which deals with
multi-table mapped classes.
Two extremely unusual cases are not supported.
Mappings which have tables across multiple metadata
objects, or which are mapped to non-Table selectables
linked across inheriting mappers may fail to function
here.
"""
def __init__(self, colkeys):
self.colkeys = colkeys
self.composite = len(colkeys) > 1
def __reduce__(self):
return self.__class__, (self.colkeys,)
@classmethod
def _reduce_from_cols(cls, cols):
def _table_key(c):
if not isinstance(c.table, expression.TableClause):
return None
else:
return c.table.key
colkeys = [(c.key, _table_key(c)) for c in cols]
return _SerializableColumnGetterV2, (colkeys,)
def _cols(self, mapper):
cols = []
metadata = getattr(mapper.local_table, 'metadata', None)
for (ckey, tkey) in self.colkeys:
if tkey is None or \
metadata is None or \
tkey not in metadata:
cols.append(mapper.local_table.c[ckey])
else:
cols.append(metadata.tables[tkey].c[ckey])
return cols
def column_mapped_collection(mapping_spec): def column_mapped_collection(mapping_spec):
"""A dictionary-based collection type with column-based keying. """A dictionary-based collection type with column-based keying.
@@ -155,10 +232,10 @@ def column_mapped_collection(mapping_spec):
from sqlalchemy.orm.util import _state_mapper from sqlalchemy.orm.util import _state_mapper
from sqlalchemy.orm.attributes import instance_state from sqlalchemy.orm.attributes import instance_state
cols = [c.key for c in [ cols = [expression._only_column_elements(q, "mapping_spec")
expression._only_column_elements(q, "mapping_spec") for q in util.to_list(mapping_spec)
for q in util.to_list(mapping_spec)]] ]
keyfunc = _SerializableColumnGetter(cols) keyfunc = _PlainColumnGetter(cols)
return lambda: MappedCollection(keyfunc) return lambda: MappedCollection(keyfunc)
class _SerializableAttrGetter(object): class _SerializableAttrGetter(object):
+1 -1
View File
@@ -1,5 +1,5 @@
# orm/dependency.py # orm/dependency.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+3 -2
View File
@@ -1,5 +1,5 @@
# orm/deprecated_interfaces.py # orm/deprecated_interfaces.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -247,7 +247,8 @@ class MapperExtension(object):
instance population will not proceed, giving this extension an instance population will not proceed, giving this extension an
opportunity to populate the instance itself, if desired. opportunity to populate the instance itself, if desired.
As of 0.5, most usages of this hook are obsolete. For a .. deprecated:: 0.5
Most usages of this hook are obsolete. For a
generic "object has been newly created from a row" hook, use generic "object has been newly created from a row" hook, use
``reconstruct_instance()``, or the ``@orm.reconstructor`` ``reconstruct_instance()``, or the ``@orm.reconstructor``
decorator. decorator.
+1 -1
View File
@@ -1,5 +1,5 @@
# orm/descriptor_props.py # orm/descriptor_props.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+7 -4
View File
@@ -1,5 +1,5 @@
# orm/dynamic.py # orm/dynamic.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -12,7 +12,6 @@ basic add/delete mutation.
""" """
from sqlalchemy import log, util from sqlalchemy import log, util
from sqlalchemy import exc as sa_exc
from sqlalchemy.orm import exc as orm_exc from sqlalchemy.orm import exc as orm_exc
from sqlalchemy.sql import operators from sqlalchemy.sql import operators
from sqlalchemy.orm import ( from sqlalchemy.orm import (
@@ -20,12 +19,16 @@ from sqlalchemy.orm import (
) )
from sqlalchemy.orm.query import Query from sqlalchemy.orm.query import Query
from sqlalchemy.orm.util import has_identity from sqlalchemy.orm.util import has_identity
from sqlalchemy.orm import attributes, collections from sqlalchemy.orm import collections
class DynaLoader(strategies.AbstractRelationshipLoader): class DynaLoader(strategies.AbstractRelationshipLoader):
def init_class_attribute(self, mapper): def init_class_attribute(self, mapper):
self.is_class_level = True self.is_class_level = True
if not self.uselist:
util.warn(
"On relationship %s, 'dynamic' loaders cannot be used with "
"many-to-one/one-to-one relationships and/or "
"uselist=False." % self.parent_property)
strategies._register_attribute(self, strategies._register_attribute(self,
mapper, mapper,
useobject=True, useobject=True,
+1 -1
View File
@@ -1,5 +1,5 @@
# orm/evaluator.py # orm/evaluator.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+12 -4
View File
@@ -1,5 +1,5 @@
# orm/events.py # orm/events.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -388,8 +388,16 @@ class MapperEvents(event.Events):
def mapper_configured(self, mapper, class_): def mapper_configured(self, mapper, class_):
"""Called when the mapper for the class is fully configured. """Called when the mapper for the class is fully configured.
This event is the latest phase of mapper construction. This event is the latest phase of mapper construction, and
The mapper should be in its final state. is invoked when the mapped classes are first used, so that relationships
between mappers can be resolved. When the event is called,
the mapper should be in its final state.
While the configuration event normally occurs automatically,
it can be forced to occur ahead of time, in the case where the event
is needed before any actual mapper usage, by using the
:func:`.configure_mappers` function.
:param mapper: the :class:`.Mapper` which is the target :param mapper: the :class:`.Mapper` which is the target
of this event. of this event.
@@ -1007,7 +1015,7 @@ class SessionEvents(event.Events):
for the given :class:`.Session` is available via the for the given :class:`.Session` is available via the
:attr:`.Session.transaction` attribute. :attr:`.Session.transaction` attribute.
New in 0.7.3. .. versionadded:: 0.7.3
""" """
+4 -2
View File
@@ -1,5 +1,5 @@
# orm/exc.py # orm/exc.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -32,7 +32,9 @@ class StaleDataError(sa.exc.SQLAlchemyError):
the object was previously attached to a different parent the object was previously attached to a different parent
identity which was garbage collected, and a decision identity which was garbage collected, and a decision
cannot be made if the new parent was really the most cannot be made if the new parent was really the most
recent "parent" (new in 0.7.4). recent "parent".
.. versionadded:: 0.7.4
""" """
+1 -1
View File
@@ -1,5 +1,5 @@
# orm/identity.py # orm/identity.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+1 -1
View File
@@ -1,5 +1,5 @@
# orm/instrumentation.py # orm/instrumentation.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+1 -1
View File
@@ -1,5 +1,5 @@
# orm/interfaces.py # orm/interfaces.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+19 -4
View File
@@ -1,5 +1,5 @@
# orm/mapper.py # orm/mapper.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -580,6 +580,12 @@ class Mapper(object):
self.inherits._inheriting_mappers.add(self) self.inherits._inheriting_mappers.add(self)
self.passive_updates = self.inherits.passive_updates self.passive_updates = self.inherits.passive_updates
self._all_tables = self.inherits._all_tables self._all_tables = self.inherits._all_tables
for key, prop in mapper._props.iteritems():
if key not in self._props and \
not self._should_exclude(key, key, local=False,
column=None):
self._adapt_inherited_property(key, prop, False)
def _set_polymorphic_on(self, polymorphic_on): def _set_polymorphic_on(self, polymorphic_on):
self.polymorphic_on = polymorphic_on self.polymorphic_on = polymorphic_on
@@ -678,9 +684,10 @@ class Mapper(object):
self._reconstructor = method self._reconstructor = method
event.listen(manager, 'load', _event_on_load, raw=True) event.listen(manager, 'load', _event_on_load, raw=True)
elif hasattr(method, '__sa_validators__'): elif hasattr(method, '__sa_validators__'):
include_removes = getattr(method, "__sa_include_removes__", False)
for name in method.__sa_validators__: for name in method.__sa_validators__:
self.validators = self.validators.union( self.validators = self.validators.union(
{name : method} {name : (method, include_removes)}
) )
manager.info[_INSTRUMENTOR] = self manager.info[_INSTRUMENTOR] = self
@@ -940,7 +947,6 @@ class Mapper(object):
# with_polymorphic selectable, as when use polymorphic_union. # with_polymorphic selectable, as when use polymorphic_union.
# we'll make a separate ColumnProperty for it. # we'll make a separate ColumnProperty for it.
instrument = True instrument = True
key = getattr(col, 'key', None) key = getattr(col, 'key', None)
if key: if key:
if self._should_exclude(col.key, col.key, False, col): if self._should_exclude(col.key, col.key, False, col):
@@ -2291,7 +2297,7 @@ def reconstructor(fn):
fn.__sa_reconstructor__ = True fn.__sa_reconstructor__ = True
return fn return fn
def validates(*names): def validates(*names, **kw):
"""Decorate a method as a 'validator' for one or more named properties. """Decorate a method as a 'validator' for one or more named properties.
Designates a method as a validator, a method which receives the Designates a method as a validator, a method which receives the
@@ -2307,9 +2313,18 @@ def validates(*names):
an assertion to avoid recursion overflows. This is a reentrant an assertion to avoid recursion overflows. This is a reentrant
condition which is not supported. condition which is not supported.
:param \*names: list of attribute names to be validated.
:param include_removes: if True, "remove" events will be
sent as well - the validation function must accept an additional
argument "is_remove" which will be a boolean.
.. versionadded:: 0.7.7
""" """
include_removes = kw.pop('include_removes', False)
def wrap(fn): def wrap(fn):
fn.__sa_validators__ = names fn.__sa_validators__ = names
fn.__sa_include_removes__ = include_removes
return fn return fn
return wrap return wrap
+6 -4
View File
@@ -1,5 +1,5 @@
# orm/persistence.py # orm/persistence.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -466,7 +466,7 @@ def _emit_update_statements(base_mapper, uowtransaction,
if needs_version_id: if needs_version_id:
clause.clauses.append(mapper.version_id_col ==\ clause.clauses.append(mapper.version_id_col ==\
sql.bindparam(mapper.version_id_col._label, sql.bindparam(mapper.version_id_col._label,
type_=col.type)) type_=mapper.version_id_col.type))
return table.update(clause) return table.update(clause)
@@ -748,13 +748,15 @@ def _connections_for_states(base_mapper, uowtransaction, states):
connection_callable = \ connection_callable = \
uowtransaction.session.connection_callable uowtransaction.session.connection_callable
else: else:
connection = uowtransaction.transaction.connection( connection = None
base_mapper)
connection_callable = None connection_callable = None
for state in _sort_states(states): for state in _sort_states(states):
if connection_callable: if connection_callable:
connection = connection_callable(base_mapper, state.obj()) connection = connection_callable(base_mapper, state.obj())
elif not connection:
connection = uowtransaction.transaction.connection(
base_mapper)
mapper = _state_mapper(state) mapper = _state_mapper(state)
+5 -10
View File
@@ -1,5 +1,5 @@
# orm/properties.py # orm/properties.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -136,7 +136,9 @@ class ColumnProperty(StrategizedProperty):
def merge(self, session, source_state, source_dict, dest_state, def merge(self, session, source_state, source_dict, dest_state,
dest_dict, load, _recursive): dest_dict, load, _recursive):
if self.key in source_dict: if not self.instrument:
return
elif self.key in source_dict:
value = source_dict[self.key] value = source_dict[self.key]
if not load: if not load:
@@ -144,8 +146,7 @@ class ColumnProperty(StrategizedProperty):
else: else:
impl = dest_state.get_impl(self.key) impl = dest_state.get_impl(self.key)
impl.set(dest_state, dest_dict, value, None) impl.set(dest_state, dest_dict, value, None)
else: elif dest_state.has_identity and self.key not in dest_dict:
if dest_state.has_identity and self.key not in dest_dict:
dest_state.expire_attributes(dest_dict, [self.key]) dest_state.expire_attributes(dest_dict, [self.key])
class Comparator(PropComparator): class Comparator(PropComparator):
@@ -322,12 +323,6 @@ class RelationshipProperty(StrategizedProperty):
else: else:
return elem return elem
def operate(self, op, *other, **kwargs):
return op(self, *other, **kwargs)
def reverse_operate(self, op, other, **kwargs):
return op(self, *other, **kwargs)
def of_type(self, cls): def of_type(self, cls):
"""Produce a construct that represents a particular 'subtype' of """Produce a construct that represents a particular 'subtype' of
attribute for the parent class. attribute for the parent class.
+78 -23
View File
@@ -1,5 +1,5 @@
# orm/query.py # orm/query.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -77,6 +77,7 @@ class Query(object):
_group_by = False _group_by = False
_having = None _having = None
_distinct = False _distinct = False
_prefixes = None
_offset = None _offset = None
_limit = None _limit = None
_statement = None _statement = None
@@ -454,7 +455,7 @@ class Query(object):
"""Return the full SELECT statement represented by this :class:`.Query` """Return the full SELECT statement represented by this :class:`.Query`
represented as a common table expression (CTE). represented as a common table expression (CTE).
The :meth:`.Query.cte` method is new in 0.7.6. .. versionadded:: 0.7.6
Parameters and usage are the same as those of the Parameters and usage are the same as those of the
:meth:`._SelectBase.cte` method; see that method for :meth:`._SelectBase.cte` method; see that method for
@@ -512,7 +513,7 @@ class Query(object):
Analogous to :meth:`sqlalchemy.sql._SelectBaseMixin.label`. Analogous to :meth:`sqlalchemy.sql._SelectBaseMixin.label`.
New in 0.6.5. .. versionadded:: 0.6.5
""" """
@@ -525,7 +526,7 @@ class Query(object):
Analogous to :meth:`sqlalchemy.sql._SelectBaseMixin.as_scalar`. Analogous to :meth:`sqlalchemy.sql._SelectBaseMixin.as_scalar`.
New in 0.6.5. .. versionadded:: 0.6.5
""" """
@@ -917,7 +918,8 @@ class Query(object):
'_order_by', '_group_by', '_order_by', '_group_by',
'_limit', '_offset', '_limit', '_offset',
'_joinpath', '_joinpoint', '_joinpath', '_joinpoint',
'_distinct', '_having' '_distinct', '_having',
'_prefixes',
): ):
self.__dict__.pop(attr, None) self.__dict__.pop(attr, None)
self._set_select_from(fromclause) self._set_select_from(fromclause)
@@ -979,7 +981,7 @@ class Query(object):
q = q.join((subq, subq.c.email < Address.email)).\\ q = q.join((subq, subq.c.email < Address.email)).\\
limit(1) limit(1)
New in 0.6.5. .. versionadded:: 0.6.5
""" """
self._set_entities(entities) self._set_entities(entities)
@@ -1056,7 +1058,7 @@ class Query(object):
This allows ad-hoc recipes to be created for :class:`.Query` This allows ad-hoc recipes to be created for :class:`.Query`
objects. See the example at :ref:`hybrid_transformers`. objects. See the example at :ref:`hybrid_transformers`.
:meth:`~.Query.with_transformation` is new in SQLAlchemy 0.7.4. .. versionadded:: 0.7.4
""" """
return fn(self) return fn(self)
@@ -1103,10 +1105,18 @@ class Query(object):
``FOR UPDATE`` (standard SQL, supported by most dialects) ``FOR UPDATE`` (standard SQL, supported by most dialects)
``'update_nowait'`` - passes ``for_update='nowait'``, which ``'update_nowait'`` - passes ``for_update='nowait'``, which
translates to ``FOR UPDATE NOWAIT`` (supported by Oracle) translates to ``FOR UPDATE NOWAIT`` (supported by Oracle,
PostgreSQL 8.1 upwards)
``'read'`` - passes ``for_update='read'``, which translates to ``'read'`` - passes ``for_update='read'``, which translates to
``LOCK IN SHARE MODE`` (supported by MySQL). ``LOCK IN SHARE MODE`` (for MySQL), and ``FOR SHARE`` (for
PostgreSQL)
``'read_nowait'`` - passes ``for_update='read_nowait'``, which
translates to ``FOR SHARE NOWAIT`` (supported by PostgreSQL).
.. versionadded:: 0.7.7
``FOR SHARE`` and ``FOR SHARE NOWAIT`` (PostgreSQL).
""" """
self._lockmode = mode self._lockmode = mode
@@ -1140,7 +1150,7 @@ class Query(object):
session.query(MyClass).filter(MyClass.name == 'some name') session.query(MyClass).filter(MyClass.name == 'some name')
Multiple criteria are joined together by AND (new in 0.7.5):: Multiple criteria are joined together by AND::
session.query(MyClass).\\ session.query(MyClass).\\
filter(MyClass.name == 'some name', MyClass.id > 5) filter(MyClass.name == 'some name', MyClass.id > 5)
@@ -1149,6 +1159,9 @@ class Query(object):
WHERE clause of a select. String expressions are coerced WHERE clause of a select. String expressions are coerced
into SQL expression constructs via the :func:`.text` construct. into SQL expression constructs via the :func:`.text` construct.
.. versionchanged:: 0.7.5
Multiple criteria joined by AND.
See also: See also:
:meth:`.Query.filter_by` - filter on keyword expressions. :meth:`.Query.filter_by` - filter on keyword expressions.
@@ -1233,7 +1246,7 @@ class Query(object):
@_generative(_no_statement_condition, _no_limit_offset) @_generative(_no_statement_condition, _no_limit_offset)
def group_by(self, *criterion): def group_by(self, *criterion):
"""apply one or more GROUP BY criterion to the query and return """apply one or more GROUP BY criterion to the query and return
the newly resulting ``Query``""" the newly resulting :class:`.Query`"""
criterion = list(chain(*[_orm_columns(c) for c in criterion])) criterion = list(chain(*[_orm_columns(c) for c in criterion]))
@@ -1247,7 +1260,19 @@ class Query(object):
@_generative(_no_statement_condition, _no_limit_offset) @_generative(_no_statement_condition, _no_limit_offset)
def having(self, criterion): def having(self, criterion):
"""apply a HAVING criterion to the query and return the """apply a HAVING criterion to the query and return the
newly resulting ``Query``.""" newly resulting :class:`.Query`.
:meth:`having` is used in conjunction with :meth:`group_by`.
HAVING criterion makes it possible to use filters on aggregate
functions like COUNT, SUM, AVG, MAX, and MIN, eg.::
q = session.query(User.id).\\
join(User.addresses).\\
group_by(User.id).\\
having(func.count(Address.id) > 2)
"""
if isinstance(criterion, basestring): if isinstance(criterion, basestring):
criterion = sql.text(criterion) criterion = sql.text(criterion)
@@ -1460,13 +1485,10 @@ class Query(object):
q = session.query(User).join(Address, User.id==Address.user_id) q = session.query(User).join(Address, User.id==Address.user_id)
.. note:: .. versionchanged:: 0.7
In SQLAlchemy 0.6 and earlier, the two argument form of In SQLAlchemy 0.6 and earlier, the two argument form of
:meth:`~.Query.join` requires the usage of a tuple:: :meth:`~.Query.join` requires the usage of a tuple:
``query(User).join((Address, User.id==Address.user_id))``\ .
query(User).join((Address, User.id==Address.user_id))
This calling form is accepted in 0.7 and further, though This calling form is accepted in 0.7 and further, though
is not necessary unless multiple join conditions are passed to is not necessary unless multiple join conditions are passed to
a single :meth:`~.Query.join` call, which itself is also not a single :meth:`~.Query.join` call, which itself is also not
@@ -1481,7 +1503,7 @@ class Query(object):
and :func:`.select` constructs, with either the one or two-argument forms:: and :func:`.select` constructs, with either the one or two-argument forms::
addresses_q = select([Address.user_id]).\\ addresses_q = select([Address.user_id]).\\
filter(Address.email_address.endswith("@bar.com")).\\ where(Address.email_address.endswith("@bar.com")).\\
alias() alias()
q = session.query(User).\\ q = session.query(User).\\
@@ -2057,6 +2079,33 @@ class Query(object):
else: else:
self._distinct = criterion self._distinct = criterion
@_generative()
def prefix_with(self, *prefixes):
"""Apply the prefixes to the query and return the newly resulting
``Query``.
:param \*prefixes: optional prefixes, typically strings,
not using any commas. In particular is useful for MySQL keywords.
e.g.::
query = sess.query(User.name).\\
prefix_with('HIGH_PRIORITY').\\
prefix_with('SQL_SMALL_RESULT', 'ALL')
Would render::
SELECT HIGH_PRIORITY SQL_SMALL_RESULT ALL users.name AS users_name
FROM users
.. versionadded:: 0.7.7
"""
if self._prefixes:
self._prefixes += prefixes
else:
self._prefixes = prefixes
def all(self): def all(self):
"""Return the results represented by this ``Query`` as a list. """Return the results represented by this ``Query`` as a list.
@@ -2125,7 +2174,9 @@ class Query(object):
unique entity or entities - this is a successful result for one(). unique entity or entities - this is a successful result for one().
Calling ``one()`` results in an execution of the underlying query. Calling ``one()`` results in an execution of the underlying query.
As of 0.6, ``one()`` fully fetches all results instead of applying
.. versionchanged:: 0.6
``one()`` fully fetches all results instead of applying
any kind of limit, so that the "unique"-ing of entities does not any kind of limit, so that the "unique"-ing of entities does not
conceal multiple object identities. conceal multiple object identities.
@@ -2364,14 +2415,16 @@ class Query(object):
mapped_entities = [i for i, e in enumerate(self._entities) mapped_entities = [i for i, e in enumerate(self._entities)
if isinstance(e, _MapperEntity)] if isinstance(e, _MapperEntity)]
result = [] result = []
keys = [ent._label_name for ent in self._entities]
for row in iterator: for row in iterator:
newrow = list(row) newrow = list(row)
for i in mapped_entities: for i in mapped_entities:
if newrow[i] is not None:
newrow[i] = session._merge( newrow[i] = session._merge(
attributes.instance_state(newrow[i]), attributes.instance_state(newrow[i]),
attributes.instance_dict(newrow[i]), attributes.instance_dict(newrow[i]),
load=load, _recursive={}) load=load, _recursive={})
result.append(util.NamedTuple(newrow, row._labels)) result.append(util.NamedTuple(newrow, keys))
return iter(result) return iter(result)
finally: finally:
@@ -2468,6 +2521,7 @@ class Query(object):
'limit':self._limit, 'limit':self._limit,
'offset':self._offset, 'offset':self._offset,
'distinct':self._distinct, 'distinct':self._distinct,
'prefixes':self._prefixes,
'group_by':self._group_by or None, 'group_by':self._group_by or None,
'having':self._having 'having':self._having
} }
@@ -2488,8 +2542,8 @@ class Query(object):
SELECT <rest of query follows...> SELECT <rest of query follows...>
) AS anon_1 ) AS anon_1
Note the above scheme is newly refined in 0.7 .. versionchanged:: 0.7
(as of 0.7b3). The above scheme is newly refined as of 0.7b3.
For fine grained control over specific columns For fine grained control over specific columns
to count, to skip the usage of a subquery or to count, to skip the usage of a subquery or
@@ -2807,6 +2861,7 @@ class Query(object):
if self._lockmode: if self._lockmode:
try: try:
for_update = {'read': 'read', for_update = {'read': 'read',
'read_nowait': 'read_nowait',
'update': True, 'update': True,
'update_nowait': 'nowait', 'update_nowait': 'nowait',
None: False}[self._lockmode] None: False}[self._lockmode]
+1 -1
View File
@@ -1,5 +1,5 @@
# orm/scoping.py # orm/scoping.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+262 -77
View File
@@ -1,5 +1,5 @@
# orm/session.py # orm/session.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -103,22 +103,61 @@ def sessionmaker(bind=None, class_=None, autoflush=True, autocommit=False,
class SessionTransaction(object): class SessionTransaction(object):
"""A Session-level transaction. """A :class:`.Session`-level transaction.
This corresponds to one or more Core :class:`~.engine.base.Transaction` :class:`.SessionTransaction` is a mostly behind-the-scenes object
instances behind the scenes, with one :class:`~.engine.base.Transaction` not normally referenced directly by application code. It coordinates
per :class:`~.engine.base.Engine` in use. among multiple :class:`.Connection` objects, maintaining a database
transaction for each one individually, committing or rolling them
back all at once. It also provides optional two-phase commit behavior
which can augment this coordination operation.
Direct usage of :class:`.SessionTransaction` is not typically The :attr:`.Session.transaction` attribute of :class:`.Session` refers to the
necessary as of SQLAlchemy 0.4; use the :meth:`.Session.rollback` and current :class:`.SessionTransaction` object in use, if any.
:meth:`.Session.commit` methods on :class:`.Session` itself to
control the transaction.
The current instance of :class:`.SessionTransaction` for a given
:class:`.Session` is available via the :attr:`.Session.transaction`
attribute.
The :class:`.SessionTransaction` object is **not** thread-safe. A :class:`.SessionTransaction` is associated with a :class:`.Session`
in its default mode of ``autocommit=False`` immediately, associated
with no database connections. As the :class:`.Session` is called upon
to emit SQL on behalf of various :class:`.Engine` or :class:`.Connection`
objects, a corresponding :class:`.Connection` and associated :class:`.Transaction`
is added to a collection within the :class:`.SessionTransaction` object,
becoming one of the connection/transaction pairs maintained by the
:class:`.SessionTransaction`.
The lifespan of the :class:`.SessionTransaction` ends when the
:meth:`.Session.commit`, :meth:`.Session.rollback` or :meth:`.Session.close`
methods are called. At this point, the :class:`.SessionTransaction` removes
its association with its parent :class:`.Session`. A :class:`.Session`
that is in ``autocommit=False`` mode will create a new
:class:`.SessionTransaction` to replace it immediately, whereas a
:class:`.Session` that's in ``autocommit=True``
mode will remain without a :class:`.SessionTransaction` until the
:meth:`.Session.begin` method is called.
Another detail of :class:`.SessionTransaction` behavior is that it is
capable of "nesting". This means that the :meth:`.begin` method can
be called while an existing :class:`.SessionTransaction` is already present,
producing a new :class:`.SessionTransaction` that temporarily replaces
the parent :class:`.SessionTransaction`. When a :class:`.SessionTransaction`
is produced as nested, it assigns itself to the :attr:`.Session.transaction`
attribute. When it is ended via :meth:`.Session.commit` or :meth:`.Session.rollback`,
it restores its parent :class:`.SessionTransaction` back onto the
:attr:`.Session.transaction` attribute. The
behavior is effectively a stack, where :attr:`.Session.transaction` refers
to the current head of the stack.
The purpose of this stack is to allow nesting of :meth:`.rollback` or
:meth:`.commit` calls in context with various flavors of :meth:`.begin`.
This nesting behavior applies to when :meth:`.Session.begin_nested`
is used to emit a SAVEPOINT transaction, and is also used to produce
a so-called "subtransaction" which allows a block of code to use a
begin/rollback/commit sequence regardless of whether or not its enclosing
code block has begun a transaction. The :meth:`.flush` method, whether called
explicitly or via autoflush, is the primary consumer of the "subtransaction"
feature, in that it wishes to guarantee that it works within in a transaction block
regardless of whether or not the :class:`.Session` is in transactional mode
when the method is called.
See also: See also:
@@ -126,6 +165,10 @@ class SessionTransaction(object):
:meth:`.Session.commit` :meth:`.Session.commit`
:meth:`.Session.begin`
:meth:`.Session.begin_nested`
:attr:`.Session.is_active` :attr:`.Session.is_active`
:meth:`.SessionEvents.after_commit` :meth:`.SessionEvents.after_commit`
@@ -134,9 +177,6 @@ class SessionTransaction(object):
:meth:`.SessionEvents.after_soft_rollback` :meth:`.SessionEvents.after_soft_rollback`
.. index::
single: thread safety; SessionTransaction
""" """
_rollback_exception = None _rollback_exception = None
@@ -211,6 +251,7 @@ class SessionTransaction(object):
if not self._is_transaction_boundary: if not self._is_transaction_boundary:
self._new = self._parent._new self._new = self._parent._new
self._deleted = self._parent._deleted self._deleted = self._parent._deleted
self._key_switches = self._parent._key_switches
return return
if not self.session._flushing: if not self.session._flushing:
@@ -218,6 +259,7 @@ class SessionTransaction(object):
self._new = weakref.WeakKeyDictionary() self._new = weakref.WeakKeyDictionary()
self._deleted = weakref.WeakKeyDictionary() self._deleted = weakref.WeakKeyDictionary()
self._key_switches = weakref.WeakKeyDictionary()
def _restore_snapshot(self): def _restore_snapshot(self):
assert self._is_transaction_boundary assert self._is_transaction_boundary
@@ -227,11 +269,16 @@ class SessionTransaction(object):
if s.key: if s.key:
del s.key del s.key
for s, (oldkey, newkey) in self._key_switches.items():
self.session.identity_map.discard(s)
s.key = oldkey
self.session.identity_map.replace(s)
for s in set(self._deleted).union(self.session._deleted): for s in set(self._deleted).union(self.session._deleted):
if s.deleted: if s.deleted:
#assert s in self._deleted #assert s in self._deleted
del s.deleted del s.deleted
self.session._update_impl(s) self.session._update_impl(s, discard_existing=True)
assert not self.session._deleted assert not self.session._deleted
@@ -294,7 +341,15 @@ class SessionTransaction(object):
subtransaction.commit() subtransaction.commit()
if not self.session._flushing: if not self.session._flushing:
for _flush_guard in xrange(100):
if self.session._is_clean():
break
self.session.flush() self.session.flush()
else:
raise exc.FlushError(
"Over 100 subsequent flushes have occurred within "
"session.commit() - is an after_flush() hook "
"creating new objects?")
if self._parent is None and self.session.twophase: if self._parent is None and self.session.twophase:
try: try:
@@ -735,49 +790,99 @@ class Session(object):
return engine.contextual_connect(**kwargs) return engine.contextual_connect(**kwargs)
def execute(self, clause, params=None, mapper=None, bind=None, **kw): def execute(self, clause, params=None, mapper=None, bind=None, **kw):
"""Execute a clause within the current transaction. """Execute a SQL expression construct or string statement within
the current transaction.
Returns a :class:`.ResultProxy` representing Returns a :class:`.ResultProxy` representing
results of the statement execution, in the same manner as that of an results of the statement execution, in the same manner as that of an
:class:`.Engine` or :class:`.Engine` or
:class:`.Connection`. :class:`.Connection`.
E.g.::
result = session.execute(
user_table.select().where(user_table.c.id == 5)
)
:meth:`~.Session.execute` accepts any executable clause construct, such :meth:`~.Session.execute` accepts any executable clause construct, such
as :func:`~.sql.expression.select`, as :func:`~.sql.expression.select`,
:func:`~.sql.expression.insert`, :func:`~.sql.expression.insert`,
:func:`~.sql.expression.update`, :func:`~.sql.expression.update`,
:func:`~.sql.expression.delete`, and :func:`~.sql.expression.delete`, and
:func:`~.sql.expression.text`, and additionally accepts :func:`~.sql.expression.text`. Plain SQL strings can be passed
plain strings that represent SQL statements. If a plain string is as well, which in the case of :meth:`.Session.execute` only
passed, it is first converted to a will be interpreted the same as if it were passed via a :func:`~.expression.text`
:func:`~.sql.expression.text` construct, which here means construct. That is, the following usage::
that bind parameters should be specified using the format ``:param``.
If raw DBAPI statement execution is desired, use :meth:`.Session.connection` result = session.execute(
to acquire a :class:`.Connection`, then call its :meth:`~.Connection.execute` "SELECT * FROM user WHERE id=:param",
method. {"param":5}
)
is equivalent to::
from sqlalchemy import text
result = session.execute(
text("SELECT * FROM user WHERE id=:param"),
{"param":5}
)
The second positional argument to :meth:`.Session.execute` is an
optional parameter set. Similar to that of :meth:`.Connection.execute`, whether this
is passed as a single dictionary, or a list of dictionaries, determines
whether the DBAPI cursor's ``execute()`` or ``executemany()`` is used to execute the
statement. An INSERT construct may be invoked for a single row::
result = session.execute(users.insert(), {"id": 7, "name": "somename"})
or for multiple rows::
result = session.execute(users.insert(), [
{"id": 7, "name": "somename7"},
{"id": 8, "name": "somename8"},
{"id": 9, "name": "somename9"}
])
The statement is executed within the current transactional context of The statement is executed within the current transactional context of
this :class:`.Session`, using the same behavior as that of this :class:`.Session`. The :class:`.Connection` which is used
the :meth:`.Session.connection` method to determine the active to execute the statement can also be acquired directly by
:class:`.Connection`. The ``close_with_result`` flag is calling the :meth:`.Session.connection` method. Both methods use
set to ``True`` so that an ``autocommit=True`` :class:`.Session` a rule-based resolution scheme in order to determine the
with no active transaction will produce a result that auto-closes :class:`.Connection`, which in the average case is derived directly
the underlying :class:`.Connection`. from the "bind" of the :class:`.Session` itself, and in other cases
can be based on the :func:`.mapper`
and :class:`.Table` objects passed to the method; see the documentation
for :meth:`.Session.get_bind` for a full description of this scheme.
The :meth:`.Session.execute` method does *not* invoke autoflush.
The :class:`.ResultProxy` returned by the :meth:`.Session.execute`
method is returned with the "close_with_result" flag set to true;
the significance of this flag is that if this :class:`.Session` is
autocommitting and does not have a transaction-dedicated :class:`.Connection`
available, a temporary :class:`.Connection` is established for the
statement execution, which is closed (meaning, returned to the connection
pool) when the :class:`.ResultProxy` has consumed all available data.
This applies *only* when the :class:`.Session` is configured with
autocommit=True and no transaction has been started.
:param clause: :param clause:
A :class:`.ClauseElement` (i.e. :func:`~.sql.expression.select`, An executable statement (i.e. an :class:`.Executable` expression
:func:`~.sql.expression.text`, etc.) or string SQL statement to be executed. The clause such as :func:`.expression.select`) or string SQL statement
will also be used to locate a bind, if this :class:`.Session` to be executed.
is not bound to a single engine already, and the ``mapper``
and ``bind`` arguments are not passed.
:param params: :param params:
Optional dictionary of bind names mapped to values. Optional dictionary, or list of dictionaries, containing
bound parameter values. If a single dictionary, single-row
execution occurs; if a list of dictionaries, an
"executemany" will be invoked. The keys in each dictionary
must correspond to parameter names present in the statement.
:param mapper: :param mapper:
Optional :func:`.mapper` or mapped class, used to identify Optional :func:`.mapper` or mapped class, used to identify
the appropriate bind. This argument takes precedence over the appropriate bind. This argument takes precedence over
``clause`` when locating a bind. ``clause`` when locating a bind. See :meth:`.Session.get_bind`
for more details.
:param bind: :param bind:
Optional :class:`.Engine` to be used as the bind. If Optional :class:`.Engine` to be used as the bind. If
@@ -787,9 +892,20 @@ class Session(object):
a bind. a bind.
:param \**kw: :param \**kw:
Additional keyword arguments are sent to :meth:`get_bind()`, Additional keyword arguments are sent to :meth:`.Session.get_bind()`
allowing additional arguments to be passed to custom to allow extensibility of "bind" schemes.
implementations of :meth:`get_bind`.
.. seealso::
:ref:`sqlexpression_toplevel` - Tutorial on using Core SQL
constructs.
:ref:`connections_toplevel` - Further information on direct
statement execution.
:meth:`.Connection.execute` - core level statement execution
method, which is :meth:`.Session.execute` ultimately uses
in order to execute the statement.
""" """
clause = expression._literal_as_text(clause) clause = expression._literal_as_text(clause)
@@ -998,7 +1114,7 @@ class Session(object):
of objects which involve existing database queries, of objects which involve existing database queries,
where the uncompleted object should not yet be flushed. where the uncompleted object should not yet be flushed.
New in 0.7.6. .. versionadded:: 0.7.6
""" """
autoflush = self.autoflush autoflush = self.autoflush
@@ -1214,6 +1330,11 @@ class Session(object):
# state has already replaced this one in the identity # state has already replaced this one in the identity
# map (see test/orm/test_naturalpks.py ReversePKsTest) # map (see test/orm/test_naturalpks.py ReversePKsTest)
self.identity_map.discard(state) self.identity_map.discard(state)
if state in self.transaction._key_switches:
orig_key = self.transaction._key_switches[state][0]
else:
orig_key = state.key
self.transaction._key_switches[state] = (orig_key, instance_key)
state.key = instance_key state.key = instance_key
self.identity_map.replace(state) self.identity_map.replace(state)
@@ -1303,20 +1424,47 @@ class Session(object):
self._delete_impl(st_) self._delete_impl(st_)
def merge(self, instance, load=True, **kw): def merge(self, instance, load=True, **kw):
"""Copy the state an instance onto the persistent instance with the """Copy the state of a given instance into a corresponding instance
same identifier. within this :class:`.Session`.
If there is no persistent instance currently associated with the :meth:`.Session.merge` examines the primary key attributes of the
session, it will be loaded. Return the persistent instance. If the source instance, and attempts to reconcile it with an instance of the
given instance is unsaved, save a copy of and return it as a newly same primary key in the session. If not found locally, it attempts
persistent instance. The given instance does not become associated to load the object from the database based on primary key, and if
with the session. none can be located, creates a new instance. The state of each attribute
on the source instance is then copied to the target instance.
The resulting target instance is then returned by the method; the
original source instance is left unmodified, and un-associated with the
:class:`.Session` if not already.
This operation cascades to associated instances if the association is This operation cascades to associated instances if the association is
mapped with ``cascade="merge"``. mapped with ``cascade="merge"``.
See :ref:`unitofwork_merging` for a detailed discussion of merging. See :ref:`unitofwork_merging` for a detailed discussion of merging.
:param instance: Instance to be merged.
:param load: Boolean, when False, :meth:`.merge` switches into
a "high performance" mode which causes it to forego emitting history
events as well as all database access. This flag is used for
cases such as transferring graphs of objects into a :class:`.Session`
from a second level cache, or to transfer just-loaded objects
into the :class:`.Session` owned by a worker thread or process
without re-querying the database.
The ``load=False`` use case adds the caveat that the given
object has to be in a "clean" state, that is, has no pending changes
to be flushed - even if the incoming object is detached from any
:class:`.Session`. This is so that when
the merge operation populates local attributes and
cascades to related objects and
collections, the values can be "stamped" onto the
target object as is, without generating any history or attribute
events, and without the need to reconcile the incoming data with
any existing related objects or collections that might not
be loaded. The resulting objects from ``load=False`` are always
produced as "clean", so it is only appropriate that the given objects
should be "clean" as well, else this suggests a mis-use of the method.
""" """
if 'dont_load' in kw: if 'dont_load' in kw:
load = not kw['dont_load'] load = not kw['dont_load']
@@ -1465,7 +1613,7 @@ class Session(object):
self._new[state] = state.obj() self._new[state] = state.obj()
state.insert_order = len(self._new) state.insert_order = len(self._new)
def _update_impl(self, state): def _update_impl(self, state, discard_existing=False):
if (self.identity_map.contains_state(state) and if (self.identity_map.contains_state(state) and
state not in self._deleted): state not in self._deleted):
return return
@@ -1481,6 +1629,10 @@ class Session(object):
"function to send this object back to the transient state." % "function to send this object back to the transient state." %
mapperutil.state_str(state) mapperutil.state_str(state)
) )
if discard_existing:
existing = self.identity_map.get(state.key)
if existing is not None:
self.identity_map.discard(attributes.instance_state(existing))
self._attach(state) self._attach(state)
self._deleted.pop(state, None) self._deleted.pop(state, None)
self.identity_map.add(state) self.identity_map.add(state)
@@ -1563,20 +1715,15 @@ class Session(object):
will create a transaction on the fly that surrounds the entire set of will create a transaction on the fly that surrounds the entire set of
operations int the flush. operations int the flush.
objects :param objects: Optional; restricts the flush operation to operate
Optional; a list or tuple collection. Restricts the flush operation only on elements that are in the given collection.
to only these objects, rather than all pending changes.
Deprecated - this flag prevents the session from properly maintaining This feature is for an extremely narrow set of use cases where
accounting among inter-object relations and can cause invalid results. particular objects may need to be operated upon before the
full flush() occurs. It is not intended for general use.
""" """
if objects:
util.warn_deprecated(
"The 'objects' argument to session.flush() is deprecated; "
"Please do not add objects to the session which should not "
"yet be persisted.")
if self._flushing: if self._flushing:
raise sa_exc.InvalidRequestError("Session is already flushing") raise sa_exc.InvalidRequestError("Session is already flushing")
@@ -1695,8 +1842,7 @@ class Session(object):
return session.is_modified(someobject, passive=True) return session.is_modified(someobject, passive=True)
.. note:: .. versionchanged:: 0.8
In SQLAlchemy 0.7 and earlier, the ``passive`` In SQLAlchemy 0.7 and earlier, the ``passive``
flag should **always** be explicitly set to ``True``. flag should **always** be explicitly set to ``True``.
The current default value of :data:`.attributes.PASSIVE_OFF` The current default value of :data:`.attributes.PASSIVE_OFF`
@@ -1750,10 +1896,11 @@ class Session(object):
is a bug, as unloaded attributes by definition have is a bug, as unloaded attributes by definition have
no changes, and the load operation also triggers an no changes, and the load operation also triggers an
autoflush which then cancels out subsequent changes. autoflush which then cancels out subsequent changes.
This flag should **always be set to This flag should **always be set to True**.
True**. In 0.8 the flag will be deprecated and the default
set to ``True``.
.. versionchanged:: 0.8
The flag will be deprecated and the default
set to ``True``.
""" """
try: try:
@@ -1784,20 +1931,58 @@ class Session(object):
@property @property
def is_active(self): def is_active(self):
"""True if this :class:`.Session` has an active transaction. """True if this :class:`.Session` is in "transaction mode" and
is not in "partial rollback" state.
This indicates if the :class:`.Session` is capable of emitting The :class:`.Session` in its default mode of ``autocommit=False``
SQL, as from the :meth:`.Session.execute`, :meth:`.Session.query`, is essentially always in "transaction mode", in that a
or :meth:`.Session.flush` methods. If False, it indicates :class:`.SessionTransaction` is associated with it as soon as
that the innermost transaction has been rolled back, but enclosing it is instantiated. This :class:`.SessionTransaction` is immediately
:class:`.SessionTransaction` objects remain in the transactional replaced with a new one as soon as it is ended, due to a rollback,
stack, which also must be rolled back. commit, or close operation.
This flag is generally only useful with a :class:`.Session` "Transaction mode" does *not* indicate whether
configured in its default mode of ``autocommit=False``. or not actual database connection resources are in use; the
:class:`.SessionTransaction` object coordinates among zero or more
actual database transactions, and starts out with none, accumulating
individual DBAPI connections as different data sources are used
within its scope. The best way to track when a particular
:class:`.Session` has actually begun to use DBAPI resources is to
implement a listener using the :meth:`.SessionEvents.after_begin`
method, which will deliver both the :class:`.Session` as well as the
target :class:`.Connection` to a user-defined event listener.
The "partial rollback" state refers to when an "inner" transaction,
typically used during a flush, encounters an error and emits
a rollback of the DBAPI connection. At this point, the :class:`.Session`
is in "partial rollback" and awaits for the user to call :meth:`.rollback`,
in order to close out the transaction stack. It is in this "partial
rollback" period that the :attr:`.is_active` flag returns False. After
the call to :meth:`.rollback`, the :class:`.SessionTransaction` is replaced
with a new one and :attr:`.is_active` returns ``True`` again.
When a :class:`.Session` is used in ``autocommit=True`` mode, the
:class:`.SessionTransaction` is only instantiated within the scope
of a flush call, or when :meth:`.Session.begin` is called. So
:attr:`.is_active` will always be ``False`` outside of a flush or
:meth:`.begin` block in this mode, and will be ``True`` within the
:meth:`.begin` block as long as it doesn't enter "partial rollback"
state.
From all the above, it follows that the only purpose to this flag is
for application frameworks that wish to detect is a "rollback" is
necessary within a generic error handling routine, for :class:`.Session`
objects that would otherwise be in "partial rollback" mode. In
a typical integration case, this is also not necessary as it is standard
practice to emit :meth:`.Session.rollback` unconditionally within the
outermost exception catch.
To track the transactional state of a :class:`.Session` fully,
use event listeners, primarily the :meth:`.SessionEvents.after_begin`,
:meth:`.SessionEvents.after_commit`, :meth:`.SessionEvents.after_rollback`
and related events.
""" """
return self.transaction and self.transaction.is_active return self.transaction and self.transaction.is_active
identity_map = None identity_map = None
+1 -1
View File
@@ -1,5 +1,5 @@
# orm/shard.py # orm/shard.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
+1 -1
View File
@@ -1,5 +1,5 @@
# orm/state.py # orm/state.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file> # Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# #
# This module is part of SQLAlchemy and is released under # This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
Loaded 100 of 121 files, more files were not shown because too many files have changed in this diff. Show more