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
+2552 -1259

No files matched your search

+5 -2
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -50,6 +50,8 @@ from sqlalchemy.sql import (
)
from sqlalchemy.types import (
BIGINT,
BINARY,
BLOB,
BOOLEAN,
BigInteger,
@@ -87,6 +89,7 @@ from sqlalchemy.types import (
TypeDecorator,
Unicode,
UnicodeText,
VARBINARY,
VARCHAR,
)
@@ -117,7 +120,7 @@ from sqlalchemy.engine import create_engine, engine_from_config
__all__ = sorted(name for name, obj in locals().items()
if not (name.startswith('_') or inspect.ismodule(obj)))
__version__ = '0.7.6'
__version__ = '0.7.10'
del inspect, sys
+27 -8
View File
@@ -241,12 +241,13 @@ static PyObject *
BaseRowProxy_subscript(BaseRowProxy *self, PyObject *key)
{
PyObject *processors, *values;
PyObject *processor, *value;
PyObject *processor, *value, *processed_value;
PyObject *row, *record, *result, *indexobject;
PyObject *exc_module, *exception;
PyObject *exc_module, *exception, *cstr_obj;
char *cstr_key;
long index;
int key_fallback = 0;
int tuple_check = 0;
if (PyInt_CheckExact(key)) {
index = PyInt_AS_LONG(key);
@@ -299,9 +300,16 @@ BaseRowProxy_subscript(BaseRowProxy *self, PyObject *key)
if (exception == NULL)
return NULL;
cstr_key = PyString_AsString(key);
if (cstr_key == NULL)
// wow. this seems quite excessive.
cstr_obj = PyObject_Str(key);
if (cstr_obj == 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,
"Ambiguous column name '%.200s' in result set! "
@@ -319,17 +327,28 @@ BaseRowProxy_subscript(BaseRowProxy *self, PyObject *key)
return NULL;
row = self->row;
if (PyTuple_CheckExact(row))
if (PyTuple_CheckExact(row)) {
value = PyTuple_GetItem(row, index);
else
tuple_check = 1;
}
else {
value = PySequence_GetItem(row, index);
tuple_check = 0;
}
if (value == NULL)
return NULL;
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 {
Py_INCREF(value);
if (tuple_check) {
Py_INCREF(value);
}
return value;
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+1 -1
View File
@@ -1,5 +1,5 @@
# 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
# 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, 'connect_timeout', int)
util.coerce_kw_type(opts, 'read_timeout', int)
util.coerce_kw_type(opts, 'client_flag', int)
util.coerce_kw_type(opts, 'local_infile', int)
# Note: using either of the below will cause all strings to be returned
+11 -3
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -37,6 +37,10 @@ class PyODBCConnector(Connector):
# if the libessqlsrv.so is detected
easysoft = False
def __init__(self, supports_unicode_binds=None, **kw):
super(PyODBCConnector, self).__init__(**kw)
self._user_supports_unicode_binds = supports_unicode_binds
@classmethod
def dbapi(cls):
return __import__('pyodbc')
@@ -119,8 +123,12 @@ class PyODBCConnector(Connector):
# have not tried pyodbc + python3.1 yet.
# Py2K
self.supports_unicode_statements = not self.freetds and not self.easysoft
self.supports_unicode_binds = (not self.freetds or
self.freetds_driver_version >= '0.91') 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.freetds_driver_version >= '0.91'
) and not self.easysoft
# end Py2K
# run other initialization which asks for user name, etc.
+1 -1
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+1 -1
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+1 -1
View File
@@ -1,5 +1,5 @@
# 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
# 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.
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.
"""
+14 -10
View File
@@ -1,18 +1,22 @@
from sqlalchemy.dialects.drizzle import base, mysqldb
# default dialect
base.dialect = mysqldb.dialect
from sqlalchemy.dialects.drizzle.base import \
BIGINT, BINARY, BLOB, BOOLEAN, CHAR, DATE, DATETIME, \
DECIMAL, DOUBLE, ENUM, \
FLOAT, INTEGER, \
NUMERIC, REAL, TEXT, TIME, TIMESTAMP, \
VARBINARY, VARCHAR, dialect
BIGINT, BINARY, BLOB, \
BOOLEAN, CHAR, DATE, \
DATETIME, DECIMAL, DOUBLE, \
ENUM, FLOAT, INTEGER, \
NUMERIC, REAL, TEXT, \
TIME, TIMESTAMP, VARBINARY, \
VARCHAR, dialect
__all__ = (
'BIGINT', 'BINARY', 'BLOB', 'BOOLEAN', 'CHAR', 'DATE', 'DATETIME', 'DECIMAL', 'DOUBLE',
'ENUM', 'FLOAT', 'INTEGER',
'NUMERIC', 'SET', 'REAL', 'TEXT', 'TIME', 'TIMESTAMP',
'VARBINARY', 'VARCHAR', 'dialect'
'BIGINT', 'BINARY', 'BLOB',
'BOOLEAN', 'CHAR', 'DATE',
'DATETIME', 'DECIMAL', 'DOUBLE',
'ENUM', 'FLOAT', 'INTEGER',
'NUMERIC', 'REAL', 'TEXT',
'TIME', 'TIMESTAMP', 'VARBINARY',
'VARCHAR', 'dialect'
)
+69 -142
View File
@@ -1,138 +1,36 @@
# 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>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
"""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.
with capabilities increasing with more modern servers.
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.
The SQLAlchemy Drizzle dialect leans heavily on the MySQL dialect, so much of
the :doc:`SQLAlchemy MySQL <mysql>` documentation is also relevant.
Connecting
----------
See the API documentation on individual drivers 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)
See the individual driver sections below for details on connecting.
"""
import datetime, inspect, re, sys
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 exc
from sqlalchemy import log
from sqlalchemy import types as sqltypes
from sqlalchemy.engine import reflection
from sqlalchemy.dialects.mysql import base as mysql_dialect
from sqlalchemy.types import DATE, DATETIME, BOOLEAN, TIME, \
BLOB, BINARY, VARBINARY
BLOB, BINARY, VARBINARY
class _NumericType(object):
"""Base for Drizzle numeric types."""
@@ -140,6 +38,7 @@ class _NumericType(object):
def __init__(self, **kw):
super(_NumericType, self).__init__(**kw)
class _FloatType(_NumericType, sqltypes.Float):
def __init__(self, precision=None, scale=None, asdecimal=True, **kw):
if isinstance(self, (REAL, DOUBLE)) and \
@@ -147,23 +46,22 @@ class _FloatType(_NumericType, sqltypes.Float):
(precision is None and scale is not None) or
(precision is not None and scale is None)
):
raise exc.ArgumentError(
"You must specify both precision and scale or omit "
"both altogether.")
raise exc.ArgumentError(
"You must specify both precision and scale or omit "
"both altogether.")
super(_FloatType, self).__init__(precision=precision, asdecimal=asdecimal, **kw)
super(_FloatType, self).__init__(precision=precision,
asdecimal=asdecimal, **kw)
self.scale = scale
class _StringType(mysql_dialect._StringType):
"""Base for Drizzle string types."""
def __init__(self, collation=None,
binary=False,
**kw):
def __init__(self, collation=None, binary=False, **kw):
kw['national'] = False
super(_StringType, self).__init__(collation=collation,
binary=binary,
**kw)
super(_StringType, self).__init__(collation=collation, binary=binary,
**kw)
class NUMERIC(_NumericType, sqltypes.NUMERIC):
@@ -180,7 +78,9 @@ class NUMERIC(_NumericType, sqltypes.NUMERIC):
: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):
@@ -215,9 +115,11 @@ class DOUBLE(_FloatType):
:param scale: The number of digits after the decimal point.
"""
super(DOUBLE, self).__init__(precision=precision, scale=scale,
asdecimal=asdecimal, **kw)
class REAL(_FloatType, sqltypes.REAL):
"""Drizzle REAL type."""
@@ -232,9 +134,11 @@ class REAL(_FloatType, sqltypes.REAL):
:param scale: The number of digits after the decimal point.
"""
super(REAL, self).__init__(precision=precision, scale=scale,
asdecimal=asdecimal, **kw)
class FLOAT(_FloatType, sqltypes.FLOAT):
"""Drizzle FLOAT type."""
@@ -249,42 +153,46 @@ class FLOAT(_FloatType, sqltypes.FLOAT):
:param scale: The number of digits after the decimal point.
"""
super(FLOAT, self).__init__(precision=precision, scale=scale,
asdecimal=asdecimal, **kw)
def bind_processor(self, dialect):
return None
class INTEGER(sqltypes.INTEGER):
"""Drizzle INTEGER type."""
__visit_name__ = 'INTEGER'
def __init__(self, **kw):
"""Construct an INTEGER.
"""Construct an INTEGER."""
"""
super(INTEGER, self).__init__(**kw)
class BIGINT(sqltypes.BIGINT):
"""Drizzle BIGINTEGER type."""
__visit_name__ = 'BIGINT'
def __init__(self, **kw):
"""Construct a BIGINTEGER.
"""Construct a BIGINTEGER."""
"""
super(BIGINT, self).__init__(**kw)
class _DrizzleTime(mysql_dialect._MSTime):
"""Drizzle TIME type."""
class TIMESTAMP(sqltypes.TIMESTAMP):
"""Drizzle TIMESTAMP type."""
__visit_name__ = 'TIMESTAMP'
class TEXT(_StringType, sqltypes.TEXT):
"""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.
"""
super(TEXT, self).__init__(length=length, **kw)
class VARCHAR(_StringType, sqltypes.VARCHAR):
"""Drizzle VARCHAR type, for variable-length character data."""
@@ -325,8 +235,10 @@ class VARCHAR(_StringType, sqltypes.VARCHAR):
only the collation of character data.
"""
super(VARCHAR, self).__init__(length=length, **kwargs)
class CHAR(_StringType, sqltypes.CHAR):
"""Drizzle CHAR type, for fixed-length character data."""
@@ -345,8 +257,10 @@ class CHAR(_StringType, sqltypes.CHAR):
compatible with the national character set.
"""
super(CHAR, self).__init__(length=length, **kwargs)
class ENUM(mysql_dialect.ENUM):
"""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
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
an out of range value- an alternate value will be stored instead.
Note that Drizzle will not raise a fatal error if you attempt to
store an out of range value- an alternate value will be stored
instead.
(See Drizzle ENUM documentation.)
: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.
"""
super(ENUM, self).__init__(*enums, **kw)
class _DrizzleBoolean(sqltypes.Boolean):
def get_dbapi_type(self, dbapi):
return dbapi.NUMERIC
colspecs = {
sqltypes.Numeric: NUMERIC,
sqltypes.Float: FLOAT,
@@ -404,6 +322,7 @@ colspecs = {
sqltypes.Boolean: _DrizzleBoolean,
}
# All the types we have in Drizzle
ischema_names = {
'BIGINT': BIGINT,
@@ -427,6 +346,7 @@ ischema_names = {
'VARCHAR': VARCHAR,
}
class DrizzleCompiler(mysql_dialect.MySQLCompiler):
def visit_typeclause(self, typeclause):
@@ -439,7 +359,7 @@ class DrizzleCompiler(mysql_dialect.MySQLCompiler):
def visit_cast(self, cast, **kwargs):
type_ = self.process(cast.typeclause)
if type_ is None:
return self.process(cast.clause)
return self.process(cast.clause)
return 'CAST(%s AS %s)' % (self.process(cast.clause), type_)
@@ -447,6 +367,7 @@ class DrizzleCompiler(mysql_dialect.MySQLCompiler):
class DrizzleDDLCompiler(mysql_dialect.MySQLDDLCompiler):
pass
class DrizzleTypeCompiler(mysql_dialect.MySQLTypeCompiler):
def _extend_numeric(self, type_, spec):
return spec
@@ -492,11 +413,16 @@ class DrizzleTypeCompiler(mysql_dialect.MySQLTypeCompiler):
class DrizzleExecutionContext(mysql_dialect.MySQLExecutionContext):
pass
class DrizzleIdentifierPreparer(mysql_dialect.MySQLIdentifierPreparer):
pass
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'
@@ -505,7 +431,6 @@ class DrizzleDialect(mysql_dialect.MySQLDialect):
supports_native_boolean = True
supports_views = False
default_paramstyle = 'format'
colspecs = colspecs
@@ -516,8 +441,8 @@ class DrizzleDialect(mysql_dialect.MySQLDialect):
preparer = DrizzleIdentifierPreparer
def on_connect(self):
"""Force autocommit - Drizzle Bug#707842 doesn't set this
properly"""
"""Force autocommit - Drizzle Bug#707842 doesn't set this properly"""
def connect(conn):
conn.autocommit(False)
return connect
@@ -535,6 +460,7 @@ class DrizzleDialect(mysql_dialect.MySQLDialect):
@reflection.cache
def get_table_names(self, connection, schema=None, **kw):
"""Return a Unicode SHOW TABLES from a given schema."""
if schema is not None:
current_schema = schema
else:
@@ -554,8 +480,8 @@ class DrizzleDialect(mysql_dialect.MySQLDialect):
Cached per-connection. This value can not change without a server
restart.
"""
return 0
def _detect_collations(self, connection):
@@ -566,7 +492,9 @@ class DrizzleDialect(mysql_dialect.MySQLDialect):
collations = {}
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):
collations[row[0]] = row[1]
return collations
@@ -575,8 +503,7 @@ class DrizzleDialect(mysql_dialect.MySQLDialect):
"""Detect and adjust for the ANSI_QUOTES sql mode."""
self._server_ansiquotes = False
self._backslash_escapes = False
log.class_logger(DrizzleDialect)
log.class_logger(DrizzleDialect)
+18 -34
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
At least version 1.2.1 or 1.2.2 should be used.
Connecting
-----------
@@ -13,37 +11,22 @@ Connect string format::
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,
DrizzleExecutionContext,
DrizzleCompiler, DrizzleIdentifierPreparer)
from sqlalchemy.dialects.drizzle.base import (
DrizzleDialect,
DrizzleExecutionContext,
DrizzleCompiler,
DrizzleIdentifierPreparer)
from sqlalchemy.connectors.mysqldb import (
MySQLDBExecutionContext,
MySQLDBCompiler,
MySQLDBIdentifierPreparer,
MySQLDBConnector
)
MySQLDBExecutionContext,
MySQLDBCompiler,
MySQLDBIdentifierPreparer,
MySQLDBConnector)
class DrizzleExecutionContext_mysqldb(
MySQLDBExecutionContext,
DrizzleExecutionContext):
class DrizzleExecutionContext_mysqldb(MySQLDBExecutionContext,
DrizzleExecutionContext):
pass
@@ -51,11 +34,11 @@ class DrizzleCompiler_mysqldb(MySQLDBCompiler, DrizzleCompiler):
pass
class DrizzleIdentifierPreparer_mysqldb(
MySQLDBIdentifierPreparer,
DrizzleIdentifierPreparer):
class DrizzleIdentifierPreparer_mysqldb(MySQLDBIdentifierPreparer,
DrizzleIdentifierPreparer):
pass
class DrizzleDialect_mysqldb(MySQLDBConnector, DrizzleDialect):
execution_ctx_cls = DrizzleExecutionContext_mysqldb
statement_compiler = DrizzleCompiler_mysqldb
@@ -63,6 +46,7 @@ class DrizzleDialect_mysqldb(MySQLDBConnector, DrizzleDialect):
def _detect_charset(self, connection):
"""Sniff out the character set in use for connection results."""
return 'utf8'
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+3 -3
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -198,7 +198,7 @@ class FBTypeCompiler(compiler.GenericTypeCompiler):
class FBCompiler(sql.compiler.SQLCompiler):
"""Firebird specific idiosincrasies"""
"""Firebird specific idiosyncrasies"""
def visit_mod(self, binary, **kw):
# Firebird lacks a builtin modulo operator, but there is
@@ -293,7 +293,7 @@ class FBCompiler(sql.compiler.SQLCompiler):
class FBDDLCompiler(sql.compiler.DDLCompiler):
"""Firebird syntactic idiosincrasies"""
"""Firebird syntactic idiosyncrasies"""
def visit_create_sequence(self, create):
"""Generate a ``CREATE GENERATOR`` statement for the sequence."""
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+6 -3
View File
@@ -1,5 +1,5 @@
# 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
#
# This module is part of SQLAlchemy and is released under
@@ -7,8 +7,11 @@
"""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(
["abs", "absolute", "access", "access_method", "acos", "active", "add",
"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",
"attributes", "audit", "authentication", "authid", "authorization",
"authorized", "auto", "autofree", "auto_reprepare", "auto_stat_mode",
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+1 -1
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+4 -3
View File
@@ -1,14 +1,15 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
"""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
--------
+1 -1
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+1 -1
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+1 -1
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+49 -24
View File
@@ -1,5 +1,5 @@
# 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
# 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
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 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
statements that are unable to be parsed by the database server.
@@ -161,11 +161,12 @@ http://msdn.microsoft.com/en-us/library/ms175095.aspx.
Scalar Select Comparisons
-------------------------
The MSSQL dialect contains a legacy behavior whereby comparing
a scalar select to a value using the ``=`` or ``!=`` operator
will resolve to IN or NOT IN, respectively. This behavior is
deprecated and will be removed in 0.8 - the ``s.in_()``/``~s.in_()`` operators
should be used when IN/NOT IN are desired.
.. deprecated:: 0.8
The MSSQL dialect contains a legacy behavior whereby comparing
a scalar select to a value using the ``=`` or ``!=`` operator
will resolve to IN or NOT IN, respectively. This behavior
will be removed in 0.8 - the ``s.in_()``/``~s.in_()`` operators
should be used when IN/NOT IN are desired.
For the time being, the existing behavior prevents a comparison
between scalar select and another value that actually wants to use ``=``.
@@ -689,18 +690,22 @@ class MSExecutionContext(default.DefaultExecutionContext):
not self.executemany
if self._enable_identity_insert:
self.cursor.execute("SET IDENTITY_INSERT %s ON" %
self.dialect.identifier_preparer.format_table(tbl))
self.root_connection._cursor_execute(self.cursor,
"SET IDENTITY_INSERT %s ON" %
self.dialect.identifier_preparer.format_table(tbl),
())
def post_exec(self):
"""Disable IDENTITY_INSERT if enabled."""
conn = self.root_connection
if self._select_lastrowid:
if self.dialect.use_scope_identity:
self.cursor.execute(
"SELECT scope_identity() AS lastrowid", ())
conn._cursor_execute(self.cursor,
"SELECT scope_identity() AS lastrowid", ())
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
row = self.cursor.fetchall()[0]
self._lastrowid = int(row[0])
@@ -710,10 +715,11 @@ class MSExecutionContext(default.DefaultExecutionContext):
self._result_proxy = base.FullyBufferedResultProxy(self)
if self._enable_identity_insert:
self.cursor.execute(
conn._cursor_execute(self.cursor,
"SET IDENTITY_INSERT %s OFF" %
self.dialect.identifier_preparer.
format_table(self.compiled.statement.table)
format_table(self.compiled.statement.table),
()
)
def get_lastrowid(self):
@@ -803,27 +809,28 @@ class MSSQLCompiler(compiler.SQLCompiler):
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.
orderby = self.process(select._order_by_clause)
if not orderby:
if not select._order_by_clause.clauses:
raise exc.CompileError('MSSQL requires an order_by when '
'using an offset.')
_offset = select._offset
_limit = select._limit
_order_by_clauses = select._order_by_clause.clauses
select = select._generate()
select._mssql_visit = True
select = select.column(
sql.literal_column("ROW_NUMBER() OVER (ORDER BY %s)" \
% orderby).label("mssql_rn")
sql.func.ROW_NUMBER().over(order_by=_order_by_clauses)
.label("mssql_rn")
).order_by(None).alias()
mssql_rn = sql.column('mssql_rn')
limitselect = sql.select([c for c in select.c if
c.key!='mssql_rn'])
limitselect.append_whereclause(mssql_rn> _offset)
c.key != 'mssql_rn'])
limitselect.append_whereclause(mssql_rn > _offset)
if _limit is not None:
limitselect.append_whereclause(mssql_rn<=(_limit + _offset))
limitselect.append_whereclause(mssql_rn <= (_limit + _offset))
return self.process(limitselect, iswrapper=True, **kwargs)
else:
return compiler.SQLCompiler.visit_select(self, select, **kwargs)
@@ -875,7 +882,8 @@ class MSSQLCompiler(compiler.SQLCompiler):
if result_map is not None:
result_map[column.name.lower()] = \
(column.name, (column, ),
(column.name, (column, column.name,
column.key),
column.type)
return super(MSSQLCompiler, self).\
@@ -980,6 +988,22 @@ class MSSQLCompiler(compiler.SQLCompiler):
else:
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):
"""A subclass of MSSQLCompiler which disables the usage of bind
parameters where not allowed natively by MS-SQL.
@@ -1431,6 +1455,7 @@ class MSDialect(default.DefaultDialect):
# Primary key constraints
s = sql.select([C.c.column_name, TC.c.constraint_type],
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_schema == current_schema)
)
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -15,8 +15,10 @@ class CoerceUnicode(TypeDecorator):
impl = Unicode
def process_bind_param(self, value, dialect):
# Py2K
if isinstance(value, str):
value = value.decode(dialect.encoding)
# end Py2K
return value
schemata = Table("SCHEMATA", ischema,
+2 -2
View File
@@ -1,5 +1,5 @@
# 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
# 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
``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
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
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+31 -2
View File
@@ -1,5 +1,5 @@
# 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
# 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')
'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):
super(MSDialect_pyodbc, self).__init__(**params)
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')
self._need_decimal_fix = self.dbapi and \
self._dbapi_version() < (2, 1, 8)
+1 -1
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+3 -2
View File
@@ -1,11 +1,12 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
from sqlalchemy.dialects.mysql import base, mysqldb, oursql, \
pyodbc, zxjdbc, mysqlconnector, pymysql
pyodbc, zxjdbc, mysqlconnector, pymysql,\
gaerdbms
# default dialect
base.dialect = mysqldb.dialect
+43 -33
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -9,29 +9,11 @@
Supported Versions and Features
-------------------------------
SQLAlchemy supports 6 major MySQL versions: 3.23, 4.0, 4.1, 5.0, 5.1 and 6.0,
with capabilities increasing with more modern servers.
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
SQLAlchemy supports MySQL starting with version 4.1 through modern releases.
However, no heroic measures are taken to work around major missing
SQL features - if your server version does not support sub-selects, for
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
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)
.. _mysql_storage_engines:
Storage Engines
---------------
Most MySQL server installations have a default table type of ``MyISAM``, a
non-transactional table type. During a transaction, non-transactional storage
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.
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'
)
.. 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
-------------------------------------
@@ -99,7 +88,7 @@ every new connection. Valid values for this parameter are
isolation_level="READ UNCOMMITTED"
)
(new in 0.7.6)
.. versionadded:: 0.7.6
Keys
----
@@ -185,6 +174,24 @@ available.
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
------------
@@ -344,9 +351,9 @@ class _FloatType(_NumericType, sqltypes.Float):
(precision is None and scale is not None) or
(precision is not None and scale is None)
):
raise exc.ArgumentError(
"You must specify both precision and scale or omit "
"both altogether.")
raise exc.ArgumentError(
"You must specify both precision and scale or omit "
"both altogether.")
super(_FloatType, self).__init__(precision=precision, asdecimal=asdecimal, **kw)
self.scale = scale
@@ -1273,11 +1280,11 @@ class MySQLCompiler(compiler.SQLCompiler):
def visit_cast(self, cast, **kwargs):
# No cast until 4, no decimals until 5.
if not self.dialect._supports_cast:
return self.process(cast.clause)
return self.process(cast.clause.self_group())
type_ = self.process(cast.typeclause)
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_)
@@ -1395,8 +1402,12 @@ class MySQLDDLCompiler(compiler.DDLCompiler):
auto_inc_column is not list(table.primary_key)[0]:
if constraint_string:
constraint_string += ", \n\t"
constraint_string += "KEY `idx_autoinc_%s`(`%s`)" % (auto_inc_column.name, \
self.preparer.format_column(auto_inc_column))
constraint_string += "KEY %s (%s)" % (
self.preparer.quote(
"idx_autoinc_%s" % auto_inc_column.name, None
),
self.preparer.format_column(auto_inc_column)
)
return constraint_string
@@ -2017,7 +2028,6 @@ class MySQLDialect(default.DefaultDialect):
@reflection.cache
def get_view_names(self, connection, schema=None, **kw):
charset = self._connection_charset
if self.server_version_info < (5, 0, 2):
raise NotImplementedError
if schema is None:
@@ -2028,7 +2038,7 @@ class MySQLDialect(default.DefaultDialect):
rp = connection.execute("SHOW FULL TABLES FROM %s" %
self.identifier_preparer.quote_identifier(schema))
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
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
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -105,15 +105,11 @@ class MySQLDialect_mysqlconnector(MySQLDialect):
def _get_server_version_info(self, 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()
return tuple(version)
def _detect_charset(self, connection):
return connection.connection.get_characterset_info()
return connection.connection.charset
def _extract_error_code(self, exception):
return exception.errno
+1 -1
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+2 -1
View File
@@ -1,5 +1,5 @@
# 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
# 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, 'compress', 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)
if opts.pop('default_charset', False):
+1 -1
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+1 -1
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+1 -1
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+2 -2
View File
@@ -1,5 +1,5 @@
# 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
# 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,\
BLOB, BFILE, CLOB, NCLOB, TIMESTAMP, RAW,\
FLOAT, DOUBLE_PRECISION, LONG, dialect, INTERVAL,\
VARCHAR2, NVARCHAR2
VARCHAR2, NVARCHAR2, ROWID
__all__ = (
+8 -6
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -63,10 +63,12 @@ used on the SQLAlchemy side.
Unicode
-------
SQLAlchemy 0.6 uses the "native unicode" mode provided as of cx_oracle 5. cx_oracle 5.0.2
or greater is recommended for support of NCLOB. 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".
.. versionchanged:: 0.6
SQLAlchemy uses the "native unicode" mode provided as of cx_oracle 5.
cx_oracle 5.0.2 or greater is recommended for support of NCLOB.
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.
When using the SQLAlchemy Unicode and UnicodeText types, these DDL types will be used
@@ -564,7 +566,7 @@ class OracleCompiler(compiler.SQLCompiler):
if not self.dialect.use_binds_for_limits:
max_row = sql.literal_column("%d" % max_row)
limitselect.append_whereclause(
sql.literal_column("ROWNUM")<=max_row)
sql.literal_column("ROWNUM") <= max_row)
# If needed, add the ora_rn, and wrap again with offset.
if select._offset is None:
+77 -21
View File
@@ -1,5 +1,5 @@
# 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
# 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 transactions are implemented using XA transactions. Success has been reported
with this feature but it should be regarded as experimental.
Two Phase transactions are implemented using XA transactions, and are known
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
------------------
@@ -98,7 +130,8 @@ to :func:`.create_engine`::
engine = create_engine("oracle+cx_oracle://dsn",
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
`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
a comma "," for european locales. From that point forward the
outputtypehandler uses that character to represent a decimal
point (this behavior is new in version 0.6.6). Note that
cx_oracle 5.0.3 or greater is required when dealing with
numerics with locale settings that don't use a period "." as the
decimal character.
point. Note that cx_oracle 5.0.3 or greater is required
when dealing with numerics with locale settings that don't use
a period "." as the 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
"""
from sqlalchemy.dialects.oracle.base import OracleCompiler, OracleDialect, \
RESERVED_WORDS, OracleExecutionContext
OracleExecutionContext
from sqlalchemy.dialects.oracle import base as oracle
from sqlalchemy.engine import base
from sqlalchemy import types as sqltypes, util, exc, processors
from datetime import datetime
import random
import collections
from sqlalchemy.util.compat import decimal
@@ -248,6 +283,13 @@ class _OracleText(_LOBMixin, sqltypes.Text):
def get_dbapi_type(self, dbapi):
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):
pass
@@ -296,13 +338,13 @@ class _OracleRowid(oracle.ROWID):
return dbapi.ROWID
class OracleCompiler_cx_oracle(OracleCompiler):
def bindparam_string(self, name):
def bindparam_string(self, name, **kw):
if self.preparer._bindparam_requires_quotes(name):
quoted_name = '"%s"' % name
self._quoted_bind_names[name] = quoted_name
return OracleCompiler.bindparam_string(self, quoted_name)
return OracleCompiler.bindparam_string(self, quoted_name, **kw)
else:
return OracleCompiler.bindparam_string(self, name)
return OracleCompiler.bindparam_string(self, name, **kw)
class OracleExecutionContext_cx_oracle(OracleExecutionContext):
@@ -333,7 +375,7 @@ class OracleExecutionContext_cx_oracle(OracleExecutionContext):
# on String, including that outparams/RETURNING
# breaks for varchars
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
@@ -467,6 +509,11 @@ class OracleDialect_cx_oracle(OracleDialect):
sqltypes.String : _OracleString,
sqltypes.UnicodeText : _OracleUnicodeText,
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.
# it would be nice if we could not use it otherwise.
oracle.RAW: _OracleRaw,
@@ -503,6 +550,7 @@ class OracleDialect_cx_oracle(OracleDialect):
getattr(self.dbapi, name, None) for name in names
]).difference([None])
self._cx_oracle_exclude_setinputsizes = types("STRING", "UNICODE")
self._cx_oracle_string_types = types("STRING", "UNICODE", "NCLOB", "CLOB")
self._cx_oracle_unicode_types = types("UNICODE", "NCLOB")
self._cx_oracle_binary_types = types("BFILE", "CLOB", "NCLOB", "BLOB")
@@ -551,13 +599,13 @@ class OracleDialect_cx_oracle(OracleDialect):
# expect encoded strings or unicodes, etc.
self.dbapi_type_map = {
self.dbapi.CLOB: oracle.CLOB(),
self.dbapi.NCLOB:oracle.NCLOB(),
self.dbapi.NCLOB: oracle.NCLOB(),
self.dbapi.BLOB: oracle.BLOB(),
self.dbapi.BINARY: oracle.RAW(),
}
@classmethod
def dbapi(cls):
import cx_Oracle
cx_Oracle = __import__('cx_Oracle')
return cx_Oracle
def initialize(self, connection):
@@ -739,15 +787,23 @@ class OracleDialect_cx_oracle(OracleDialect):
connection.connection.begin(*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)
def do_commit_twophase(self, connection, xid, is_prepared=True, recover=False):
self.do_commit(connection.connection)
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)
def do_recover_twophase(self, connection):
pass
connection.info.pop('cx_oracle_prepared', None)
dialect = OracleDialect_cx_oracle
+1 -1
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+1 -1
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+52 -29
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -82,9 +82,10 @@ to inspect the actual value using::
SHOW search_path;
Prior to version 0.7.3, cross-schema foreign keys when the schemas
were also in the ``search_path`` could make an incorrect assumption
if the schemas were explicitly stated on each :class:`.Table`.
.. 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
if the schemas were explicitly stated on each :class:`.Table`.
Background on PG's ``search_path`` is at:
http://www.postgresql.org/docs/9.0/static/ddl-schemas.html#DDL-SCHEMAS-PATH
@@ -137,7 +138,7 @@ Operator Classes
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).
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,
postgresql_ops={
@@ -145,6 +146,9 @@ keyword argument (new as of SQLAlchemy 0.7.2)::
'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
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
@@ -348,21 +352,24 @@ class ARRAY(sqltypes.MutableType, sqltypes.Concatenable, sqltypes.TypeEngine):
class should be considered mutable - this enables
"mutable types" mode in the ORM. Be sure to read the
notes for :class:`.MutableType` regarding ORM
performance implications (default changed from ``True`` in
0.7.0).
performance implications.
.. note::
.. versionchanged:: 0.7.0
Default changed from ``True``\ .
This functionality is now superseded by the
``sqlalchemy.ext.mutable`` extension described in
:ref:`mutable_toplevel`.
.. versionchanged:: 0.7
This functionality is now superseded by the
``sqlalchemy.ext.mutable`` extension described in
:ref:`mutable_toplevel`.
:param as_tuple=False: Specify whether return results
should be converted to tuples from lists. DBAPIs such
as psycopg2 return lists by default. When tuples are
returned, the results are hashable. This flag can only
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):
@@ -489,7 +496,8 @@ class ENUM(sqltypes.Enum):
the :meth:`~.postgresql.ENUM.create` and
:meth:`~.postgresql.ENUM.drop` methods can
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)
@@ -675,6 +683,10 @@ class PGCompiler(compiler.SQLCompiler):
def for_update_clause(self, select):
if select.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:
return super(PGCompiler, self).for_update_clause(select)
@@ -1040,7 +1052,7 @@ class PGDialect(default.DefaultDialect):
if is_prepared:
if recover:
#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
# open a transaction.
connection.execute("ROLLBACK")
@@ -1439,20 +1451,33 @@ class PGDialect(default.DefaultDialect):
table_oid = self.get_table_oid(connection, table_name, schema,
info_cache=kw.get('info_cache'))
PK_SQL = """
SELECT a.attname
if self.server_version_info < (8, 4):
# unnest() and generate_subscripts() both introduced in
# version 8.4
PK_SQL = """
SELECT a.attname
FROM
pg_class t
join pg_index ix on t.oid = ix.indrelid
join pg_attribute a
on t.oid=a.attrelid and a.attnum=ANY(ix.indkey)
WHERE
t.oid = :table_oid and
ix.indisprimary = 't'
ORDER BY
a.attnum
"""
t = sql.text(PK_SQL, typemap={'attname':sqltypes.Unicode})
WHERE
t.oid = :table_oid and ix.indisprimary = 't'
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})
c = connection.execute(t, table_oid=table_oid)
primary_keys = [r[0] for r in c.fetchall()]
return primary_keys
@@ -1610,7 +1635,6 @@ class PGDialect(default.DefaultDialect):
e.enumlabel as "label"
FROM pg_catalog.pg_type t
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
WHERE t.typtype = 'e'
ORDER BY "name", e.oid -- e.oid gives us label order
@@ -1625,8 +1649,8 @@ class PGDialect(default.DefaultDialect):
for enum in c.fetchall():
if enum['visible']:
# 'visible' just means whether or not the enum is in a
# schema that's on the search path -- or not overriden by
# a schema with higher presedence. If it's not visible,
# schema that's on the search path -- or not overridden by
# a schema with higher precedence. If it's not visible,
# it will be prefixed with the schema-name when it's used.
name = enum['name']
else:
@@ -1652,7 +1676,6 @@ class PGDialect(default.DefaultDialect):
n.nspname as "schema"
FROM pg_catalog.pg_type t
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'
"""
@@ -1665,8 +1688,8 @@ class PGDialect(default.DefaultDialect):
attype = re.search('([^\(]+)', domain['attype']).group(1)
if domain['visible']:
# 'visible' just means whether or not the domain is in a
# schema that's on the search path -- or not overriden by
# a schema with higher presedence. If it's not visible,
# schema that's on the search path -- or not overridden by
# a schema with higher precedence. If it's not visible,
# it will be prefixed with the schema-name when it's used.
name = domain['name']
else:
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -1,5 +1,5 @@
# 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
# 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')
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
``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.
# TODO: these are sent through gettext in libpq and we can't
# 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 \
'could not receive data from server' in str(e)
elif isinstance(e, self.dbapi.InterfaceError):
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -40,7 +40,7 @@ class PGDialect_pypostgresql(PGDialect):
default_paramstyle = 'pyformat'
# 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_multi_rowcount = False
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+1 -1
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+176 -77
View File
@@ -1,5 +1,5 @@
# 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
# 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
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
@@ -103,13 +171,13 @@ class DATETIME(_DateTimeMixin, sqltypes.DateTime):
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,
value.minute, value.second, value.microsecond)``, given a
Python datetime.datetime() object.
: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,
match_obj.groups(0))``.
"""
@@ -164,12 +232,12 @@ class DATE(_DateTimeMixin, sqltypes.Date):
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)``,
given a Python datetime.date() object.
: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,
match_obj.groups(0))``.
@@ -221,12 +289,12 @@ class TIME(_DateTimeMixin, sqltypes.Time):
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,
value.microsecond)``, given a Python datetime.time() object.
: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,
match_obj.groups(0))``.
@@ -302,6 +370,9 @@ class SQLiteCompiler(compiler.SQLCompiler):
def visit_now_func(self, fn, **kw):
return "CURRENT_TIMESTAMP"
def visit_localtimestamp_func(self, func, **kw):
return 'DATETIME(CURRENT_TIMESTAMP, "localtime")'
def visit_true(self, expr, **kw):
return '1'
@@ -441,6 +512,22 @@ class SQLiteIdentifierPreparer(compiler.IdentifierPreparer):
result = self.quote_schema(index.table.schema, index.table.quote_schema) + "." + 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):
name = 'sqlite'
supports_alter = False
@@ -451,6 +538,7 @@ class SQLiteDialect(default.DefaultDialect):
supports_cast = True
default_paramstyle = 'qmark'
execution_ctx_cls = SQLiteExecutionContext
statement_compiler = SQLiteCompiler
ddl_compiler = SQLiteDDLCompiler
type_compiler = SQLiteTypeCompiler
@@ -462,6 +550,8 @@ class SQLiteDialect(default.DefaultDialect):
supports_cast = True
supports_default_values = True
_broken_fk_pragma_quotes = False
def __init__(self, isolation_level=None, native_datetime=False, **kwargs):
default.DefaultDialect.__init__(self, **kwargs)
self.isolation_level = isolation_level
@@ -478,6 +568,12 @@ class SQLiteDialect(default.DefaultDialect):
self.supports_cast = \
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 = {
'READ UNCOMMITTED':1,
'SERIALIZABLE':0
@@ -525,16 +621,6 @@ class SQLiteDialect(default.DefaultDialect):
else:
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
def get_table_names(self, connection, schema=None, **kw):
if schema is not None:
@@ -633,43 +719,50 @@ class SQLiteDialect(default.DefaultDialect):
c = _pragma_cursor(
connection.execute("%stable_info(%s)" %
(pragma, qtable)))
found_table = False
columns = []
while True:
row = c.fetchone()
if row is None:
break
(name, type_, nullable, default, has_default, primary_key) = \
(row[1], row[2].upper(), not row[3],
row[4], row[4] is not None, row[5])
name = re.sub(r'^\"|\"$', '', name)
match = re.match(r'(\w+)(\(.*?\))?', type_)
if match:
coltype = match.group(1)
args = match.group(2)
else:
coltype = "VARCHAR"
args = ''
try:
coltype = self.ischema_names[coltype]
if args is not None:
args = re.findall(r'(\d+)', args)
coltype = coltype(*[int(a) for a in args])
except KeyError:
util.warn("Did not recognize type '%s' of column '%s'" %
(coltype, name))
coltype = sqltypes.NullType()
columns.append({
'name' : name,
'type' : coltype,
'nullable' : nullable,
'default' : default,
'autoincrement':default is None,
'primary_key': primary_key
})
rows = c.fetchall()
columns = []
for row in rows:
(name, type_, nullable, default, primary_key) = \
(row[1], row[2].upper(), not row[3],
row[4], row[5])
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_)
if match:
coltype = match.group(1)
args = match.group(2)
else:
coltype = "VARCHAR"
args = ''
try:
coltype = self.ischema_names[coltype]
if args is not None:
args = re.findall(r'(\d+)', args)
coltype = coltype(*[int(a) for a in args])
except KeyError:
util.warn("Did not recognize type '%s' of column '%s'" %
(coltype, name))
coltype = sqltypes.NullType()
if default is not None:
default = unicode(default)
return {
'name': name,
'type': coltype,
'nullable': nullable,
'default': default,
'autoincrement': default is None,
'primary_key': primary_key
}
@reflection.cache
def get_primary_keys(self, connection, table_name, schema=None, **kw):
cols = self.get_columns(connection, table_name, schema, **kw)
@@ -687,7 +780,8 @@ class SQLiteDialect(default.DefaultDialect):
else:
pragma = "PRAGMA "
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 = []
fks = {}
while True:
@@ -695,34 +789,38 @@ class SQLiteDialect(default.DefaultDialect):
if row is None:
break
(numerical_id, rtbl, lcol, rcol) = (row[0], row[2], row[3], row[4])
# sqlite won't return rcol if the table
# was created with REFERENCES <tablename>, no col
if rcol is None:
rcol = lcol
rtbl = re.sub(r'^\"|\"$', '', rtbl)
lcol = re.sub(r'^\"|\"$', '', lcol)
rcol = re.sub(r'^\"|\"$', '', rcol)
try:
fk = fks[numerical_id]
except KeyError:
fk = {
'name' : None,
'constrained_columns' : [],
'referred_schema' : None,
'referred_table' : rtbl,
'referred_columns' : []
}
fkeys.append(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']:
fk['constrained_columns'].append(lcol)
if rcol not in fk['referred_columns']:
fk['referred_columns'].append(rcol)
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
# was created with REFERENCES <tablename>, no col
if rcol is None:
rcol = lcol
if self._broken_fk_pragma_quotes:
rtbl = re.sub(r'^[\"\[`\']|[\"\]`\']$', '', rtbl)
try:
fk = fks[numerical_id]
except KeyError:
fk = {
'name': None,
'constrained_columns': [],
'referred_schema': None,
'referred_table': rtbl,
'referred_columns': []
}
fkeys.append(fk)
fks[numerical_id] = fk
if lcol not in fk['constrained_columns']:
fk['constrained_columns'].append(lcol)
if rcol not in fk['referred_columns']:
fk['referred_columns'].append(rcol)
return fk
@reflection.cache
def get_indexes(self, connection, table_name, schema=None, **kw):
quote = self.identifier_preparer.quote_identifier
@@ -762,4 +860,5 @@ def _pragma_cursor(cursor):
if cursor.closed:
cursor.fetchone = lambda: None
cursor.fetchall = lambda: []
return cursor
+15 -12
View File
@@ -1,5 +1,5 @@
# 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
# 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
in more than one thread. This is controlled by the ``check_same_thread``
Pysqlite flag. This default is intended to work with older versions
in more than one thread. This is originally intended to work with older versions
of SQLite that did not support multithreaded operation under
various circumstances. In particular, older SQLite versions
did not allow a ``:memory:`` database to be used in multiple threads
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:
* 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
best with SQLite's coarse-grained file locking.
.. note::
.. versionchanged:: 0.7
Default selection of :class:`.NullPool` for SQLite file-based databases.
Previous versions select :class:`.SingletonThreadPool` by
default for all SQLite databases.
The default selection of :class:`.NullPool` for SQLite file-based databases
is new in SQLAlchemy 0.7. Previous versions
select :class:`.SingletonThreadPool` by
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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+1 -1
View File
@@ -1,5 +1,5 @@
# 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
# 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).
Note that this dialect is no longer specific to Sybase iAnywhere.
ASE is the primary support platform.
.. note::
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
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+1 -1
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+5 -5
View File
@@ -52,7 +52,7 @@ class SybaseExecutionContext_pysybase(SybaseExecutionContext):
class SybaseSQLCompiler_pysybase(SybaseSQLCompiler):
def bindparam_string(self, name):
def bindparam_string(self, name, **kw):
return "@" + name
class SybaseDialect_pysybase(SybaseDialect):
@@ -82,10 +82,10 @@ class SybaseDialect_pysybase(SybaseDialect):
cursor.execute(statement, param)
def _get_server_version_info(self, connection):
vers = connection.scalar("select @@version_number")
# i.e. 15500, 15000, 12500 == (15, 5, 0, 0), (15, 0, 0, 0),
# (12, 5, 0, 0)
return (vers / 1000, vers % 1000 / 100, vers % 100 / 10, vers % 10)
vers = connection.scalar("select @@version_number")
# i.e. 15500, 15000, 12500 == (15, 5, 0, 0), (15, 0, 0, 0),
# (12, 5, 0, 0)
return (vers / 1000, vers % 1000 / 100, vers % 100 / 10, vers % 10)
def is_disconnect(self, e, connection, cursor):
if isinstance(e, (self.dbapi.OperationalError,
+15 -4
View File
@@ -1,5 +1,5 @@
# 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
# 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()``,
``commit()``, or nothing is called upon connections
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
up on getting a connection from the pool. This is only used
with :class:`~sqlalchemy.pool.QueuePool`.
:param strategy='plain': selects alternate engine implementations.
Currently available is the ``threadlocal``
strategy, which is described in :ref:`threadlocal_strategy`.
Currently available are:
* 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'``.
"""
+80 -36
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -663,13 +663,10 @@ class ExecutionContext(object):
raise NotImplementedError()
def get_rowcount(self):
"""Return the number of rows produced (by a SELECT query)
or affected (by an INSERT/UPDATE/DELETE statement).
"""Return the DBAPI ``cursor.rowcount`` value, or in some
cases an interpreted value.
Note that this row count may not be properly implemented
in some dialects; this is indicated by the
``supports_sane_rowcount`` and ``supports_sane_multi_rowcount``
dialect attributes.
See :attr:`.ResultProxy.rowcount` for details on this.
"""
@@ -734,7 +731,7 @@ class Compiled(object):
def construct_params(self, params=None):
"""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
statement.
"""
@@ -978,10 +975,10 @@ class Connection(Connectable):
:ref:`postgresql_toplevel` - see those dialect's documentation
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
: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.
the :meth:`.Connection.close` method is called.
@@ -995,7 +992,9 @@ class Connection(Connectable):
containing percent signs (and possibly other characters)
that is neutral regarding whether it's executed by the DBAPI
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.
Indicate to the dialect that results should be
@@ -1074,7 +1073,7 @@ class Connection(Connectable):
"""Returns self.
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.
"""
@@ -1084,7 +1083,7 @@ class Connection(Connectable):
"""Returns self.
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.
"""
@@ -1123,7 +1122,7 @@ class Connection(Connectable):
def detach(self):
"""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
returned to its pool. The pool will typically lazily create a
new connection to replace the detached connection.
@@ -1471,7 +1470,8 @@ class Connection(Connectable):
elif len(multiparams) == 1:
zero = multiparams[0]
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
else:
return [zero]
@@ -1480,7 +1480,8 @@ class Connection(Connectable):
else:
return [[zero]]
else:
if hasattr(multiparams[0], '__iter__'):
if hasattr(multiparams[0], '__iter__') and \
not hasattr(multiparams[0], 'strip'):
return multiparams
else:
return [multiparams]
@@ -1714,7 +1715,6 @@ class Connection(Connectable):
# create a resultproxy, get rowcount/implicit RETURNING
# rows, close cursor if no further results pending
result = context.get_result_proxy()
if context.isinsert:
if context._is_implicit_returning:
context._fetch_implicit_returning(result)
@@ -1806,10 +1806,19 @@ class Connection(Connectable):
(statement is not None and context is None)
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)
is_disconnect = isinstance(e, self.dialect.dbapi.Error) and \
self.dialect.is_disconnect(e, self.__connection, cursor)
if is_disconnect:
self.invalidate(e)
self.engine.dispose()
@@ -2130,7 +2139,7 @@ class Engine(Connectable, log.Identified):
:class:`~sqlalchemy.engine.base.Dialect` together to provide a source
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.
See also:
@@ -2178,7 +2187,7 @@ class Engine(Connectable, log.Identified):
The given keys/values in \**opt are added to the
default execution options that will be used for
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`.
See :meth:`.Connection.execution_options` for more
@@ -2232,7 +2241,7 @@ class Engine(Connectable, log.Identified):
"""
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 "
"object directly, i.e. :meth:`.Table.create`, "
@@ -2334,7 +2343,7 @@ class Engine(Connectable, log.Identified):
close the :class:`.Connection` when that :class:`.ResultProxy`
has exhausted all result rows.
New in 0.7.6.
.. versionadded:: 0.7.6
See also:
@@ -2346,7 +2355,11 @@ class Engine(Connectable, log.Identified):
"""
conn = self.contextual_connect(close_with_result=close_with_result)
trans = conn.begin()
try:
trans = conn.begin()
except:
conn.close()
raise
return Engine._trans_ctx(conn, trans, close_with_result)
def transaction(self, callable_, *args, **kwargs):
@@ -2710,7 +2723,7 @@ class ResultMetaData(object):
context = parent.context
dialect = context.dialect
typemap = dialect.dbapi_type_map
translate_colname = dialect._translate_colname
translate_colname = context._translate_colname
# high precedence key values.
primary_keymap = {}
@@ -2907,18 +2920,41 @@ class ResultProxy(object):
def rowcount(self):
"""Return the 'rowcount' for this result.
The 'rowcount' reports the number of rows affected
by an UPDATE or DELETE statement. It has *no* other
uses and is not intended to provide the number of rows
present from a SELECT.
The 'rowcount' reports the number of rows *matched*
by the WHERE criterion of an UPDATE or DELETE statement.
Note that this row count may not be properly implemented in some
dialects; this is indicated by
:meth:`~sqlalchemy.engine.base.ResultProxy.supports_sane_rowcount()`
and
:meth:`~sqlalchemy.engine.base.ResultProxy.supports_sane_multi_rowcount()`.
``rowcount()`` also may not work at this time for a statement that
uses ``returning()``.
.. note::
Notes regarding :attr:`.ResultProxy.rowcount`:
* This attribute returns the number of rows *matched*,
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:
@@ -3098,12 +3134,20 @@ class ResultProxy(object):
return self.context.prefetch_cols
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
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
+9 -7
View File
@@ -1,5 +1,5 @@
# 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
# 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.
requires_name_normalize = False
# a hook for SQLite's translation of
# result column names
_translate_colname = None
reflection_options = ()
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)
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().
"""
@@ -341,7 +337,7 @@ class DefaultDialect(base.Dialect):
return False
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
# the configured default of this dialect.
self.set_isolation_level(dbapi_conn, self.default_isolation_level)
@@ -355,9 +351,15 @@ class DefaultExecutionContext(base.ExecutionContext):
result_map = None
compiled = None
statement = None
postfetch_cols = None
prefetch_cols = None
_is_implicit_returning = False
_is_explicit_returning = False
# a hook for SQLite's translation of
# result column names
_translate_colname = None
@classmethod
def _init_ddl(cls, dialect, connection, dbapi_connection, compiled_ddl):
"""Initialize execution context for a DDLElement construct."""
+2 -2
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -379,7 +379,7 @@ class Inspector(object):
coltype = col_d['type']
col_kw = {
'nullable':col_d['nullable'],
'nullable': col_d['nullable'],
}
for k in ('autoincrement', 'quote', 'info', 'key'):
if k in col_d:
+4 -4
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -24,7 +24,7 @@ strategies = {}
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
produces an instance of base.Engine or a subclass.
@@ -41,7 +41,7 @@ class EngineStrategy(object):
class DefaultEngineStrategy(EngineStrategy):
"""Base class for built-in stratgies."""
"""Base class for built-in strategies."""
def create(self, name_or_url, **kwargs):
# create url.URL object
@@ -180,7 +180,7 @@ PlainEngineStrategy()
class ThreadLocalEngineStrategy(DefaultEngineStrategy):
"""Strategy for configuring an Engine with thredlocal behavior."""
"""Strategy for configuring an Engine with threadlocal behavior."""
name = 'threadlocal'
engine_cls = threadlocal.TLEngine
+1 -1
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+1 -1
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+111 -15
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -7,6 +7,7 @@
"""Base event API."""
from sqlalchemy import util, exc
import weakref
CANCEL = util.symbol('CANCEL')
NO_RETVAL = util.symbol('NO_RETVAL')
@@ -37,7 +38,7 @@ def listen(target, identifier, fn, *args, **kw):
tgt.dispatch._listen(tgt, identifier, fn, *args, **kw)
return
raise exc.InvalidRequestError("No such event '%s' for target '%s'" %
(identifier,target))
(identifier, target))
def listens_for(target, identifier, *args, **kw):
"""Decorate a function as a listener for the given target + identifier.
@@ -69,7 +70,7 @@ def remove(target, identifier, fn):
"""
for evt_cls in _registrars[identifier]:
for tgt in evt_cls._accept_with(target):
tgt.dispatch._remove(identifier, tgt, fn, *args, **kw)
tgt.dispatch._remove(identifier, tgt, fn)
return
_registrars = util.defaultdict(list)
@@ -120,7 +121,8 @@ class _Dispatch(object):
object."""
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):
return [getattr(target, k) for k in dir(target) if _is_event_name(k)]
@@ -180,9 +182,11 @@ class Events(object):
@classmethod
def _listen(cls, target, identifier, fn, propagate=False, insert=False):
if insert:
getattr(target.dispatch, identifier).insert(fn, target, propagate)
getattr(target.dispatch, identifier).\
for_modify(target.dispatch).insert(fn, target, propagate)
else:
getattr(target.dispatch, identifier).append(fn, target, propagate)
getattr(target.dispatch, identifier).\
for_modify(target.dispatch).append(fn, target, propagate)
@classmethod
def _remove(cls, target, identifier, fn):
@@ -200,7 +204,12 @@ class _DispatchDescriptor(object):
def __init__(self, fn):
self.__name__ = fn.__name__
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):
assert isinstance(target, type), \
@@ -212,6 +221,8 @@ class _DispatchDescriptor(object):
if cls is not target and cls not in self._clslevel:
self.update_subclass(cls)
else:
if cls not in self._clslevel:
self._clslevel[cls] = []
self._clslevel[cls].insert(0, obj)
def append(self, obj, target, propagate):
@@ -225,9 +236,13 @@ class _DispatchDescriptor(object):
if cls is not target and cls not in self._clslevel:
self.update_subclass(cls)
else:
if cls not in self._clslevel:
self._clslevel[cls] = []
self._clslevel[cls].append(obj)
def update_subclass(self, target):
if target not in self._clslevel:
self._clslevel[target] = []
clslevel = self._clslevel[target]
for cls in target.__mro__[1:]:
if cls in self._clslevel:
@@ -242,7 +257,8 @@ class _DispatchDescriptor(object):
while stack:
cls = stack.pop(0)
stack.extend(cls.__subclasses__())
self._clslevel[cls].remove(obj)
if cls in self._clslevel:
self._clslevel[cls].remove(obj)
def clear(self):
"""Clear all class level listeners"""
@@ -250,18 +266,91 @@ class _DispatchDescriptor(object):
for dispatcher in self._clslevel.values():
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):
if obj is None:
return self
obj.__dict__[self.__name__] = result = \
_ListenerCollection(self, obj._parent_cls)
elif obj._parent_cls in self._empty_listeners:
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
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):
"""Instance-level attributes on instances of :class:`._Dispatch`.
Represents a collection of listeners.
As of 0.7.9, _ListenerCollection is only first
created via the _EmptyListener.for_modify() method.
"""
_exec_once = False
@@ -274,6 +363,15 @@ class _ListenerCollection(object):
self.listeners = []
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):
"""Execute this event, but only if it has not been
executed already for this collection."""
@@ -294,11 +392,9 @@ class _ListenerCollection(object):
# but this allows class-level listeners to be added
# at any point.
#
# alternatively, _DispatchDescriptor could notify
# all _ListenerCollection objects, but then we move
# to a higher memory model, i.e.weakrefs to all _ListenerCollection
# objects, the _DispatchDescriptor collection repeated
# for all instances.
# In the absense of instance-level listeners,
# we stay with the _EmptyListener object when called
# at the instance level.
def __len__(self):
return len(self.parent_listeners + self.listeners)
+31 -1
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -401,6 +401,36 @@ class ConnectionEvents(event.Events):
parameters, context, executemany):
"""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):
"""Intercept begin() events."""
+1 -1
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+1 -1
View File
@@ -1,5 +1,5 @@
# ext/__init__.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+4 -4
View File
@@ -1,5 +1,5 @@
# ext/associationproxy.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -141,7 +141,7 @@ class AssociationProxy(object):
"""The 'remote' :class:`.MapperProperty` referenced by this
:class:`.AssociationProxy`.
New in 0.7.3.
.. versionadded:: 0.7.3
See also:
@@ -157,7 +157,7 @@ class AssociationProxy(object):
"""The 'local' :class:`.MapperProperty` referenced by this
:class:`.AssociationProxy`.
New in 0.7.3.
.. versionadded:: 0.7.3
See also:
@@ -177,7 +177,7 @@ class AssociationProxy(object):
sess.query(Parent).join(*Parent.proxied.attr)
New in 0.7.3.
.. versionadded:: 0.7.3
See also:
+1 -1
View File
@@ -1,5 +1,5 @@
# ext/compiler.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+34 -36
View File
@@ -1,5 +1,5 @@
# ext/declarative.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -86,16 +86,6 @@ CREATE statements for all tables::
engine = create_engine('sqlite://')
Base.metadata.create_all(engine)
The usual techniques of associating :class:`.MetaData:` with :class:`.Engine`
apply, such as assigning to the ``bind`` attribute::
Base.metadata.bind = create_engine('sqlite://')
To associate the engine with the :func:`declarative_base` at time
of construction, the ``bind`` argument is accepted::
Base = declarative_base(bind=create_engine('sqlite://'))
:func:`declarative_base` can also receive a pre-existing
:class:`.MetaData` object, which allows a
declarative setup to be associated with an already
@@ -400,8 +390,8 @@ table::
Note that above, the ``Engineer.id`` attribute, since it shares the
same attribute name as the ``Person.id`` attribute, will in fact
represent the ``people.id`` and ``engineers.id`` columns together, and
will render inside a query as ``"people.id"``.
represent the ``people.id`` and ``engineers.id`` columns together,
with the "Engineer.id" column taking precedence if queried directly.
To provide the ``Engineer`` class with an attribute that represents
only the ``engineers.id`` column, give it a different attribute name::
@@ -412,6 +402,11 @@ only the ``engineers.id`` column, give it a different attribute name::
primary_key=True)
primary_language = Column(String(50))
.. versionchanged:: 0.7 joined table inheritance favors the subclass
column over that of the superclass, such as querying above
for ``Engineer.id``. Prior to 0.7 this was the reverse.
Single Table Inheritance
~~~~~~~~~~~~~~~~~~~~~~~~
@@ -506,10 +501,12 @@ before the class is built::
Using the Concrete Helpers
^^^^^^^^^^^^^^^^^^^^^^^^^^^
New helper classes released in 0.7.3 provides a simpler pattern for concrete inheritance.
Helper classes provides a simpler pattern for concrete inheritance.
With these objects, the ``__declare_last__`` helper is used to configure the "polymorphic"
loader for the mapper after all subclasses have been declared.
.. versionadded:: 0.7.3
An abstract base can be declared using the :class:`.AbstractConcreteBase` class::
from sqlalchemy.ext.declarative import AbstractConcreteBase
@@ -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
have foreign keys, as well as for the variety of mapper-level constructs
that require destination-explicit context, the
:func:`~.declared_attr` decorator (renamed from ``sqlalchemy.util.classproperty`` in 0.6.5)
is provided so that
:func:`~.declared_attr` decorator is provided so that
patterns common to many classes can be defined as callables::
from sqlalchemy.ext.declarative import declared_attr
@@ -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
the method without the need to copy it.
.. versionchanged:: > 0.6.5
Rename 0.6.5 ``sqlalchemy.util.classproperty`` into :func:`~.declared_attr`.
Columns generated by :func:`~.declared_attr` can also be
referenced by ``__mapper_args__`` to a limited degree, currently
by ``polymorphic_on`` and ``version_id_col``, by specifying the
@@ -713,7 +712,7 @@ Mixing in Relationships
Relationships created by :func:`~sqlalchemy.orm.relationship` are provided
with declarative mixin classes exclusively using the
:func:`.declared_attr` approach, eliminating any ambiguity
:class:`.declared_attr` approach, eliminating any ambiguity
which could arise when copying a relationship and its possibly column-bound
contents. Below is an example which combines a foreign key column and a
relationship so that two classes ``Foo`` and ``Bar`` can both be configured to
@@ -744,7 +743,7 @@ reference a common target class via many-to-one::
primaryjoin, order_by etc. expressions should use the string forms
for these arguments, so that they are evaluated as late as possible.
To reference the mixin class in these expressions, use the given ``cls``
to get it's name::
to get its name::
class RefTargetMixin(object):
@declared_attr
@@ -764,7 +763,7 @@ Like :func:`~sqlalchemy.orm.relationship`, all
:class:`~sqlalchemy.orm.interfaces.MapperProperty` subclasses such as
:func:`~sqlalchemy.orm.deferred`, :func:`~sqlalchemy.orm.column_property`,
etc. ultimately involve references to columns, and therefore, when
used with declarative mixins, have the :func:`.declared_attr`
used with declarative mixins, have the :class:`.declared_attr`
requirement so that no reliance on copying is needed::
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
some parameters from several mixins with those you wish to
define on the class iteself. The
:func:`.declared_attr` decorator can be used
:class:`.declared_attr` decorator can be used
here to create user-defined collation routines that pull
from multiple collections::
@@ -928,7 +927,7 @@ Special Directives
``__declare_last__()``
~~~~~~~~~~~~~~~~~~~~~~
The ``__declare_last__()`` hook, introduced in 0.7.3, allows definition of
The ``__declare_last__()`` hook allows definition of
a class level function that is automatically called by the :meth:`.MapperEvents.after_configured`
event, which occurs after mappings are assumed to be completed and the 'configure' step
has finished::
@@ -939,12 +938,14 @@ has finished::
""
# do something with mappings
.. versionadded:: 0.7.3
.. _declarative_abstract:
``__abstract__``
~~~~~~~~~~~~~~~~~~~
``__abstract__`` is introduced in 0.7.3 and causes declarative to skip the production
``__abstract__`` causes declarative to skip the production
of a table or mapper for the class entirely. A class can be added within a hierarchy
in the same way as mixin (see :ref:`declarative_mixins`), allowing subclasses to extend
just from the special class::
@@ -982,6 +983,8 @@ The tables themselves can then be created perhaps within distinct databases::
DefaultBase.metadata.create_all(some_engine)
OtherBase.metadata_create_all(some_other_engine)
.. versionadded:: 0.7.3
Class Constructor
=================
@@ -1322,11 +1325,9 @@ def _as_declarative(cls, classname, dict_):
if k in inherited_mapper._props:
p = inherited_mapper._props[k]
if isinstance(p, ColumnProperty):
# note here we place the superclass column
# first. this corresponds to the
# append() in mapper._configure_property().
# change this ordering when we do [ticket:1892]
our_stuff[k] = p.columns + [col]
# note here we place the subclass column
# first. See [ticket:1892] for background.
our_stuff[k] = [col] + p.columns
cls.__mapper__ = mapper_cls(cls,
@@ -1336,11 +1337,9 @@ def _as_declarative(cls, classname, dict_):
class DeclarativeMeta(type):
def __init__(cls, classname, bases, dict_):
if '_decl_class_registry' in cls.__dict__:
return type.__init__(cls, classname, bases, dict_)
else:
if '_decl_class_registry' not in cls.__dict__:
_as_declarative(cls, classname, cls.__dict__)
return type.__init__(cls, classname, bases, dict_)
type.__init__(cls, classname, bases, dict_)
def __setattr__(cls, key, value):
if '__mapper__' in cls.__dict__:
@@ -1493,11 +1492,10 @@ class declared_attr(property):
"""Mark a class-level method as representing the definition of
a mapped property or special declarative member name.
.. note::
@declared_attr is available as
``sqlalchemy.util.classproperty`` for SQLAlchemy versions
0.6.2, 0.6.3, 0.6.4.
.. versionchanged:: 0.6.{2,3,4}
``@declared_attr`` is available as
``sqlalchemy.util.classproperty`` for SQLAlchemy versions
0.6.2, 0.6.3, 0.6.4.
@declared_attr turns the attribute into a scalar-like
property that can be invoked from the uninstantiated class.
+2 -2
View File
@@ -1,5 +1,5 @@
# ext/horizontal_shard.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -10,7 +10,7 @@ Defines a rudimental 'horizontal sharding' system which allows a Session to
distribute queries and persistence operations across multiple databases.
For a usage example, see the :ref:`examples_sharding` example included in
the source distrbution.
the source distribution.
"""
+79 -14
View File
@@ -1,5 +1,5 @@
# ext/hybrid.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -179,9 +179,16 @@ The ``length(self, value)`` method is now called upon set::
Working with Relationships
--------------------------
There's no essential difference when creating hybrids that work with related objects as
opposed to column-based data. The need for distinct expressions tends to be greater.
Consider the following declarative mapping which relates a ``User`` to a ``SavingsAccount``::
There's no essential difference when creating hybrids that work with
related objects as opposed to column-based data. The need for distinct
expressions tends to be greater. Two variants of we'll illustrate
are the "join-dependent" hybrid, and the "correlated subquery" hybrid.
Join-Dependent Relationship Hybrid
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Consider the following declarative
mapping which relates a ``User`` to a ``SavingsAccount``::
from sqlalchemy import Column, Integer, ForeignKey, Numeric, String
from sqlalchemy.orm import relationship
@@ -222,30 +229,88 @@ Consider the following declarative mapping which relates a ``User`` to a ``Savin
def balance(cls):
return SavingsAccount.balance
The above hybrid property ``balance`` works with the first ``SavingsAccount`` entry in the list of
accounts for this user. The in-Python getter/setter methods can treat ``accounts`` as a Python
The above hybrid property ``balance`` works with the first
``SavingsAccount`` entry in the list of accounts for this user. The
in-Python getter/setter methods can treat ``accounts`` as a Python
list available on ``self``.
However, at the expression level, we can't travel along relationships to column attributes
directly since SQLAlchemy is explicit about joins. So here, it's expected that the ``User`` class will be
used in an appropriate context such that an appropriate join to ``SavingsAccount`` will be present::
However, at the expression level, it's expected that the ``User`` class will be used
in an appropriate context such that an appropriate join to
``SavingsAccount`` will be present::
>>> print Session().query(User, User.balance).join(User.accounts).filter(User.balance > 5000)
SELECT "user".id AS user_id, "user".name AS user_name, account.balance AS account_balance
>>> print Session().query(User, User.balance).\\
... join(User.accounts).filter(User.balance > 5000)
SELECT "user".id AS user_id, "user".name AS user_name,
account.balance AS account_balance
FROM "user" JOIN account ON "user".id = account.user_id
WHERE account.balance > :balance_1
Note however, that while the instance level accessors need to worry about whether ``self.accounts``
is even present, this issue expresses itself differently at the SQL expression level, where we basically
Note however, that while the instance level accessors need to worry
about whether ``self.accounts`` is even present, this issue expresses
itself differently at the SQL expression level, where we basically
would use an outer join::
>>> from sqlalchemy import or_
>>> print (Session().query(User, User.balance).outerjoin(User.accounts).
... filter(or_(User.balance < 5000, User.balance == None)))
SELECT "user".id AS user_id, "user".name AS user_name, account.balance AS account_balance
SELECT "user".id AS user_id, "user".name AS user_name,
account.balance AS account_balance
FROM "user" LEFT OUTER JOIN account ON "user".id = account.user_id
WHERE account.balance < :balance_1 OR account.balance IS NULL
Correlated Subquery Relationship Hybrid
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
We can, of course, forego being dependent on the enclosing query's usage
of joins in favor of the correlated
subquery, which can portably be packed into a single colunn expression.
A correlated subquery is more portable, but often performs more poorly
at the SQL level.
Using the same technique illustrated at :ref:`mapper_column_property_sql_expressions`,
we can adjust our ``SavingsAccount`` example to aggregate the balances for
*all* accounts, and use a correlated subquery for the column expression::
from sqlalchemy import Column, Integer, ForeignKey, Numeric, String
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy import select, func
Base = declarative_base()
class SavingsAccount(Base):
__tablename__ = 'account'
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('user.id'), nullable=False)
balance = Column(Numeric(15, 5))
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String(100), nullable=False)
accounts = relationship("SavingsAccount", backref="owner")
@hybrid_property
def balance(self):
return sum(acc.balance for acc in self.accounts)
@balance.expression
def balance(cls):
return select([func.sum(SavingsAccount.balance)]).\\
where(SavingsAccount.user_id==cls.id).\\
label('total_balance')
The above recipe will give us the ``balance`` column which renders
a correlated SELECT::
>>> print s.query(User).filter(User.balance > 400)
SELECT "user".id AS user_id, "user".name AS user_name
FROM "user"
WHERE (SELECT sum(account.balance) AS sum_1
FROM account
WHERE account.user_id = "user".id) > :param_1
.. _hybrid_custom_comparators:
Building Custom Comparators
+65 -32
View File
@@ -1,5 +1,5 @@
# ext/mutable.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -180,7 +180,7 @@ not picklable, due to the fact that they contain weakrefs and function
callbacks. In our case, this is a good thing, since if this dictionary were
picklable, it could lead to an excessively large pickle size for our value
objects that are pickled by themselves outside of the context of the parent.
The developer responsiblity here is only to provide a ``__getstate__`` method
The developer responsibility here is only to provide a ``__getstate__`` method
that excludes the :meth:`~.MutableBase._parents` collection from the pickle
stream::
@@ -217,12 +217,13 @@ be assigned an object value which represents information "composed" from one
or more columns from the underlying mapped table. The usual example is that of
a geometric "point", and is introduced in :ref:`mapper_composite`.
As of SQLAlchemy 0.7, the internals of :func:`.orm.composite` have been
greatly simplified and in-place mutation detection is no longer enabled by
default; instead, the user-defined value must detect changes on its own and
propagate them to all owning parents. The :mod:`sqlalchemy.ext.mutable`
extension provides the helper class :class:`.MutableComposite`, which is a
slight variant on the :class:`.Mutable` class.
.. versionchanged:: 0.7
The internals of :func:`.orm.composite` have been
greatly simplified and in-place mutation detection is no longer enabled by
default; instead, the user-defined value must detect changes on its own and
propagate them to all owning parents. The :mod:`sqlalchemy.ext.mutable`
extension provides the helper class :class:`.MutableComposite`, which is a
slight variant on the :class:`.Mutable` class.
As is the case with :class:`.Mutable`, the user-defined composite class
subclasses :class:`.MutableComposite` as a mixin, and detects and delivers
@@ -300,6 +301,31 @@ will flag the attribute as "dirty" on the parent object::
>>> assert v1 in sess.dirty
True
Coercing Mutable Composites
---------------------------
The :meth:`.MutableBase.coerce` method is also supported on composite types.
In the case of :class:`.MutableComposite`, the :meth:`.MutableBase.coerce`
method is only called for attribute set operations, not load operations.
Overriding the :meth:`.MutableBase.coerce` method is essentially equivalent
to using a :func:`.validates` validation routine for all attributes which
make use of the custom composite type::
class Point(MutableComposite):
# other Point methods
# ...
def coerce(cls, key, value):
if isinstance(value, tuple):
value = Point(*value)
elif not isinstance(value, Point):
raise ValueError("tuple or Point expected")
return value
.. versionadded:: 0.7.10,0.8.0b2
Support for the :meth:`.MutableBase.coerce` method in conjunction with
objects of type :class:`.MutableComposite`.
Supporting Pickling
--------------------
@@ -327,7 +353,7 @@ pickling process of the parent's object-relational state so that the
"""
from sqlalchemy.orm.attributes import flag_modified
from sqlalchemy import event, types
from sqlalchemy.orm import mapper, object_mapper
from sqlalchemy.orm import mapper, object_mapper, Mapper
from sqlalchemy.util import memoized_property
import weakref
@@ -348,9 +374,27 @@ class MutableBase(object):
@classmethod
def coerce(cls, key, value):
"""Given a value, coerce it into this type.
"""Given a value, coerce it into the target type.
Can be overridden by custom subclasses to coerce incoming
data into a particular type.
By default, raises ``ValueError``.
This method is called in different scenarios depending on if
the parent class is of type :class:`.Mutable` or of type
:class:`.MutableComposite`. In the case of the former, it is called
for both attribute-set operations as well as during ORM loading
operations. For the latter, it is only called during attribute-set
operations; the mechanics of the :func:`.composite` construct
handle coercion during load operations.
:param key: string name of the ORM-mapped attribute being set.
:param value: the incoming value.
:return: the method should return the coerced value, or raise
``ValueError`` if the coercion cannot be completed.
By default raises ValueError.
"""
if value is None:
return None
@@ -511,11 +555,6 @@ class Mutable(MutableBase):
return sqltype
class _MutableCompositeMeta(type):
def __init__(cls, classname, bases, dict_):
cls._setup_listeners()
return type.__init__(cls, classname, bases, dict_)
class MutableComposite(MutableBase):
"""Mixin that defines transparent propagation of change
events on a SQLAlchemy "composite" object to its
@@ -532,7 +571,6 @@ class MutableComposite(MutableBase):
in memory usage.
"""
__metaclass__ = _MutableCompositeMeta
def changed(self):
"""Subclasses should call this method whenever change events occur."""
@@ -545,19 +583,14 @@ class MutableComposite(MutableBase):
prop._attribute_keys):
setattr(parent, attr_name, value)
@classmethod
def _setup_listeners(cls):
"""Associate this wrapper with all future mapped composites
of the given type.
This is a convenience method that calls ``associate_with_attribute`` automatically.
"""
def listen_for_type(mapper, class_):
for prop in mapper.iterate_properties:
if hasattr(prop, 'composite_class') and issubclass(prop.composite_class, cls):
cls._listen_on_attribute(getattr(class_, prop.key), False, class_)
event.listen(mapper, 'mapper_configured', listen_for_type)
def _setup_composite_listener():
def _listen_for_type(mapper, class_):
for prop in mapper.iterate_properties:
if (hasattr(prop, 'composite_class') and
issubclass(prop.composite_class, MutableComposite)):
prop.composite_class._listen_on_attribute(
getattr(class_, prop.key), False, class_)
if not Mapper.dispatch.mapper_configured._contains(Mapper, _listen_for_type):
event.listen(Mapper, 'mapper_configured', _listen_for_type)
_setup_composite_listener()
+120 -75
View File
@@ -1,64 +1,77 @@
# ext/orderinglist.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
"""A custom list that manages index/position information for its children.
"""A custom list that manages index/position information for contained
elements.
:author: Jason Kirtland
``orderinglist`` is a helper for mutable ordered relationships. It will intercept
list operations performed on a relationship collection and automatically
synchronize changes in list position with an attribute on the related objects.
(See :ref:`advdatamapping_entitycollections` for more information on the general pattern.)
``orderinglist`` is a helper for mutable ordered relationships. It will
intercept list operations performed on a :func:`.relationship`-managed
collection and
automatically synchronize changes in list position onto a target scalar
attribute.
Example: Two tables that store slides in a presentation. Each slide
has a number of bullet points, displayed in order by the 'position'
column on the bullets table. These bullets can be inserted and re-ordered
by your end users, and you need to update the 'position' column of all
affected rows when changes are made.
Example: A ``slide`` table, where each row refers to zero or more entries
in a related ``bullet`` table. The bullets within a slide are
displayed in order based on the value of the ``position`` column in the
``bullet`` table. As entries are reordered in memory, the value of the
``position`` attribute should be updated to reflect the new sort order::
.. sourcecode:: python+sql
slides_table = Table('Slides', metadata,
Column('id', Integer, primary_key=True),
Column('name', String))
Base = declarative_base()
bullets_table = Table('Bullets', metadata,
Column('id', Integer, primary_key=True),
Column('slide_id', Integer, ForeignKey('Slides.id')),
Column('position', Integer),
Column('text', String))
class Slide(Base):
__tablename__ = 'slide'
class Slide(object):
pass
class Bullet(object):
pass
id = Column(Integer, primary_key=True)
name = Column(String)
mapper(Slide, slides_table, properties={
'bullets': relationship(Bullet, order_by=[bullets_table.c.position])
})
mapper(Bullet, bullets_table)
bullets = relationship("Bullet", order_by="Bullet.position")
The standard relationship mapping will produce a list-like attribute on each Slide
containing all related Bullets, but coping with changes in ordering is totally
your responsibility. If you insert a Bullet into that list, there is no
magic- it won't have a position attribute unless you assign it it one, and
you'll need to manually renumber all the subsequent Bullets in the list to
accommodate the insert.
class Bullet(Base):
__tablename__ = 'bullet'
id = Column(Integer, primary_key=True)
slide_id = Column(Integer, ForeignKey('slide.id'))
position = Column(Integer)
text = Column(String)
An ``orderinglist`` can automate this and manage the 'position' attribute on all
related bullets for you.
The standard relationship mapping will produce a list-like attribute on each
``Slide`` containing all related ``Bullet`` objects,
but coping with changes in ordering is not handled automatically.
When appending a ``Bullet`` into ``Slide.bullets``, the ``Bullet.position``
attribute will remain unset until manually assigned. When the ``Bullet``
is inserted into the middle of the list, the following ``Bullet`` objects
will also need to be renumbered.
.. sourcecode:: python+sql
The :class:`.OrderingList` object automates this task, managing the
``position`` attribute on all ``Bullet`` objects in the collection. It is
constructed using the :func:`.ordering_list` factory::
mapper(Slide, slides_table, properties={
'bullets': relationship(Bullet,
collection_class=ordering_list('position'),
order_by=[bullets_table.c.position])
})
mapper(Bullet, bullets_table)
from sqlalchemy.ext.orderinglist import ordering_list
Base = declarative_base()
class Slide(Base):
__tablename__ = 'slide'
id = Column(Integer, primary_key=True)
name = Column(String)
bullets = relationship("Bullet", order_by="Bullet.position",
collection_class=ordering_list('position'))
class Bullet(Base):
__tablename__ = 'bullet'
id = Column(Integer, primary_key=True)
slide_id = Column(Integer, ForeignKey('slide.id'))
position = Column(Integer)
text = Column(String)
With the above mapping the ``Bullet.position`` attribute is managed::
s = Slide()
s.bullets.append(Bullet())
@@ -69,73 +82,87 @@ related bullets for you.
s.bullets[2].position
>>> 2
Use the ``ordering_list`` function to set up the ``collection_class`` on relationships
(as in the mapper example above). This implementation depends on the list
starting in the proper order, so be SURE to put an order_by on your relationship.
The :class:`.OrderingList` construct only works with **changes** to a collection,
and not the initial load from the database, and requires that the list be
sorted when loaded. Therefore, be sure to
specify ``order_by`` on the :func:`.relationship` against the target ordering
attribute, so that the ordering is correct when first loaded.
.. warning::
``ordering_list`` only provides limited functionality when a primary
key column or unique column is the target of the sort. Since changing the order of
entries often means that two rows must trade values, this is not possible when
the value is constrained by a primary key or unique constraint, since one of the rows
would temporarily have to point to a third available value so that the other row
could take its old value. ``ordering_list`` doesn't do any of this for you,
:class:`.OrderingList` only provides limited functionality when a primary
key column or unique column is the target of the sort. Since changing the
order of entries often means that two rows must trade values, this is not
possible when the value is constrained by a primary key or unique
constraint, since one of the rows would temporarily have to point to a
third available value so that the other row could take its old
value. :class:`.OrderingList` doesn't do any of this for you,
nor does SQLAlchemy itself.
``ordering_list`` takes the name of the related object's ordering attribute as
:func:`.ordering_list` takes the name of the related object's ordering attribute as
an argument. By default, the zero-based integer index of the object's
position in the ``ordering_list`` is synchronized with the ordering attribute:
position in the :func:`.ordering_list` is synchronized with the ordering attribute:
index 0 will get position 0, index 1 position 1, etc. To start numbering at 1
or some other integer, provide ``count_from=1``.
Ordering values are not limited to incrementing integers. Almost any scheme
can implemented by supplying a custom ``ordering_func`` that maps a Python list
index to any value you require.
"""
from sqlalchemy.orm.collections import collection
from sqlalchemy import util
__all__ = [ 'ordering_list' ]
__all__ = ['ordering_list']
def ordering_list(attr, count_from=None, **kw):
"""Prepares an OrderingList factory for use in mapper definitions.
"""Prepares an :class:`OrderingList` factory for use in mapper definitions.
Returns an object suitable for use as an argument to a Mapper relationship's
``collection_class`` option. Arguments are:
Returns an object suitable for use as an argument to a Mapper
relationship's ``collection_class`` option. e.g.::
attr
from sqlalchemy.ext.orderinglist import ordering_list
class Slide(Base):
__tablename__ = 'slide'
id = Column(Integer, primary_key=True)
name = Column(String)
bullets = relationship("Bullet", order_by="Bullet.position",
collection_class=ordering_list('position'))
:param attr:
Name of the mapped attribute to use for storage and retrieval of
ordering information
count_from (optional)
:param count_from:
Set up an integer-based ordering, starting at ``count_from``. For
example, ``ordering_list('pos', count_from=1)`` would create a 1-based
list in SQL, storing the value in the 'pos' column. Ignored if
``ordering_func`` is supplied.
Passes along any keyword arguments to ``OrderingList`` constructor.
Additional arguments are passed to the :class:`.OrderingList` constructor.
"""
kw = _unsugar_count_from(count_from=count_from, **kw)
return lambda: OrderingList(attr, **kw)
# Ordering utility functions
def count_from_0(index, collection):
"""Numbering function: consecutive integers starting at 0."""
return index
def count_from_1(index, collection):
"""Numbering function: consecutive integers starting at 1."""
return index + 1
def count_from_n_factory(start):
"""Numbering function: consecutive integers starting at arbitrary start."""
@@ -147,8 +174,9 @@ def count_from_n_factory(start):
pass
return f
def _unsugar_count_from(**kw):
"""Builds counting functions from keywrod arguments.
"""Builds counting functions from keyword arguments.
Keyword argument filter, prepares a simple ``ordering_func`` from a
``count_from`` argument, otherwise passes ``ordering_func`` on unchanged.
@@ -164,12 +192,13 @@ def _unsugar_count_from(**kw):
kw['ordering_func'] = count_from_n_factory(count_from)
return kw
class OrderingList(list):
"""A custom list that manages position information for its children.
See the module and __init__ documentation for more details. The
``ordering_list`` factory function is used to configure ``OrderingList``
collections in ``mapper`` relationship definitions.
The :class:`.OrderingList` object is normally set up using the
:func:`.ordering_list` factory function, used in conjunction with
the :func:`.relationship` function.
"""
@@ -188,9 +217,10 @@ class OrderingList(list):
Name of the attribute that stores the object's order in the
relationship.
:param ordering_func: Optional. A function that maps the position in the Python list to a
value to store in the ``ordering_attr``. Values returned are
usually (but need not be!) integers.
:param ordering_func: Optional. A function that maps the position in
the Python list to a value to store in the
``ordering_attr``. Values returned are usually (but need not be!)
integers.
An ``ordering_func`` is called with two positional parameters: the
index of the element in the list, and the list itself.
@@ -215,7 +245,7 @@ class OrderingList(list):
making changes, any of whom happen to load this collection even in
passing, all of the sessions would try to "clean up" the numbering
in their commits, possibly causing all but one to fail with a
concurrent modification error. Spooky action at a distance.
concurrent modification error.
Recommend leaving this with the default of False, and just call
``reorder()`` if you're doing ``append()`` operations with
@@ -314,9 +344,24 @@ class OrderingList(list):
self._reorder()
# end Py2K
def __reduce__(self):
return _reconstitute, (self.__class__, self.__dict__, list(self))
for func_name, func in locals().items():
if (util.callable(func) and func.func_name == func_name and
not func.__doc__ and hasattr(list, func_name)):
func.__doc__ = getattr(list, func_name).__doc__
del func_name, func
def _reconstitute(cls, dict_, items):
""" Reconstitute an :class:`.OrderingList`.
This is the adjoint to :meth:`.OrderingList.__reduce__`. It is used for
unpickling :class:`.OrderingList` objects.
"""
obj = cls.__new__(cls)
obj.__dict__.update(dict_)
list.extend(obj, items)
return obj
+1 -1
View File
@@ -1,5 +1,5 @@
# ext/serializer.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+20 -22
View File
@@ -1,22 +1,17 @@
# ext/sqlsoup.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
"""
.. note::
SQLSoup is now its own project. Documentation
and project status are available at:
http://pypi.python.org/pypi/sqlsoup
http://readthedocs.org/docs/sqlsoup
SQLSoup will no longer be included with SQLAlchemy as of
version 0.8.
.. versionchanged:: 0.8
SQLSoup is now its own project. Documentation
and project status are available at:
http://pypi.python.org/pypi/sqlsoup and
http://readthedocs.org/docs/sqlsoup\ .
SQLSoup will no longer be included with SQLAlchemy.
Introduction
@@ -256,8 +251,8 @@ accepts in normal mapper definition:
Advanced Use
============
Sessions, Transations and Application Integration
-------------------------------------------------
Sessions, Transactions and Application Integration
---------------------------------------------------
.. note::
@@ -599,7 +594,7 @@ class SqlSoup(object):
self.session.flush()
def rollback(self):
"""Rollback the current transction.
"""Rollback the current transaction.
See :meth:`.Session.rollback`.
@@ -642,7 +637,7 @@ class SqlSoup(object):
This is the "master" method that can be used to create any
configuration.
(new in 0.6.6)
.. versionadded:: 0.6.6
:param attrname: String attribute name which will be
established as an attribute on this :class:.`.SqlSoup`
@@ -723,8 +718,9 @@ class SqlSoup(object):
def map(self, selectable, base=None, **mapper_args):
"""Map a selectable directly.
The class and its mapping are not cached and will
be discarded once dereferenced (as of 0.6.6).
.. versionchanged:: 0.6.6
The class and its mapping are not cached and will
be discarded once dereferenced.
:param selectable: an :func:`.expression.select` construct.
:param base: a Python class which will be used as the
@@ -749,8 +745,9 @@ class SqlSoup(object):
"""Map a selectable directly, wrapping the
selectable in a subquery with labels.
The class and its mapping are not cached and will
be discarded once dereferenced (as of 0.6.6).
.. versionchanged:: 0.6.6
The class and its mapping are not cached and will
be discarded once dereferenced.
:param selectable: an :func:`.expression.select` construct.
:param base: a Python class which will be used as the
@@ -773,8 +770,9 @@ class SqlSoup(object):
base=None, **mapper_args):
"""Create an :func:`.expression.join` and map to it.
The class and its mapping are not cached and will
be discarded once dereferenced (as of 0.6.6).
.. versionchanged:: 0.6.6
The class and its mapping are not cached and will
be discarded once dereferenced.
:param left: a mapped class or table object.
:param right: a mapped class or table object.
+1 -1
View File
@@ -1,5 +1,5 @@
# 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
#
# This module is part of SQLAlchemy and is released under
+60 -51
View File
@@ -1,5 +1,5 @@
# 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
# 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):
"""Provide a relationship of a primary Mapper to a secondary Mapper.
.. note::
:func:`relationship` is historically known as
:func:`relation` prior to version 0.6.
.. versionchanged:: 0.6
:func:`relationship` is historically known as :func:`relation`.
This corresponds to a parent-child or associative table relationship. The
constructed class is an instance of :class:`.RelationshipProperty`.
@@ -299,11 +297,13 @@ def relationship(argument, secondary=None, **kwargs):
* ``delete-orphan`` - if an item of the child's type is
detached from its parent, mark it for deletion.
As of version 0.7, this option does not prevent
a new instance of the child object from being persisted
without a parent to start with; to constrain against
that case, ensure the child's foreign key column(s)
is configured as NOT NULL
.. versionchanged:: 0.7
This option does not prevent
a new instance of the child object from being persisted
without a parent to start with; to constrain against
that case, ensure the child's foreign key column(s)
is configured as NOT NULL
* ``refresh-expire`` - cascade the :meth:`.Session.expire`
and :meth:`~sqlalchemy.orm.session.Session.refresh` operations
@@ -390,12 +390,14 @@ def relationship(argument, secondary=None, **kwargs):
:param innerjoin=False:
when ``True``, joined eager loads will use an inner join to join
against related tables instead of an outer join. The purpose
of this option is strictly one of performance, as inner joins
generally perform better than outer joins. This flag can
be set to ``True`` when the relationship references an 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.
of this option is generally one of performance, as inner joins
generally perform better than outer joins. Another reason can be
the use of ``with_lockmode``, which does not support outer joins.
This flag can be set to ``True`` when the relationship references an
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:
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,
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
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'
* False - a synonyn for 'joined'
* False - a synonym for 'joined'
* None - a synonym for 'noload'
@@ -668,7 +672,9 @@ def column_property(*cols, **kw):
flag is available for applications that make use of
:func:`.attributes.get_history` or :meth:`.Session.is_modified`
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
: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
settings still expires
all attributes after a :meth:`.Session.commit` call, however.
New in 0.7.3.
.. versionadded:: 0.7.3
:param extension:
an
@@ -719,7 +725,7 @@ def column_property(*cols, **kw):
def composite(class_, *cols, **kwargs):
"""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.
:param class\_:
@@ -732,8 +738,10 @@ def composite(class_, *cols, **kwargs):
When ``True``, indicates that the "previous" value for a
scalar attribute should be loaded when replaced, if not
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:
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"
}
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
use the :func:`.case` construct to provide a conditional
approach::
@@ -992,7 +1000,7 @@ def mapper(class_, local_table=None, *args, **params):
"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
string name of one::
@@ -1013,6 +1021,11 @@ def mapper(class_, local_table=None, *args, **params):
"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
attribute or expression that's not present in the
locally mapped :class:`.Table`, yet the value
@@ -1119,11 +1132,10 @@ def synonym(name, map_column=False, descriptor=None,
comparator_factory=None, doc=None):
"""Denote an attribute name as a synonym to a mapped property.
.. note::
:func:`.synonym` is superseded as of 0.7 by
the :mod:`~sqlalchemy.ext.hybrid` extension. See
the documentation for hybrids at :ref:`hybrids_toplevel`.
.. versionchanged:: 0.7
:func:`.synonym` is superseded by the :mod:`~sqlalchemy.ext.hybrid`
extension. See the documentation for hybrids
at :ref:`hybrids_toplevel`.
Used with the ``properties`` dictionary sent to
:func:`~sqlalchemy.orm.mapper`::
@@ -1163,11 +1175,10 @@ def comparable_property(comparator_factory, descriptor=None):
"""Provides a method of applying a :class:`.PropComparator`
to any Python descriptor attribute.
.. note::
:func:`.comparable_property` is superseded as of 0.7 by
the :mod:`~sqlalchemy.ext.hybrid` extension. See the example
at :ref:`hybrid_custom_comparators`.
.. versionchanged:: 0.7
:func:`.comparable_property` is superseded by
the :mod:`~sqlalchemy.ext.hybrid` extension. See the example
at :ref:`hybrid_custom_comparators`.
Allows any Python descriptor to behave like a SQL-enabled
attribute when used at the class level in queries, allowing
@@ -1218,7 +1229,7 @@ def comparable_property(comparator_factory, descriptor=None):
Optional when used in a ``properties={}`` declaration. The Python
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.
"""
@@ -1267,18 +1278,17 @@ def joinedload(*keys, **kw):
"""Return a ``MapperOption`` that will convert the property of the given
name or series of mapped attributes into an joined eager load.
.. note::
This function is known as :func:`eagerload` in all versions
of SQLAlchemy prior to version 0.6beta3, including the 0.5 and 0.4
series. :func:`eagerload` will remain available for the foreseeable
future in order to enable cross-compatibility.
.. versionchanged:: 0.6beta3
This function is known as :func:`eagerload` in all versions
of SQLAlchemy prior to version 0.6beta3, including the 0.5 and 0.4
series. :func:`eagerload` will remain available for the foreseeable
future in order to enable cross-compatibility.
Used with :meth:`~sqlalchemy.orm.query.Query.options`.
examples::
# joined-load the "orders" colleciton on "User"
# joined-load the "orders" collection on "User"
query(User).options(joinedload(User.orders))
# joined-load the "keywords" collection on each "Item",
@@ -1327,12 +1337,11 @@ def joinedload_all(*keys, **kw):
given dot-separated path or series of mapped attributes
into an joined eager load.
.. note::
This function is known as :func:`eagerload_all` in all versions
of SQLAlchemy prior to version 0.6beta3, including the 0.5 and 0.4
series. :func:`eagerload_all` will remain available for the
foreseeable future in order to enable cross-compatibility.
.. versionchanged:: 0.6beta3
This function is known as :func:`eagerload_all` in all versions
of SQLAlchemy prior to version 0.6beta3, including the 0.5 and 0.4
series. :func:`eagerload_all` will remain available for the
foreseeable future in order to enable cross-compatibility.
Used with :meth:`~sqlalchemy.orm.query.Query.options`.
@@ -1381,7 +1390,7 @@ def subqueryload(*keys):
examples::
# subquery-load the "orders" colleciton on "User"
# subquery-load the "orders" collection on "User"
query(User).options(subqueryload(User.orders))
# subquery-load the "keywords" collection on each "Item",
@@ -1478,7 +1487,7 @@ def immediateload(*keys):
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')
+43 -23
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -413,7 +413,7 @@ class AttributeImpl(object):
Only applies to object-based attributes.
This is an inlining of existing functionality
which roughly correponds to:
which roughly corresponds to:
get_state_history(
state,
@@ -653,7 +653,7 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl):
if current is not None:
ret = [(instance_state(current), current)]
else:
ret = []
ret = [(None, None)]
if self.key in state.committed_state:
original = state.committed_state[self.key]
@@ -686,7 +686,7 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl):
old is not PASSIVE_NO_RESULT and \
check_old is not old:
if pop:
return
return
else:
raise ValueError(
"Object %s not associated with %s on attribute '%s'" % (
@@ -973,6 +973,14 @@ def backref_listeners(attribute, key, uselist):
# 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):
if oldchild is child:
return child
@@ -991,35 +999,47 @@ def backref_listeners(attribute, key, uselist):
if child is not None:
child_state, child_dict = instance_state(child),\
instance_dict(child)
child_state.manager[key].impl.append(
child_state,
child_dict,
state.obj(),
initiator,
passive=PASSIVE_NO_FETCH)
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_dict,
state.obj(),
initiator,
passive=PASSIVE_NO_FETCH)
return child
def emit_backref_from_collection_append_event(state, child, initiator):
child_state, child_dict = instance_state(child), \
instance_dict(child)
child_state.manager[key].impl.append(
child_state,
child_dict,
state.obj(),
initiator,
passive=PASSIVE_NO_FETCH)
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_dict,
state.obj(),
initiator,
passive=PASSIVE_NO_FETCH)
return child
def emit_backref_from_collection_remove_event(state, child, initiator):
if child is not None:
child_state, child_dict = instance_state(child),\
instance_dict(child)
child_state.manager[key].impl.pop(
child_state,
child_dict,
state.obj(),
initiator,
passive=PASSIVE_NO_FETCH)
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_dict,
state.obj(),
initiator,
passive=PASSIVE_NO_FETCH)
if uselist:
event.listen(attribute, "append",
@@ -1126,7 +1146,7 @@ class History(tuple):
return cls((), (), ())
else:
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:
return cls((), [current], ())
else:
+86 -9
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -111,22 +111,57 @@ import weakref
from sqlalchemy.sql import expression
from sqlalchemy import schema, util, exc as sa_exc
__all__ = ['collection', 'collection_adapter',
'mapped_collection', 'column_mapped_collection',
'attribute_mapped_collection']
__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):
"""Column-based getter used in version 0.7.6 only.
Remains here for pickle compatibility with 0.7.6.
"""
def __init__(self, colkeys):
self.colkeys = colkeys
self.composite = len(colkeys) > 1
def __reduce__(self):
return _SerializableColumnGetter, (self.colkeys,)
def __call__(self, value):
state = instance_state(value)
m = _state_mapper(state)
@@ -139,6 +174,48 @@ class _SerializableColumnGetter(object):
else:
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):
"""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.attributes import instance_state
cols = [c.key for c in [
expression._only_column_elements(q, "mapping_spec")
for q in util.to_list(mapping_spec)]]
keyfunc = _SerializableColumnGetter(cols)
cols = [expression._only_column_elements(q, "mapping_spec")
for q in util.to_list(mapping_spec)
]
keyfunc = _PlainColumnGetter(cols)
return lambda: MappedCollection(keyfunc)
class _SerializableAttrGetter(object):
+1 -1
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+6 -5
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -247,10 +247,11 @@ class MapperExtension(object):
instance population will not proceed, giving this extension an
opportunity to populate the instance itself, if desired.
As of 0.5, most usages of this hook are obsolete. For a
generic "object has been newly created from a row" hook, use
``reconstruct_instance()``, or the ``@orm.reconstructor``
decorator.
.. deprecated:: 0.5
Most usages of this hook are obsolete. For a
generic "object has been newly created from a row" hook, use
``reconstruct_instance()``, or the ``@orm.reconstructor``
decorator.
"""
return EXT_CONTINUE
+1 -1
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+7 -4
View File
@@ -1,5 +1,5 @@
# 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
# 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 exc as sa_exc
from sqlalchemy.orm import exc as orm_exc
from sqlalchemy.sql import operators
from sqlalchemy.orm import (
@@ -20,12 +19,16 @@ from sqlalchemy.orm import (
)
from sqlalchemy.orm.query import Query
from sqlalchemy.orm.util import has_identity
from sqlalchemy.orm import attributes, collections
from sqlalchemy.orm import collections
class DynaLoader(strategies.AbstractRelationshipLoader):
def init_class_attribute(self, mapper):
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,
mapper,
useobject=True,
+1 -1
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+12 -4
View File
@@ -1,5 +1,5 @@
# 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
# 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_):
"""Called when the mapper for the class is fully configured.
This event is the latest phase of mapper construction.
The mapper should be in its final state.
This event is the latest phase of mapper construction, and
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
of this event.
@@ -1007,7 +1015,7 @@ class SessionEvents(event.Events):
for the given :class:`.Session` is available via the
:attr:`.Session.transaction` attribute.
New in 0.7.3.
.. versionadded:: 0.7.3
"""
+4 -2
View File
@@ -1,5 +1,5 @@
# 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
# 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
identity which was garbage collected, and a decision
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
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+1 -1
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+1 -1
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+19 -4
View File
@@ -1,5 +1,5 @@
# 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
# 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.passive_updates = self.inherits.passive_updates
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):
self.polymorphic_on = polymorphic_on
@@ -678,9 +684,10 @@ class Mapper(object):
self._reconstructor = method
event.listen(manager, 'load', _event_on_load, raw=True)
elif hasattr(method, '__sa_validators__'):
include_removes = getattr(method, "__sa_include_removes__", False)
for name in method.__sa_validators__:
self.validators = self.validators.union(
{name : method}
{name : (method, include_removes)}
)
manager.info[_INSTRUMENTOR] = self
@@ -940,7 +947,6 @@ class Mapper(object):
# with_polymorphic selectable, as when use polymorphic_union.
# we'll make a separate ColumnProperty for it.
instrument = True
key = getattr(col, 'key', None)
if key:
if self._should_exclude(col.key, col.key, False, col):
@@ -2291,7 +2297,7 @@ def reconstructor(fn):
fn.__sa_reconstructor__ = True
return fn
def validates(*names):
def validates(*names, **kw):
"""Decorate a method as a 'validator' for one or more named properties.
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
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):
fn.__sa_validators__ = names
fn.__sa_include_removes__ = include_removes
return fn
return wrap
+6 -4
View File
@@ -1,5 +1,5 @@
# 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
# 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:
clause.clauses.append(mapper.version_id_col ==\
sql.bindparam(mapper.version_id_col._label,
type_=col.type))
type_=mapper.version_id_col.type))
return table.update(clause)
@@ -748,13 +748,15 @@ def _connections_for_states(base_mapper, uowtransaction, states):
connection_callable = \
uowtransaction.session.connection_callable
else:
connection = uowtransaction.transaction.connection(
base_mapper)
connection = None
connection_callable = None
for state in _sort_states(states):
if connection_callable:
connection = connection_callable(base_mapper, state.obj())
elif not connection:
connection = uowtransaction.transaction.connection(
base_mapper)
mapper = _state_mapper(state)
+6 -11
View File
@@ -1,5 +1,5 @@
# 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
# 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,
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]
if not load:
@@ -144,9 +146,8 @@ class ColumnProperty(StrategizedProperty):
else:
impl = dest_state.get_impl(self.key)
impl.set(dest_state, dest_dict, value, None)
else:
if dest_state.has_identity and self.key not in dest_dict:
dest_state.expire_attributes(dest_dict, [self.key])
elif dest_state.has_identity and self.key not in dest_dict:
dest_state.expire_attributes(dest_dict, [self.key])
class Comparator(PropComparator):
@util.memoized_instancemethod
@@ -322,12 +323,6 @@ class RelationshipProperty(StrategizedProperty):
else:
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):
"""Produce a construct that represents a particular 'subtype' of
attribute for the parent class.
+90 -35
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -77,6 +77,7 @@ class Query(object):
_group_by = False
_having = None
_distinct = False
_prefixes = None
_offset = None
_limit = None
_statement = None
@@ -454,7 +455,7 @@ class Query(object):
"""Return the full SELECT statement represented by this :class:`.Query`
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
:meth:`._SelectBase.cte` method; see that method for
@@ -512,7 +513,7 @@ class Query(object):
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`.
New in 0.6.5.
.. versionadded:: 0.6.5
"""
@@ -917,7 +918,8 @@ class Query(object):
'_order_by', '_group_by',
'_limit', '_offset',
'_joinpath', '_joinpoint',
'_distinct', '_having'
'_distinct', '_having',
'_prefixes',
):
self.__dict__.pop(attr, None)
self._set_select_from(fromclause)
@@ -979,7 +981,7 @@ class Query(object):
q = q.join((subq, subq.c.email < Address.email)).\\
limit(1)
New in 0.6.5.
.. versionadded:: 0.6.5
"""
self._set_entities(entities)
@@ -1056,7 +1058,7 @@ class Query(object):
This allows ad-hoc recipes to be created for :class:`.Query`
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)
@@ -1103,10 +1105,18 @@ class Query(object):
``FOR UPDATE`` (standard SQL, supported by most dialects)
``'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
``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
@@ -1140,7 +1150,7 @@ class Query(object):
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).\\
filter(MyClass.name == 'some name', MyClass.id > 5)
@@ -1149,6 +1159,9 @@ class Query(object):
WHERE clause of a select. String expressions are coerced
into SQL expression constructs via the :func:`.text` construct.
.. versionchanged:: 0.7.5
Multiple criteria joined by AND.
See also:
:meth:`.Query.filter_by` - filter on keyword expressions.
@@ -1233,7 +1246,7 @@ class Query(object):
@_generative(_no_statement_condition, _no_limit_offset)
def group_by(self, *criterion):
"""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]))
@@ -1247,7 +1260,19 @@ class Query(object):
@_generative(_no_statement_condition, _no_limit_offset)
def having(self, criterion):
"""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):
criterion = sql.text(criterion)
@@ -1460,18 +1485,15 @@ class Query(object):
q = session.query(User).join(Address, User.id==Address.user_id)
.. note::
In SQLAlchemy 0.6 and earlier, the two argument form of
:meth:`~.Query.join` requires the usage of a tuple::
query(User).join((Address, User.id==Address.user_id))
This calling form is accepted in 0.7 and further, though
is not necessary unless multiple join conditions are passed to
a single :meth:`~.Query.join` call, which itself is also not
generally necessary as it is now equivalent to multiple
calls (this wasn't always the case).
.. versionchanged:: 0.7
In SQLAlchemy 0.6 and earlier, the two argument form of
:meth:`~.Query.join` requires the usage of a tuple:
``query(User).join((Address, User.id==Address.user_id))``\ .
This calling form is accepted in 0.7 and further, though
is not necessary unless multiple join conditions are passed to
a single :meth:`~.Query.join` call, which itself is also not
generally necessary as it is now equivalent to multiple
calls (this wasn't always the case).
**Advanced Join Targeting and Adaption**
@@ -1481,7 +1503,7 @@ class Query(object):
and :func:`.select` constructs, with either the one or two-argument forms::
addresses_q = select([Address.user_id]).\\
filter(Address.email_address.endswith("@bar.com")).\\
where(Address.email_address.endswith("@bar.com")).\\
alias()
q = session.query(User).\\
@@ -2057,6 +2079,33 @@ class Query(object):
else:
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):
"""Return the results represented by this ``Query`` as a list.
@@ -2125,9 +2174,11 @@ class Query(object):
unique entity or entities - this is a successful result for one().
Calling ``one()`` results in an execution of the underlying query.
As of 0.6, ``one()`` fully fetches all results instead of applying
any kind of limit, so that the "unique"-ing of entities does not
conceal multiple object identities.
.. versionchanged:: 0.6
``one()`` fully fetches all results instead of applying
any kind of limit, so that the "unique"-ing of entities does not
conceal multiple object identities.
"""
ret = list(self)
@@ -2364,14 +2415,16 @@ class Query(object):
mapped_entities = [i for i, e in enumerate(self._entities)
if isinstance(e, _MapperEntity)]
result = []
keys = [ent._label_name for ent in self._entities]
for row in iterator:
newrow = list(row)
for i in mapped_entities:
newrow[i] = session._merge(
attributes.instance_state(newrow[i]),
attributes.instance_dict(newrow[i]),
load=load, _recursive={})
result.append(util.NamedTuple(newrow, row._labels))
if newrow[i] is not None:
newrow[i] = session._merge(
attributes.instance_state(newrow[i]),
attributes.instance_dict(newrow[i]),
load=load, _recursive={})
result.append(util.NamedTuple(newrow, keys))
return iter(result)
finally:
@@ -2468,6 +2521,7 @@ class Query(object):
'limit':self._limit,
'offset':self._offset,
'distinct':self._distinct,
'prefixes':self._prefixes,
'group_by':self._group_by or None,
'having':self._having
}
@@ -2488,8 +2542,8 @@ class Query(object):
SELECT <rest of query follows...>
) AS anon_1
Note the above scheme is newly refined in 0.7
(as of 0.7b3).
.. versionchanged:: 0.7
The above scheme is newly refined as of 0.7b3.
For fine grained control over specific columns
to count, to skip the usage of a subquery or
@@ -2807,6 +2861,7 @@ class Query(object):
if self._lockmode:
try:
for_update = {'read': 'read',
'read_nowait': 'read_nowait',
'update': True,
'update_nowait': 'nowait',
None: False}[self._lockmode]
+1 -1
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+272 -87
View File
@@ -1,5 +1,5 @@
# 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
# 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):
"""A Session-level transaction.
"""A :class:`.Session`-level transaction.
This corresponds to one or more Core :class:`~.engine.base.Transaction`
instances behind the scenes, with one :class:`~.engine.base.Transaction`
per :class:`~.engine.base.Engine` in use.
:class:`.SessionTransaction` is a mostly behind-the-scenes object
not normally referenced directly by application code. It coordinates
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
necessary as of SQLAlchemy 0.4; use the :meth:`.Session.rollback` and
:meth:`.Session.commit` methods on :class:`.Session` itself to
control the transaction.
The :attr:`.Session.transaction` attribute of :class:`.Session` refers to the
current :class:`.SessionTransaction` object in use, if any.
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:
@@ -126,6 +165,10 @@ class SessionTransaction(object):
:meth:`.Session.commit`
:meth:`.Session.begin`
:meth:`.Session.begin_nested`
:attr:`.Session.is_active`
:meth:`.SessionEvents.after_commit`
@@ -134,9 +177,6 @@ class SessionTransaction(object):
:meth:`.SessionEvents.after_soft_rollback`
.. index::
single: thread safety; SessionTransaction
"""
_rollback_exception = None
@@ -211,6 +251,7 @@ class SessionTransaction(object):
if not self._is_transaction_boundary:
self._new = self._parent._new
self._deleted = self._parent._deleted
self._key_switches = self._parent._key_switches
return
if not self.session._flushing:
@@ -218,6 +259,7 @@ class SessionTransaction(object):
self._new = weakref.WeakKeyDictionary()
self._deleted = weakref.WeakKeyDictionary()
self._key_switches = weakref.WeakKeyDictionary()
def _restore_snapshot(self):
assert self._is_transaction_boundary
@@ -227,11 +269,16 @@ class SessionTransaction(object):
if 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):
if s.deleted:
#assert s in self._deleted
del s.deleted
self.session._update_impl(s)
self.session._update_impl(s, discard_existing=True)
assert not self.session._deleted
@@ -294,7 +341,15 @@ class SessionTransaction(object):
subtransaction.commit()
if not self.session._flushing:
self.session.flush()
for _flush_guard in xrange(100):
if self.session._is_clean():
break
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:
try:
@@ -735,49 +790,99 @@ class Session(object):
return engine.contextual_connect(**kwargs)
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
results of the statement execution, in the same manner as that of an
:class:`.Engine` or
: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
as :func:`~.sql.expression.select`,
:func:`~.sql.expression.insert`,
:func:`~.sql.expression.update`,
:func:`~.sql.expression.delete`, and
:func:`~.sql.expression.text`, and additionally accepts
plain strings that represent SQL statements. If a plain string is
passed, it is first converted to a
:func:`~.sql.expression.text` construct, which here means
that bind parameters should be specified using the format ``:param``.
If raw DBAPI statement execution is desired, use :meth:`.Session.connection`
to acquire a :class:`.Connection`, then call its :meth:`~.Connection.execute`
method.
:func:`~.sql.expression.text`. Plain SQL strings can be passed
as well, which in the case of :meth:`.Session.execute` only
will be interpreted the same as if it were passed via a :func:`~.expression.text`
construct. That is, the following usage::
result = session.execute(
"SELECT * FROM user WHERE id=:param",
{"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
this :class:`.Session`, using the same behavior as that of
the :meth:`.Session.connection` method to determine the active
:class:`.Connection`. The ``close_with_result`` flag is
set to ``True`` so that an ``autocommit=True`` :class:`.Session`
with no active transaction will produce a result that auto-closes
the underlying :class:`.Connection`.
this :class:`.Session`. The :class:`.Connection` which is used
to execute the statement can also be acquired directly by
calling the :meth:`.Session.connection` method. Both methods use
a rule-based resolution scheme in order to determine the
:class:`.Connection`, which in the average case is derived directly
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:
A :class:`.ClauseElement` (i.e. :func:`~.sql.expression.select`,
:func:`~.sql.expression.text`, etc.) or string SQL statement to be executed. The clause
will also be used to locate a bind, if this :class:`.Session`
is not bound to a single engine already, and the ``mapper``
and ``bind`` arguments are not passed.
An executable statement (i.e. an :class:`.Executable` expression
such as :func:`.expression.select`) or string SQL statement
to be executed.
: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:
Optional :func:`.mapper` or mapped class, used to identify
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:
Optional :class:`.Engine` to be used as the bind. If
@@ -787,9 +892,20 @@ class Session(object):
a bind.
:param \**kw:
Additional keyword arguments are sent to :meth:`get_bind()`,
allowing additional arguments to be passed to custom
implementations of :meth:`get_bind`.
Additional keyword arguments are sent to :meth:`.Session.get_bind()`
to allow extensibility of "bind" schemes.
.. 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)
@@ -998,7 +1114,7 @@ class Session(object):
of objects which involve existing database queries,
where the uncompleted object should not yet be flushed.
New in 0.7.6.
.. versionadded:: 0.7.6
"""
autoflush = self.autoflush
@@ -1214,6 +1330,11 @@ class Session(object):
# state has already replaced this one in the identity
# map (see test/orm/test_naturalpks.py ReversePKsTest)
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
self.identity_map.replace(state)
@@ -1303,20 +1424,47 @@ class Session(object):
self._delete_impl(st_)
def merge(self, instance, load=True, **kw):
"""Copy the state an instance onto the persistent instance with the
same identifier.
"""Copy the state of a given instance into a corresponding instance
within this :class:`.Session`.
If there is no persistent instance currently associated with the
session, it will be loaded. Return the persistent instance. If the
given instance is unsaved, save a copy of and return it as a newly
persistent instance. The given instance does not become associated
with the session.
:meth:`.Session.merge` examines the primary key attributes of the
source instance, and attempts to reconcile it with an instance of the
same primary key in the session. If not found locally, it attempts
to load the object from the database based on primary key, and if
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
mapped with ``cascade="merge"``.
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:
load = not kw['dont_load']
@@ -1465,7 +1613,7 @@ class Session(object):
self._new[state] = state.obj()
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
state not in self._deleted):
return
@@ -1481,6 +1629,10 @@ class Session(object):
"function to send this object back to the transient 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._deleted.pop(state, None)
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
operations int the flush.
objects
Optional; a list or tuple collection. Restricts the flush operation
to only these objects, rather than all pending changes.
Deprecated - this flag prevents the session from properly maintaining
accounting among inter-object relations and can cause invalid results.
:param objects: Optional; restricts the flush operation to operate
only on elements that are in the given collection.
This feature is for an extremely narrow set of use cases where
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:
raise sa_exc.InvalidRequestError("Session is already flushing")
@@ -1695,17 +1842,16 @@ class Session(object):
return session.is_modified(someobject, passive=True)
.. note::
In SQLAlchemy 0.7 and earlier, the ``passive``
flag should **always** be explicitly set to ``True``.
The current default value of :data:`.attributes.PASSIVE_OFF`
for this flag is incorrect, in that it loads unloaded
collections and attributes which by definition
have no modified state, and furthermore trips off
autoflush which then causes all subsequent, possibly
modified attributes to lose their modified state.
The default value of the flag will be changed in 0.8.
.. versionchanged:: 0.8
In SQLAlchemy 0.7 and earlier, the ``passive``
flag should **always** be explicitly set to ``True``.
The current default value of :data:`.attributes.PASSIVE_OFF`
for this flag is incorrect, in that it loads unloaded
collections and attributes which by definition
have no modified state, and furthermore trips off
autoflush which then causes all subsequent, possibly
modified attributes to lose their modified state.
The default value of the flag will be changed in 0.8.
A few caveats to this method apply:
@@ -1750,10 +1896,11 @@ class Session(object):
is a bug, as unloaded attributes by definition have
no changes, and the load operation also triggers an
autoflush which then cancels out subsequent changes.
This flag should **always be set to
True**. In 0.8 the flag will be deprecated and the default
set to ``True``.
This flag should **always be set to True**.
.. versionchanged:: 0.8
The flag will be deprecated and the default
set to ``True``.
"""
try:
@@ -1784,20 +1931,58 @@ class Session(object):
@property
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
SQL, as from the :meth:`.Session.execute`, :meth:`.Session.query`,
or :meth:`.Session.flush` methods. If False, it indicates
that the innermost transaction has been rolled back, but enclosing
:class:`.SessionTransaction` objects remain in the transactional
stack, which also must be rolled back.
The :class:`.Session` in its default mode of ``autocommit=False``
is essentially always in "transaction mode", in that a
:class:`.SessionTransaction` is associated with it as soon as
it is instantiated. This :class:`.SessionTransaction` is immediately
replaced with a new one as soon as it is ended, due to a rollback,
commit, or close operation.
This flag is generally only useful with a :class:`.Session`
configured in its default mode of ``autocommit=False``.
"Transaction mode" does *not* indicate whether
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
identity_map = None
+1 -1
View File
@@ -1,5 +1,5 @@
# 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
# the MIT License: http://www.opensource.org/licenses/mit-license.php
+1 -1
View File
@@ -1,5 +1,5 @@
# 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
# 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