Update SQLAlchemy
This commit is contained in:
1 parent
267ecfacab
commit
4aa6700ceb
121 files changed
+2552
-1259
No files matched your search
@@ -1,5 +1,5 @@
|
|||||||
# sqlalchemy/__init__.py
|
# sqlalchemy/__init__.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -50,6 +50,8 @@ from sqlalchemy.sql import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from sqlalchemy.types import (
|
from sqlalchemy.types import (
|
||||||
|
BIGINT,
|
||||||
|
BINARY,
|
||||||
BLOB,
|
BLOB,
|
||||||
BOOLEAN,
|
BOOLEAN,
|
||||||
BigInteger,
|
BigInteger,
|
||||||
@@ -87,6 +89,7 @@ from sqlalchemy.types import (
|
|||||||
TypeDecorator,
|
TypeDecorator,
|
||||||
Unicode,
|
Unicode,
|
||||||
UnicodeText,
|
UnicodeText,
|
||||||
|
VARBINARY,
|
||||||
VARCHAR,
|
VARCHAR,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -117,7 +120,7 @@ from sqlalchemy.engine import create_engine, engine_from_config
|
|||||||
__all__ = sorted(name for name, obj in locals().items()
|
__all__ = sorted(name for name, obj in locals().items()
|
||||||
if not (name.startswith('_') or inspect.ismodule(obj)))
|
if not (name.startswith('_') or inspect.ismodule(obj)))
|
||||||
|
|
||||||
__version__ = '0.7.6'
|
__version__ = '0.7.10'
|
||||||
|
|
||||||
del inspect, sys
|
del inspect, sys
|
||||||
|
|
||||||
|
|||||||
@@ -241,12 +241,13 @@ static PyObject *
|
|||||||
BaseRowProxy_subscript(BaseRowProxy *self, PyObject *key)
|
BaseRowProxy_subscript(BaseRowProxy *self, PyObject *key)
|
||||||
{
|
{
|
||||||
PyObject *processors, *values;
|
PyObject *processors, *values;
|
||||||
PyObject *processor, *value;
|
PyObject *processor, *value, *processed_value;
|
||||||
PyObject *row, *record, *result, *indexobject;
|
PyObject *row, *record, *result, *indexobject;
|
||||||
PyObject *exc_module, *exception;
|
PyObject *exc_module, *exception, *cstr_obj;
|
||||||
char *cstr_key;
|
char *cstr_key;
|
||||||
long index;
|
long index;
|
||||||
int key_fallback = 0;
|
int key_fallback = 0;
|
||||||
|
int tuple_check = 0;
|
||||||
|
|
||||||
if (PyInt_CheckExact(key)) {
|
if (PyInt_CheckExact(key)) {
|
||||||
index = PyInt_AS_LONG(key);
|
index = PyInt_AS_LONG(key);
|
||||||
@@ -299,9 +300,16 @@ BaseRowProxy_subscript(BaseRowProxy *self, PyObject *key)
|
|||||||
if (exception == NULL)
|
if (exception == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
cstr_key = PyString_AsString(key);
|
// wow. this seems quite excessive.
|
||||||
if (cstr_key == NULL)
|
cstr_obj = PyObject_Str(key);
|
||||||
|
if (cstr_obj == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
cstr_key = PyString_AsString(cstr_obj);
|
||||||
|
if (cstr_key == NULL) {
|
||||||
|
Py_DECREF(cstr_obj);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
Py_DECREF(cstr_obj);
|
||||||
|
|
||||||
PyErr_Format(exception,
|
PyErr_Format(exception,
|
||||||
"Ambiguous column name '%.200s' in result set! "
|
"Ambiguous column name '%.200s' in result set! "
|
||||||
@@ -319,17 +327,28 @@ BaseRowProxy_subscript(BaseRowProxy *self, PyObject *key)
|
|||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
row = self->row;
|
row = self->row;
|
||||||
if (PyTuple_CheckExact(row))
|
if (PyTuple_CheckExact(row)) {
|
||||||
value = PyTuple_GetItem(row, index);
|
value = PyTuple_GetItem(row, index);
|
||||||
else
|
tuple_check = 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
value = PySequence_GetItem(row, index);
|
value = PySequence_GetItem(row, index);
|
||||||
|
tuple_check = 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (value == NULL)
|
if (value == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
if (processor != Py_None) {
|
if (processor != Py_None) {
|
||||||
return PyObject_CallFunctionObjArgs(processor, value, NULL);
|
processed_value = PyObject_CallFunctionObjArgs(processor, value, NULL);
|
||||||
|
if (!tuple_check) {
|
||||||
|
Py_DECREF(value);
|
||||||
|
}
|
||||||
|
return processed_value;
|
||||||
} else {
|
} else {
|
||||||
Py_INCREF(value);
|
if (tuple_check) {
|
||||||
|
Py_INCREF(value);
|
||||||
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# connectors/__init__.py
|
# connectors/__init__.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# connectors/mxodbc.py
|
# connectors/mxodbc.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ class MySQLDBConnector(Connector):
|
|||||||
|
|
||||||
util.coerce_kw_type(opts, 'compress', bool)
|
util.coerce_kw_type(opts, 'compress', bool)
|
||||||
util.coerce_kw_type(opts, 'connect_timeout', int)
|
util.coerce_kw_type(opts, 'connect_timeout', int)
|
||||||
|
util.coerce_kw_type(opts, 'read_timeout', int)
|
||||||
util.coerce_kw_type(opts, 'client_flag', int)
|
util.coerce_kw_type(opts, 'client_flag', int)
|
||||||
util.coerce_kw_type(opts, 'local_infile', int)
|
util.coerce_kw_type(opts, 'local_infile', int)
|
||||||
# Note: using either of the below will cause all strings to be returned
|
# Note: using either of the below will cause all strings to be returned
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# connectors/pyodbc.py
|
# connectors/pyodbc.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -37,6 +37,10 @@ class PyODBCConnector(Connector):
|
|||||||
# if the libessqlsrv.so is detected
|
# if the libessqlsrv.so is detected
|
||||||
easysoft = False
|
easysoft = False
|
||||||
|
|
||||||
|
def __init__(self, supports_unicode_binds=None, **kw):
|
||||||
|
super(PyODBCConnector, self).__init__(**kw)
|
||||||
|
self._user_supports_unicode_binds = supports_unicode_binds
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def dbapi(cls):
|
def dbapi(cls):
|
||||||
return __import__('pyodbc')
|
return __import__('pyodbc')
|
||||||
@@ -119,8 +123,12 @@ class PyODBCConnector(Connector):
|
|||||||
# have not tried pyodbc + python3.1 yet.
|
# have not tried pyodbc + python3.1 yet.
|
||||||
# Py2K
|
# Py2K
|
||||||
self.supports_unicode_statements = not self.freetds and not self.easysoft
|
self.supports_unicode_statements = not self.freetds and not self.easysoft
|
||||||
self.supports_unicode_binds = (not self.freetds or
|
if self._user_supports_unicode_binds is not None:
|
||||||
self.freetds_driver_version >= '0.91') and not self.easysoft
|
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
|
# end Py2K
|
||||||
|
|
||||||
# run other initialization which asks for user name, etc.
|
# run other initialization which asks for user name, etc.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# connectors/zxJDBC.py
|
# connectors/zxJDBC.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# databases/__init__.py
|
# databases/__init__.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# dialects/__init__.py
|
# dialects/__init__.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -9,9 +9,10 @@
|
|||||||
"""
|
"""
|
||||||
Support for the Microsoft Access database.
|
Support for the Microsoft Access database.
|
||||||
|
|
||||||
This dialect is *not* ported to SQLAlchemy 0.6 or 0.7.
|
.. note::
|
||||||
|
|
||||||
This dialect is *not* tested on SQLAlchemy 0.6 or 0.7.
|
The Access dialect is **non-functional as of SQLAlchemy 0.6**,
|
||||||
|
pending development efforts to bring it up-to-date.
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,18 +1,22 @@
|
|||||||
from sqlalchemy.dialects.drizzle import base, mysqldb
|
from sqlalchemy.dialects.drizzle import base, mysqldb
|
||||||
|
|
||||||
# default dialect
|
|
||||||
base.dialect = mysqldb.dialect
|
base.dialect = mysqldb.dialect
|
||||||
|
|
||||||
from sqlalchemy.dialects.drizzle.base import \
|
from sqlalchemy.dialects.drizzle.base import \
|
||||||
BIGINT, BINARY, BLOB, BOOLEAN, CHAR, DATE, DATETIME, \
|
BIGINT, BINARY, BLOB, \
|
||||||
DECIMAL, DOUBLE, ENUM, \
|
BOOLEAN, CHAR, DATE, \
|
||||||
FLOAT, INTEGER, \
|
DATETIME, DECIMAL, DOUBLE, \
|
||||||
NUMERIC, REAL, TEXT, TIME, TIMESTAMP, \
|
ENUM, FLOAT, INTEGER, \
|
||||||
VARBINARY, VARCHAR, dialect
|
NUMERIC, REAL, TEXT, \
|
||||||
|
TIME, TIMESTAMP, VARBINARY, \
|
||||||
|
VARCHAR, dialect
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'BIGINT', 'BINARY', 'BLOB', 'BOOLEAN', 'CHAR', 'DATE', 'DATETIME', 'DECIMAL', 'DOUBLE',
|
'BIGINT', 'BINARY', 'BLOB',
|
||||||
'ENUM', 'FLOAT', 'INTEGER',
|
'BOOLEAN', 'CHAR', 'DATE',
|
||||||
'NUMERIC', 'SET', 'REAL', 'TEXT', 'TIME', 'TIMESTAMP',
|
'DATETIME', 'DECIMAL', 'DOUBLE',
|
||||||
'VARBINARY', 'VARCHAR', 'dialect'
|
'ENUM', 'FLOAT', 'INTEGER',
|
||||||
|
'NUMERIC', 'REAL', 'TEXT',
|
||||||
|
'TIME', 'TIMESTAMP', 'VARBINARY',
|
||||||
|
'VARCHAR', 'dialect'
|
||||||
)
|
)
|
||||||
@@ -1,138 +1,36 @@
|
|||||||
# drizzle/base.py
|
# drizzle/base.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
# Copyright (C) 2010-2011 Monty Taylor <mordred@inaugust.com>
|
# Copyright (C) 2010-2011 Monty Taylor <mordred@inaugust.com>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|
||||||
|
|
||||||
"""Support for the Drizzle database.
|
"""Support for the Drizzle database.
|
||||||
|
|
||||||
Supported Versions and Features
|
Drizzle is a variant of MySQL. Unlike MySQL, Drizzle's default storage engine
|
||||||
-------------------------------
|
is InnoDB (transactions, foreign-keys) rather than MyISAM. For more
|
||||||
|
`Notable Differences <http://docs.drizzle.org/mysql_differences.html>`_, visit
|
||||||
|
the `Drizzle Documentation <http://docs.drizzle.org/index.html>`_.
|
||||||
|
|
||||||
SQLAlchemy supports the Drizzle database starting with 2010.08.
|
The SQLAlchemy Drizzle dialect leans heavily on the MySQL dialect, so much of
|
||||||
with capabilities increasing with more modern servers.
|
the :doc:`SQLAlchemy MySQL <mysql>` documentation is also relevant.
|
||||||
|
|
||||||
Most available DBAPI drivers are supported; see below.
|
|
||||||
|
|
||||||
===================================== ===============
|
|
||||||
Feature Minimum Version
|
|
||||||
===================================== ===============
|
|
||||||
sqlalchemy.orm 2010.08
|
|
||||||
Table Reflection 2010.08
|
|
||||||
DDL Generation 2010.08
|
|
||||||
utf8/Full Unicode Connections 2010.08
|
|
||||||
Transactions 2010.08
|
|
||||||
Two-Phase Transactions 2010.08
|
|
||||||
Nested Transactions 2010.08
|
|
||||||
===================================== ===============
|
|
||||||
|
|
||||||
See the official Drizzle documentation for detailed information about features
|
|
||||||
supported in any given server release.
|
|
||||||
|
|
||||||
Connecting
|
Connecting
|
||||||
----------
|
----------
|
||||||
|
|
||||||
See the API documentation on individual drivers for details on connecting.
|
See the individual driver sections below for details on connecting.
|
||||||
|
|
||||||
Connection Timeouts
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
Drizzle features an automatic connection close behavior, for connections that
|
|
||||||
have been idle for eight hours or more. To circumvent having this issue, use
|
|
||||||
the ``pool_recycle`` option which controls the maximum age of any connection::
|
|
||||||
|
|
||||||
engine = create_engine('drizzle+mysqldb://...', pool_recycle=3600)
|
|
||||||
|
|
||||||
Storage Engines
|
|
||||||
---------------
|
|
||||||
|
|
||||||
Drizzle defaults to the ``InnoDB`` storage engine, which is transactional.
|
|
||||||
|
|
||||||
Storage engines can be elected when creating tables in SQLAlchemy by supplying
|
|
||||||
a ``drizzle_engine='whatever'`` to the ``Table`` constructor. Any Drizzle table
|
|
||||||
creation option can be specified in this syntax::
|
|
||||||
|
|
||||||
Table('mytable', metadata,
|
|
||||||
Column('data', String(32)),
|
|
||||||
drizzle_engine='InnoDB',
|
|
||||||
)
|
|
||||||
|
|
||||||
Keys
|
|
||||||
----
|
|
||||||
|
|
||||||
Not all Drizzle storage engines support foreign keys. For ``BlitzDB`` and
|
|
||||||
similar engines, the information loaded by table reflection will not include
|
|
||||||
foreign keys. For these tables, you may supply a
|
|
||||||
:class:`~sqlalchemy.ForeignKeyConstraint` at reflection time::
|
|
||||||
|
|
||||||
Table('mytable', metadata,
|
|
||||||
ForeignKeyConstraint(['other_id'], ['othertable.other_id']),
|
|
||||||
autoload=True
|
|
||||||
)
|
|
||||||
|
|
||||||
When creating tables, SQLAlchemy will automatically set ``AUTO_INCREMENT`` on
|
|
||||||
an integer primary key column::
|
|
||||||
|
|
||||||
>>> t = Table('mytable', metadata,
|
|
||||||
... Column('mytable_id', Integer, primary_key=True)
|
|
||||||
... )
|
|
||||||
>>> t.create()
|
|
||||||
CREATE TABLE mytable (
|
|
||||||
id INTEGER NOT NULL AUTO_INCREMENT,
|
|
||||||
PRIMARY KEY (id)
|
|
||||||
)
|
|
||||||
|
|
||||||
You can disable this behavior by supplying ``autoincrement=False`` to the
|
|
||||||
:class:`~sqlalchemy.Column`. This flag can also be used to enable
|
|
||||||
auto-increment on a secondary column in a multi-column key for some storage
|
|
||||||
engines::
|
|
||||||
|
|
||||||
Table('mytable', metadata,
|
|
||||||
Column('gid', Integer, primary_key=True, autoincrement=False),
|
|
||||||
Column('id', Integer, primary_key=True)
|
|
||||||
)
|
|
||||||
|
|
||||||
Drizzle SQL Extensions
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
Many of the Drizzle SQL extensions are handled through SQLAlchemy's generic
|
|
||||||
function and operator support::
|
|
||||||
|
|
||||||
table.select(table.c.password==func.md5('plaintext'))
|
|
||||||
table.select(table.c.username.op('regexp')('^[a-d]'))
|
|
||||||
|
|
||||||
And of course any valid Drizzle statement can be executed as a string as well.
|
|
||||||
|
|
||||||
Some limited direct support for Drizzle extensions to SQL is currently
|
|
||||||
available.
|
|
||||||
|
|
||||||
* SELECT pragma::
|
|
||||||
|
|
||||||
select(..., prefixes=['HIGH_PRIORITY', 'SQL_SMALL_RESULT'])
|
|
||||||
|
|
||||||
* UPDATE with LIMIT::
|
|
||||||
|
|
||||||
update(..., drizzle_limit=10)
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import datetime, inspect, re, sys
|
from sqlalchemy import exc
|
||||||
|
from sqlalchemy import log
|
||||||
from sqlalchemy import schema as sa_schema
|
|
||||||
from sqlalchemy import exc, log, sql, util
|
|
||||||
from sqlalchemy.sql import operators as sql_operators
|
|
||||||
from sqlalchemy.sql import functions as sql_functions
|
|
||||||
from sqlalchemy.sql import compiler
|
|
||||||
from array import array as _array
|
|
||||||
|
|
||||||
from sqlalchemy.engine import reflection
|
|
||||||
from sqlalchemy.engine import base as engine_base, default
|
|
||||||
from sqlalchemy import types as sqltypes
|
from sqlalchemy import types as sqltypes
|
||||||
|
from sqlalchemy.engine import reflection
|
||||||
from sqlalchemy.dialects.mysql import base as mysql_dialect
|
from sqlalchemy.dialects.mysql import base as mysql_dialect
|
||||||
|
|
||||||
from sqlalchemy.types import DATE, DATETIME, BOOLEAN, TIME, \
|
from sqlalchemy.types import DATE, DATETIME, BOOLEAN, TIME, \
|
||||||
BLOB, BINARY, VARBINARY
|
BLOB, BINARY, VARBINARY
|
||||||
|
|
||||||
|
|
||||||
class _NumericType(object):
|
class _NumericType(object):
|
||||||
"""Base for Drizzle numeric types."""
|
"""Base for Drizzle numeric types."""
|
||||||
@@ -140,6 +38,7 @@ class _NumericType(object):
|
|||||||
def __init__(self, **kw):
|
def __init__(self, **kw):
|
||||||
super(_NumericType, self).__init__(**kw)
|
super(_NumericType, self).__init__(**kw)
|
||||||
|
|
||||||
|
|
||||||
class _FloatType(_NumericType, sqltypes.Float):
|
class _FloatType(_NumericType, sqltypes.Float):
|
||||||
def __init__(self, precision=None, scale=None, asdecimal=True, **kw):
|
def __init__(self, precision=None, scale=None, asdecimal=True, **kw):
|
||||||
if isinstance(self, (REAL, DOUBLE)) and \
|
if isinstance(self, (REAL, DOUBLE)) and \
|
||||||
@@ -147,23 +46,22 @@ class _FloatType(_NumericType, sqltypes.Float):
|
|||||||
(precision is None and scale is not None) or
|
(precision is None and scale is not None) or
|
||||||
(precision is not None and scale is None)
|
(precision is not None and scale is None)
|
||||||
):
|
):
|
||||||
raise exc.ArgumentError(
|
raise exc.ArgumentError(
|
||||||
"You must specify both precision and scale or omit "
|
"You must specify both precision and scale or omit "
|
||||||
"both altogether.")
|
"both altogether.")
|
||||||
|
|
||||||
super(_FloatType, self).__init__(precision=precision, asdecimal=asdecimal, **kw)
|
super(_FloatType, self).__init__(precision=precision,
|
||||||
|
asdecimal=asdecimal, **kw)
|
||||||
self.scale = scale
|
self.scale = scale
|
||||||
|
|
||||||
|
|
||||||
class _StringType(mysql_dialect._StringType):
|
class _StringType(mysql_dialect._StringType):
|
||||||
"""Base for Drizzle string types."""
|
"""Base for Drizzle string types."""
|
||||||
|
|
||||||
def __init__(self, collation=None,
|
def __init__(self, collation=None, binary=False, **kw):
|
||||||
binary=False,
|
|
||||||
**kw):
|
|
||||||
kw['national'] = False
|
kw['national'] = False
|
||||||
super(_StringType, self).__init__(collation=collation,
|
super(_StringType, self).__init__(collation=collation, binary=binary,
|
||||||
binary=binary,
|
**kw)
|
||||||
**kw)
|
|
||||||
|
|
||||||
|
|
||||||
class NUMERIC(_NumericType, sqltypes.NUMERIC):
|
class NUMERIC(_NumericType, sqltypes.NUMERIC):
|
||||||
@@ -180,7 +78,9 @@ class NUMERIC(_NumericType, sqltypes.NUMERIC):
|
|||||||
:param scale: The number of digits after the decimal point.
|
:param scale: The number of digits after the decimal point.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
super(NUMERIC, self).__init__(precision=precision, scale=scale, asdecimal=asdecimal, **kw)
|
|
||||||
|
super(NUMERIC, self).__init__(precision=precision, scale=scale,
|
||||||
|
asdecimal=asdecimal, **kw)
|
||||||
|
|
||||||
|
|
||||||
class DECIMAL(_NumericType, sqltypes.DECIMAL):
|
class DECIMAL(_NumericType, sqltypes.DECIMAL):
|
||||||
@@ -215,9 +115,11 @@ class DOUBLE(_FloatType):
|
|||||||
:param scale: The number of digits after the decimal point.
|
:param scale: The number of digits after the decimal point.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super(DOUBLE, self).__init__(precision=precision, scale=scale,
|
super(DOUBLE, self).__init__(precision=precision, scale=scale,
|
||||||
asdecimal=asdecimal, **kw)
|
asdecimal=asdecimal, **kw)
|
||||||
|
|
||||||
|
|
||||||
class REAL(_FloatType, sqltypes.REAL):
|
class REAL(_FloatType, sqltypes.REAL):
|
||||||
"""Drizzle REAL type."""
|
"""Drizzle REAL type."""
|
||||||
|
|
||||||
@@ -232,9 +134,11 @@ class REAL(_FloatType, sqltypes.REAL):
|
|||||||
:param scale: The number of digits after the decimal point.
|
:param scale: The number of digits after the decimal point.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super(REAL, self).__init__(precision=precision, scale=scale,
|
super(REAL, self).__init__(precision=precision, scale=scale,
|
||||||
asdecimal=asdecimal, **kw)
|
asdecimal=asdecimal, **kw)
|
||||||
|
|
||||||
|
|
||||||
class FLOAT(_FloatType, sqltypes.FLOAT):
|
class FLOAT(_FloatType, sqltypes.FLOAT):
|
||||||
"""Drizzle FLOAT type."""
|
"""Drizzle FLOAT type."""
|
||||||
|
|
||||||
@@ -249,42 +153,46 @@ class FLOAT(_FloatType, sqltypes.FLOAT):
|
|||||||
:param scale: The number of digits after the decimal point.
|
:param scale: The number of digits after the decimal point.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super(FLOAT, self).__init__(precision=precision, scale=scale,
|
super(FLOAT, self).__init__(precision=precision, scale=scale,
|
||||||
asdecimal=asdecimal, **kw)
|
asdecimal=asdecimal, **kw)
|
||||||
|
|
||||||
def bind_processor(self, dialect):
|
def bind_processor(self, dialect):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class INTEGER(sqltypes.INTEGER):
|
class INTEGER(sqltypes.INTEGER):
|
||||||
"""Drizzle INTEGER type."""
|
"""Drizzle INTEGER type."""
|
||||||
|
|
||||||
__visit_name__ = 'INTEGER'
|
__visit_name__ = 'INTEGER'
|
||||||
|
|
||||||
def __init__(self, **kw):
|
def __init__(self, **kw):
|
||||||
"""Construct an INTEGER.
|
"""Construct an INTEGER."""
|
||||||
|
|
||||||
"""
|
|
||||||
super(INTEGER, self).__init__(**kw)
|
super(INTEGER, self).__init__(**kw)
|
||||||
|
|
||||||
|
|
||||||
class BIGINT(sqltypes.BIGINT):
|
class BIGINT(sqltypes.BIGINT):
|
||||||
"""Drizzle BIGINTEGER type."""
|
"""Drizzle BIGINTEGER type."""
|
||||||
|
|
||||||
__visit_name__ = 'BIGINT'
|
__visit_name__ = 'BIGINT'
|
||||||
|
|
||||||
def __init__(self, **kw):
|
def __init__(self, **kw):
|
||||||
"""Construct a BIGINTEGER.
|
"""Construct a BIGINTEGER."""
|
||||||
|
|
||||||
"""
|
|
||||||
super(BIGINT, self).__init__(**kw)
|
super(BIGINT, self).__init__(**kw)
|
||||||
|
|
||||||
|
|
||||||
class _DrizzleTime(mysql_dialect._MSTime):
|
class _DrizzleTime(mysql_dialect._MSTime):
|
||||||
"""Drizzle TIME type."""
|
"""Drizzle TIME type."""
|
||||||
|
|
||||||
|
|
||||||
class TIMESTAMP(sqltypes.TIMESTAMP):
|
class TIMESTAMP(sqltypes.TIMESTAMP):
|
||||||
"""Drizzle TIMESTAMP type."""
|
"""Drizzle TIMESTAMP type."""
|
||||||
|
|
||||||
__visit_name__ = 'TIMESTAMP'
|
__visit_name__ = 'TIMESTAMP'
|
||||||
|
|
||||||
|
|
||||||
class TEXT(_StringType, sqltypes.TEXT):
|
class TEXT(_StringType, sqltypes.TEXT):
|
||||||
"""Drizzle TEXT type, for text up to 2^16 characters."""
|
"""Drizzle TEXT type, for text up to 2^16 characters."""
|
||||||
|
|
||||||
@@ -306,8 +214,10 @@ class TEXT(_StringType, sqltypes.TEXT):
|
|||||||
only the collation of character data.
|
only the collation of character data.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super(TEXT, self).__init__(length=length, **kw)
|
super(TEXT, self).__init__(length=length, **kw)
|
||||||
|
|
||||||
|
|
||||||
class VARCHAR(_StringType, sqltypes.VARCHAR):
|
class VARCHAR(_StringType, sqltypes.VARCHAR):
|
||||||
"""Drizzle VARCHAR type, for variable-length character data."""
|
"""Drizzle VARCHAR type, for variable-length character data."""
|
||||||
|
|
||||||
@@ -325,8 +235,10 @@ class VARCHAR(_StringType, sqltypes.VARCHAR):
|
|||||||
only the collation of character data.
|
only the collation of character data.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super(VARCHAR, self).__init__(length=length, **kwargs)
|
super(VARCHAR, self).__init__(length=length, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class CHAR(_StringType, sqltypes.CHAR):
|
class CHAR(_StringType, sqltypes.CHAR):
|
||||||
"""Drizzle CHAR type, for fixed-length character data."""
|
"""Drizzle CHAR type, for fixed-length character data."""
|
||||||
|
|
||||||
@@ -345,8 +257,10 @@ class CHAR(_StringType, sqltypes.CHAR):
|
|||||||
compatible with the national character set.
|
compatible with the national character set.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super(CHAR, self).__init__(length=length, **kwargs)
|
super(CHAR, self).__init__(length=length, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class ENUM(mysql_dialect.ENUM):
|
class ENUM(mysql_dialect.ENUM):
|
||||||
"""Drizzle ENUM type."""
|
"""Drizzle ENUM type."""
|
||||||
|
|
||||||
@@ -363,8 +277,9 @@ class ENUM(mysql_dialect.ENUM):
|
|||||||
|
|
||||||
:param strict: Defaults to False: ensure that a given value is in this
|
:param strict: Defaults to False: ensure that a given value is in this
|
||||||
ENUM's range of permissible values when inserting or updating rows.
|
ENUM's range of permissible values when inserting or updating rows.
|
||||||
Note that Drizzle will not raise a fatal error if you attempt to store
|
Note that Drizzle will not raise a fatal error if you attempt to
|
||||||
an out of range value- an alternate value will be stored instead.
|
store an out of range value- an alternate value will be stored
|
||||||
|
instead.
|
||||||
(See Drizzle ENUM documentation.)
|
(See Drizzle ENUM documentation.)
|
||||||
|
|
||||||
:param collation: Optional, a column-level collation for this string
|
:param collation: Optional, a column-level collation for this string
|
||||||
@@ -390,12 +305,15 @@ class ENUM(mysql_dialect.ENUM):
|
|||||||
literals for you. This is a transitional option.
|
literals for you. This is a transitional option.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super(ENUM, self).__init__(*enums, **kw)
|
super(ENUM, self).__init__(*enums, **kw)
|
||||||
|
|
||||||
|
|
||||||
class _DrizzleBoolean(sqltypes.Boolean):
|
class _DrizzleBoolean(sqltypes.Boolean):
|
||||||
def get_dbapi_type(self, dbapi):
|
def get_dbapi_type(self, dbapi):
|
||||||
return dbapi.NUMERIC
|
return dbapi.NUMERIC
|
||||||
|
|
||||||
|
|
||||||
colspecs = {
|
colspecs = {
|
||||||
sqltypes.Numeric: NUMERIC,
|
sqltypes.Numeric: NUMERIC,
|
||||||
sqltypes.Float: FLOAT,
|
sqltypes.Float: FLOAT,
|
||||||
@@ -404,6 +322,7 @@ colspecs = {
|
|||||||
sqltypes.Boolean: _DrizzleBoolean,
|
sqltypes.Boolean: _DrizzleBoolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# All the types we have in Drizzle
|
# All the types we have in Drizzle
|
||||||
ischema_names = {
|
ischema_names = {
|
||||||
'BIGINT': BIGINT,
|
'BIGINT': BIGINT,
|
||||||
@@ -427,6 +346,7 @@ ischema_names = {
|
|||||||
'VARCHAR': VARCHAR,
|
'VARCHAR': VARCHAR,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class DrizzleCompiler(mysql_dialect.MySQLCompiler):
|
class DrizzleCompiler(mysql_dialect.MySQLCompiler):
|
||||||
|
|
||||||
def visit_typeclause(self, typeclause):
|
def visit_typeclause(self, typeclause):
|
||||||
@@ -439,7 +359,7 @@ class DrizzleCompiler(mysql_dialect.MySQLCompiler):
|
|||||||
def visit_cast(self, cast, **kwargs):
|
def visit_cast(self, cast, **kwargs):
|
||||||
type_ = self.process(cast.typeclause)
|
type_ = self.process(cast.typeclause)
|
||||||
if type_ is None:
|
if type_ is None:
|
||||||
return self.process(cast.clause)
|
return self.process(cast.clause)
|
||||||
|
|
||||||
return 'CAST(%s AS %s)' % (self.process(cast.clause), type_)
|
return 'CAST(%s AS %s)' % (self.process(cast.clause), type_)
|
||||||
|
|
||||||
@@ -447,6 +367,7 @@ class DrizzleCompiler(mysql_dialect.MySQLCompiler):
|
|||||||
class DrizzleDDLCompiler(mysql_dialect.MySQLDDLCompiler):
|
class DrizzleDDLCompiler(mysql_dialect.MySQLDDLCompiler):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class DrizzleTypeCompiler(mysql_dialect.MySQLTypeCompiler):
|
class DrizzleTypeCompiler(mysql_dialect.MySQLTypeCompiler):
|
||||||
def _extend_numeric(self, type_, spec):
|
def _extend_numeric(self, type_, spec):
|
||||||
return spec
|
return spec
|
||||||
@@ -492,11 +413,16 @@ class DrizzleTypeCompiler(mysql_dialect.MySQLTypeCompiler):
|
|||||||
class DrizzleExecutionContext(mysql_dialect.MySQLExecutionContext):
|
class DrizzleExecutionContext(mysql_dialect.MySQLExecutionContext):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class DrizzleIdentifierPreparer(mysql_dialect.MySQLIdentifierPreparer):
|
class DrizzleIdentifierPreparer(mysql_dialect.MySQLIdentifierPreparer):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class DrizzleDialect(mysql_dialect.MySQLDialect):
|
class DrizzleDialect(mysql_dialect.MySQLDialect):
|
||||||
"""Details of the Drizzle dialect. Not used directly in application code."""
|
"""Details of the Drizzle dialect.
|
||||||
|
|
||||||
|
Not used directly in application code.
|
||||||
|
"""
|
||||||
|
|
||||||
name = 'drizzle'
|
name = 'drizzle'
|
||||||
|
|
||||||
@@ -505,7 +431,6 @@ class DrizzleDialect(mysql_dialect.MySQLDialect):
|
|||||||
supports_native_boolean = True
|
supports_native_boolean = True
|
||||||
supports_views = False
|
supports_views = False
|
||||||
|
|
||||||
|
|
||||||
default_paramstyle = 'format'
|
default_paramstyle = 'format'
|
||||||
colspecs = colspecs
|
colspecs = colspecs
|
||||||
|
|
||||||
@@ -516,8 +441,8 @@ class DrizzleDialect(mysql_dialect.MySQLDialect):
|
|||||||
preparer = DrizzleIdentifierPreparer
|
preparer = DrizzleIdentifierPreparer
|
||||||
|
|
||||||
def on_connect(self):
|
def on_connect(self):
|
||||||
"""Force autocommit - Drizzle Bug#707842 doesn't set this
|
"""Force autocommit - Drizzle Bug#707842 doesn't set this properly"""
|
||||||
properly"""
|
|
||||||
def connect(conn):
|
def connect(conn):
|
||||||
conn.autocommit(False)
|
conn.autocommit(False)
|
||||||
return connect
|
return connect
|
||||||
@@ -535,6 +460,7 @@ class DrizzleDialect(mysql_dialect.MySQLDialect):
|
|||||||
@reflection.cache
|
@reflection.cache
|
||||||
def get_table_names(self, connection, schema=None, **kw):
|
def get_table_names(self, connection, schema=None, **kw):
|
||||||
"""Return a Unicode SHOW TABLES from a given schema."""
|
"""Return a Unicode SHOW TABLES from a given schema."""
|
||||||
|
|
||||||
if schema is not None:
|
if schema is not None:
|
||||||
current_schema = schema
|
current_schema = schema
|
||||||
else:
|
else:
|
||||||
@@ -554,8 +480,8 @@ class DrizzleDialect(mysql_dialect.MySQLDialect):
|
|||||||
|
|
||||||
Cached per-connection. This value can not change without a server
|
Cached per-connection. This value can not change without a server
|
||||||
restart.
|
restart.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def _detect_collations(self, connection):
|
def _detect_collations(self, connection):
|
||||||
@@ -566,7 +492,9 @@ class DrizzleDialect(mysql_dialect.MySQLDialect):
|
|||||||
|
|
||||||
collations = {}
|
collations = {}
|
||||||
charset = self._connection_charset
|
charset = self._connection_charset
|
||||||
rs = connection.execute('SELECT CHARACTER_SET_NAME, COLLATION_NAME from data_dictionary.COLLATIONS')
|
rs = connection.execute(
|
||||||
|
'SELECT CHARACTER_SET_NAME, COLLATION_NAME FROM'
|
||||||
|
' data_dictionary.COLLATIONS')
|
||||||
for row in self._compat_fetchall(rs, charset):
|
for row in self._compat_fetchall(rs, charset):
|
||||||
collations[row[0]] = row[1]
|
collations[row[0]] = row[1]
|
||||||
return collations
|
return collations
|
||||||
@@ -575,8 +503,7 @@ class DrizzleDialect(mysql_dialect.MySQLDialect):
|
|||||||
"""Detect and adjust for the ANSI_QUOTES sql mode."""
|
"""Detect and adjust for the ANSI_QUOTES sql mode."""
|
||||||
|
|
||||||
self._server_ansiquotes = False
|
self._server_ansiquotes = False
|
||||||
|
|
||||||
self._backslash_escapes = False
|
self._backslash_escapes = False
|
||||||
|
|
||||||
log.class_logger(DrizzleDialect)
|
|
||||||
|
|
||||||
|
log.class_logger(DrizzleDialect)
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
"""Support for the Drizzle database via the Drizzle-python adapter.
|
"""Support for the Drizzle database via the mysql-python adapter.
|
||||||
|
|
||||||
Drizzle-Python is available at:
|
MySQL-Python is available at:
|
||||||
|
|
||||||
http://sourceforge.net/projects/mysql-python
|
http://sourceforge.net/projects/mysql-python
|
||||||
|
|
||||||
At least version 1.2.1 or 1.2.2 should be used.
|
|
||||||
|
|
||||||
Connecting
|
Connecting
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
@@ -13,37 +11,22 @@ Connect string format::
|
|||||||
|
|
||||||
drizzle+mysqldb://<user>:<password>@<host>[:<port>]/<dbname>
|
drizzle+mysqldb://<user>:<password>@<host>[:<port>]/<dbname>
|
||||||
|
|
||||||
Unicode
|
|
||||||
-------
|
|
||||||
|
|
||||||
Drizzle accommodates Python ``unicode`` objects directly and
|
|
||||||
uses the ``utf8`` encoding in all cases.
|
|
||||||
|
|
||||||
Known Issues
|
|
||||||
-------------
|
|
||||||
|
|
||||||
Drizzle-python at least as of version 1.2.2 has a serious memory leak related
|
|
||||||
to unicode conversion, a feature which is disabled via ``use_unicode=0``.
|
|
||||||
The recommended connection form with SQLAlchemy is::
|
|
||||||
|
|
||||||
engine = create_engine('mysql://scott:tiger@localhost/test?charset=utf8&use_unicode=0', pool_recycle=3600)
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from sqlalchemy.dialects.drizzle.base import (DrizzleDialect,
|
from sqlalchemy.dialects.drizzle.base import (
|
||||||
DrizzleExecutionContext,
|
DrizzleDialect,
|
||||||
DrizzleCompiler, DrizzleIdentifierPreparer)
|
DrizzleExecutionContext,
|
||||||
|
DrizzleCompiler,
|
||||||
|
DrizzleIdentifierPreparer)
|
||||||
from sqlalchemy.connectors.mysqldb import (
|
from sqlalchemy.connectors.mysqldb import (
|
||||||
MySQLDBExecutionContext,
|
MySQLDBExecutionContext,
|
||||||
MySQLDBCompiler,
|
MySQLDBCompiler,
|
||||||
MySQLDBIdentifierPreparer,
|
MySQLDBIdentifierPreparer,
|
||||||
MySQLDBConnector
|
MySQLDBConnector)
|
||||||
)
|
|
||||||
|
|
||||||
class DrizzleExecutionContext_mysqldb(
|
|
||||||
MySQLDBExecutionContext,
|
class DrizzleExecutionContext_mysqldb(MySQLDBExecutionContext,
|
||||||
DrizzleExecutionContext):
|
DrizzleExecutionContext):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -51,11 +34,11 @@ class DrizzleCompiler_mysqldb(MySQLDBCompiler, DrizzleCompiler):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class DrizzleIdentifierPreparer_mysqldb(
|
class DrizzleIdentifierPreparer_mysqldb(MySQLDBIdentifierPreparer,
|
||||||
MySQLDBIdentifierPreparer,
|
DrizzleIdentifierPreparer):
|
||||||
DrizzleIdentifierPreparer):
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class DrizzleDialect_mysqldb(MySQLDBConnector, DrizzleDialect):
|
class DrizzleDialect_mysqldb(MySQLDBConnector, DrizzleDialect):
|
||||||
execution_ctx_cls = DrizzleExecutionContext_mysqldb
|
execution_ctx_cls = DrizzleExecutionContext_mysqldb
|
||||||
statement_compiler = DrizzleCompiler_mysqldb
|
statement_compiler = DrizzleCompiler_mysqldb
|
||||||
@@ -63,6 +46,7 @@ class DrizzleDialect_mysqldb(MySQLDBConnector, DrizzleDialect):
|
|||||||
|
|
||||||
def _detect_charset(self, connection):
|
def _detect_charset(self, connection):
|
||||||
"""Sniff out the character set in use for connection results."""
|
"""Sniff out the character set in use for connection results."""
|
||||||
|
|
||||||
return 'utf8'
|
return 'utf8'
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# firebird/__init__.py
|
# firebird/__init__.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# firebird/base.py
|
# firebird/base.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -198,7 +198,7 @@ class FBTypeCompiler(compiler.GenericTypeCompiler):
|
|||||||
|
|
||||||
|
|
||||||
class FBCompiler(sql.compiler.SQLCompiler):
|
class FBCompiler(sql.compiler.SQLCompiler):
|
||||||
"""Firebird specific idiosincrasies"""
|
"""Firebird specific idiosyncrasies"""
|
||||||
|
|
||||||
def visit_mod(self, binary, **kw):
|
def visit_mod(self, binary, **kw):
|
||||||
# Firebird lacks a builtin modulo operator, but there is
|
# Firebird lacks a builtin modulo operator, but there is
|
||||||
@@ -293,7 +293,7 @@ class FBCompiler(sql.compiler.SQLCompiler):
|
|||||||
|
|
||||||
|
|
||||||
class FBDDLCompiler(sql.compiler.DDLCompiler):
|
class FBDDLCompiler(sql.compiler.DDLCompiler):
|
||||||
"""Firebird syntactic idiosincrasies"""
|
"""Firebird syntactic idiosyncrasies"""
|
||||||
|
|
||||||
def visit_create_sequence(self, create):
|
def visit_create_sequence(self, create):
|
||||||
"""Generate a ``CREATE GENERATOR`` statement for the sequence."""
|
"""Generate a ``CREATE GENERATOR`` statement for the sequence."""
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# firebird/kinterbasdb.py
|
# firebird/kinterbasdb.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# informix/__init__.py
|
# informix/__init__.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# informix/base.py
|
# informix/base.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
# coding: gbk
|
# coding: gbk
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
@@ -7,8 +7,11 @@
|
|||||||
|
|
||||||
"""Support for the Informix database.
|
"""Support for the Informix database.
|
||||||
|
|
||||||
This dialect is mostly functional as of SQLAlchemy 0.6.5.
|
.. note::
|
||||||
|
|
||||||
|
The Informix dialect functions on current SQLAlchemy versions
|
||||||
|
but is not regularly tested, and may have many issues and
|
||||||
|
caveats not currently handled.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -23,7 +26,7 @@ from sqlalchemy import types as sqltypes
|
|||||||
RESERVED_WORDS = set(
|
RESERVED_WORDS = set(
|
||||||
["abs", "absolute", "access", "access_method", "acos", "active", "add",
|
["abs", "absolute", "access", "access_method", "acos", "active", "add",
|
||||||
"address", "add_months", "admin", "after", "aggregate", "alignment",
|
"address", "add_months", "admin", "after", "aggregate", "alignment",
|
||||||
"all", "allocate", "all_rows", "altere", "and", "ansi", "any", "append",
|
"all", "allocate", "all_rows", "alter", "and", "ansi", "any", "append",
|
||||||
"array", "as", "asc", "ascii", "asin", "at", "atan", "atan2", "attach",
|
"array", "as", "asc", "ascii", "asin", "at", "atan", "atan2", "attach",
|
||||||
"attributes", "audit", "authentication", "authid", "authorization",
|
"attributes", "audit", "authentication", "authid", "authorization",
|
||||||
"authorized", "auto", "autofree", "auto_reprepare", "auto_stat_mode",
|
"authorized", "auto", "autofree", "auto_reprepare", "auto_stat_mode",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# informix/informixdb.py
|
# informix/informixdb.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# maxdb/__init__.py
|
# maxdb/__init__.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
# maxdb/base.py
|
# maxdb/base.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|
||||||
"""Support for the MaxDB database.
|
"""Support for the MaxDB database.
|
||||||
|
|
||||||
This dialect is *not* ported to SQLAlchemy 0.6 or 0.7.
|
.. note::
|
||||||
|
|
||||||
This dialect is *not* tested on SQLAlchemy 0.6 or 0.7.
|
The MaxDB dialect is **non-functional as of SQLAlchemy 0.6**,
|
||||||
|
pending development efforts to bring it up-to-date.
|
||||||
|
|
||||||
Overview
|
Overview
|
||||||
--------
|
--------
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# maxdb/sapdb.py
|
# maxdb/sapdb.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# mssql/__init__.py
|
# mssql/__init__.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# mssql/adodbapi.py
|
# mssql/adodbapi.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# mssql/base.py
|
# mssql/base.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -107,10 +107,10 @@ Compatibility Levels
|
|||||||
--------------------
|
--------------------
|
||||||
MSSQL supports the notion of setting compatibility levels at the
|
MSSQL supports the notion of setting compatibility levels at the
|
||||||
database level. This allows, for instance, to run a database that
|
database level. This allows, for instance, to run a database that
|
||||||
is compatibile with SQL2000 while running on a SQL2005 database
|
is compatible with SQL2000 while running on a SQL2005 database
|
||||||
server. ``server_version_info`` will always return the database
|
server. ``server_version_info`` will always return the database
|
||||||
server version information (in this case SQL2005) and not the
|
server version information (in this case SQL2005) and not the
|
||||||
compatibiility level information. Because of this, if running under
|
compatibility level information. Because of this, if running under
|
||||||
a backwards compatibility mode SQAlchemy may attempt to use T-SQL
|
a backwards compatibility mode SQAlchemy may attempt to use T-SQL
|
||||||
statements that are unable to be parsed by the database server.
|
statements that are unable to be parsed by the database server.
|
||||||
|
|
||||||
@@ -161,11 +161,12 @@ http://msdn.microsoft.com/en-us/library/ms175095.aspx.
|
|||||||
Scalar Select Comparisons
|
Scalar Select Comparisons
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
The MSSQL dialect contains a legacy behavior whereby comparing
|
.. deprecated:: 0.8
|
||||||
a scalar select to a value using the ``=`` or ``!=`` operator
|
The MSSQL dialect contains a legacy behavior whereby comparing
|
||||||
will resolve to IN or NOT IN, respectively. This behavior is
|
a scalar select to a value using the ``=`` or ``!=`` operator
|
||||||
deprecated and will be removed in 0.8 - the ``s.in_()``/``~s.in_()`` operators
|
will resolve to IN or NOT IN, respectively. This behavior
|
||||||
should be used when IN/NOT IN are desired.
|
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
|
For the time being, the existing behavior prevents a comparison
|
||||||
between scalar select and another value that actually wants to use ``=``.
|
between scalar select and another value that actually wants to use ``=``.
|
||||||
@@ -689,18 +690,22 @@ class MSExecutionContext(default.DefaultExecutionContext):
|
|||||||
not self.executemany
|
not self.executemany
|
||||||
|
|
||||||
if self._enable_identity_insert:
|
if self._enable_identity_insert:
|
||||||
self.cursor.execute("SET IDENTITY_INSERT %s ON" %
|
self.root_connection._cursor_execute(self.cursor,
|
||||||
self.dialect.identifier_preparer.format_table(tbl))
|
"SET IDENTITY_INSERT %s ON" %
|
||||||
|
self.dialect.identifier_preparer.format_table(tbl),
|
||||||
|
())
|
||||||
|
|
||||||
def post_exec(self):
|
def post_exec(self):
|
||||||
"""Disable IDENTITY_INSERT if enabled."""
|
"""Disable IDENTITY_INSERT if enabled."""
|
||||||
|
|
||||||
|
conn = self.root_connection
|
||||||
if self._select_lastrowid:
|
if self._select_lastrowid:
|
||||||
if self.dialect.use_scope_identity:
|
if self.dialect.use_scope_identity:
|
||||||
self.cursor.execute(
|
conn._cursor_execute(self.cursor,
|
||||||
"SELECT scope_identity() AS lastrowid", ())
|
"SELECT scope_identity() AS lastrowid", ())
|
||||||
else:
|
else:
|
||||||
self.cursor.execute("SELECT @@identity AS lastrowid", ())
|
conn._cursor_execute(self.cursor,
|
||||||
|
"SELECT @@identity AS lastrowid", ())
|
||||||
# fetchall() ensures the cursor is consumed without closing it
|
# fetchall() ensures the cursor is consumed without closing it
|
||||||
row = self.cursor.fetchall()[0]
|
row = self.cursor.fetchall()[0]
|
||||||
self._lastrowid = int(row[0])
|
self._lastrowid = int(row[0])
|
||||||
@@ -710,10 +715,11 @@ class MSExecutionContext(default.DefaultExecutionContext):
|
|||||||
self._result_proxy = base.FullyBufferedResultProxy(self)
|
self._result_proxy = base.FullyBufferedResultProxy(self)
|
||||||
|
|
||||||
if self._enable_identity_insert:
|
if self._enable_identity_insert:
|
||||||
self.cursor.execute(
|
conn._cursor_execute(self.cursor,
|
||||||
"SET IDENTITY_INSERT %s OFF" %
|
"SET IDENTITY_INSERT %s OFF" %
|
||||||
self.dialect.identifier_preparer.
|
self.dialect.identifier_preparer.
|
||||||
format_table(self.compiled.statement.table)
|
format_table(self.compiled.statement.table),
|
||||||
|
()
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_lastrowid(self):
|
def get_lastrowid(self):
|
||||||
@@ -803,27 +809,28 @@ class MSSQLCompiler(compiler.SQLCompiler):
|
|||||||
so tries to wrap it in a subquery with ``row_number()`` criterion.
|
so tries to wrap it in a subquery with ``row_number()`` criterion.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not getattr(select, '_mssql_visit', None) and select._offset:
|
if select._offset and not getattr(select, '_mssql_visit', None):
|
||||||
# to use ROW_NUMBER(), an ORDER BY is required.
|
# to use ROW_NUMBER(), an ORDER BY is required.
|
||||||
orderby = self.process(select._order_by_clause)
|
if not select._order_by_clause.clauses:
|
||||||
if not orderby:
|
|
||||||
raise exc.CompileError('MSSQL requires an order_by when '
|
raise exc.CompileError('MSSQL requires an order_by when '
|
||||||
'using an offset.')
|
'using an offset.')
|
||||||
|
|
||||||
_offset = select._offset
|
_offset = select._offset
|
||||||
_limit = select._limit
|
_limit = select._limit
|
||||||
|
_order_by_clauses = select._order_by_clause.clauses
|
||||||
|
select = select._generate()
|
||||||
select._mssql_visit = True
|
select._mssql_visit = True
|
||||||
select = select.column(
|
select = select.column(
|
||||||
sql.literal_column("ROW_NUMBER() OVER (ORDER BY %s)" \
|
sql.func.ROW_NUMBER().over(order_by=_order_by_clauses)
|
||||||
% orderby).label("mssql_rn")
|
.label("mssql_rn")
|
||||||
).order_by(None).alias()
|
).order_by(None).alias()
|
||||||
|
|
||||||
mssql_rn = sql.column('mssql_rn')
|
mssql_rn = sql.column('mssql_rn')
|
||||||
limitselect = sql.select([c for c in select.c if
|
limitselect = sql.select([c for c in select.c if
|
||||||
c.key!='mssql_rn'])
|
c.key != 'mssql_rn'])
|
||||||
limitselect.append_whereclause(mssql_rn> _offset)
|
limitselect.append_whereclause(mssql_rn > _offset)
|
||||||
if _limit is not None:
|
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)
|
return self.process(limitselect, iswrapper=True, **kwargs)
|
||||||
else:
|
else:
|
||||||
return compiler.SQLCompiler.visit_select(self, select, **kwargs)
|
return compiler.SQLCompiler.visit_select(self, select, **kwargs)
|
||||||
@@ -875,7 +882,8 @@ class MSSQLCompiler(compiler.SQLCompiler):
|
|||||||
|
|
||||||
if result_map is not None:
|
if result_map is not None:
|
||||||
result_map[column.name.lower()] = \
|
result_map[column.name.lower()] = \
|
||||||
(column.name, (column, ),
|
(column.name, (column, column.name,
|
||||||
|
column.key),
|
||||||
column.type)
|
column.type)
|
||||||
|
|
||||||
return super(MSSQLCompiler, self).\
|
return super(MSSQLCompiler, self).\
|
||||||
@@ -980,6 +988,22 @@ class MSSQLCompiler(compiler.SQLCompiler):
|
|||||||
else:
|
else:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
def update_from_clause(self, update_stmt,
|
||||||
|
from_table, extra_froms,
|
||||||
|
from_hints,
|
||||||
|
**kw):
|
||||||
|
"""Render the UPDATE..FROM clause specific to MSSQL.
|
||||||
|
|
||||||
|
In MSSQL, if the UPDATE statement involves an alias of the table to
|
||||||
|
be updated, then the table itself must be added to the FROM list as
|
||||||
|
well. Otherwise, it is optional. Here, we add it regardless.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return "FROM " + ', '.join(
|
||||||
|
t._compiler_dispatch(self, asfrom=True,
|
||||||
|
fromhints=from_hints, **kw)
|
||||||
|
for t in [from_table] + extra_froms)
|
||||||
|
|
||||||
class MSSQLStrictCompiler(MSSQLCompiler):
|
class MSSQLStrictCompiler(MSSQLCompiler):
|
||||||
"""A subclass of MSSQLCompiler which disables the usage of bind
|
"""A subclass of MSSQLCompiler which disables the usage of bind
|
||||||
parameters where not allowed natively by MS-SQL.
|
parameters where not allowed natively by MS-SQL.
|
||||||
@@ -1431,6 +1455,7 @@ class MSDialect(default.DefaultDialect):
|
|||||||
# Primary key constraints
|
# Primary key constraints
|
||||||
s = sql.select([C.c.column_name, TC.c.constraint_type],
|
s = sql.select([C.c.column_name, TC.c.constraint_type],
|
||||||
sql.and_(TC.c.constraint_name == C.c.constraint_name,
|
sql.and_(TC.c.constraint_name == C.c.constraint_name,
|
||||||
|
TC.c.table_schema == C.c.table_schema,
|
||||||
C.c.table_name == tablename,
|
C.c.table_name == tablename,
|
||||||
C.c.table_schema == current_schema)
|
C.c.table_schema == current_schema)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# mssql/information_schema.py
|
# mssql/information_schema.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -15,8 +15,10 @@ class CoerceUnicode(TypeDecorator):
|
|||||||
impl = Unicode
|
impl = Unicode
|
||||||
|
|
||||||
def process_bind_param(self, value, dialect):
|
def process_bind_param(self, value, dialect):
|
||||||
|
# Py2K
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
value = value.decode(dialect.encoding)
|
value = value.decode(dialect.encoding)
|
||||||
|
# end Py2K
|
||||||
return value
|
return value
|
||||||
|
|
||||||
schemata = Table("SCHEMATA", ischema,
|
schemata = Table("SCHEMATA", ischema,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# mssql/mxodbc.py
|
# mssql/mxodbc.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -47,7 +47,7 @@ This behavior can be controlled via
|
|||||||
:meth:`~sqlalchemy.sql.expression.Executable.execution_options` using the
|
:meth:`~sqlalchemy.sql.expression.Executable.execution_options` using the
|
||||||
``native_odbc_execute`` flag with a value of ``True`` or ``False``, where a
|
``native_odbc_execute`` flag with a value of ``True`` or ``False``, where a
|
||||||
value of ``True`` will unconditionally use native bind parameters and a value
|
value of ``True`` will unconditionally use native bind parameters and a value
|
||||||
of ``False`` will uncondtionally use string-escaped parameters.
|
of ``False`` will unconditionally use string-escaped parameters.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# mssql/pymssql.py
|
# mssql/pymssql.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# mssql/pyodbc.py
|
# mssql/pyodbc.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -80,6 +80,34 @@ the python shell. For example::
|
|||||||
>>> urllib.quote_plus('dsn=mydsn;Database=db')
|
>>> urllib.quote_plus('dsn=mydsn;Database=db')
|
||||||
'dsn%3Dmydsn%3BDatabase%3Ddb'
|
'dsn%3Dmydsn%3BDatabase%3Ddb'
|
||||||
|
|
||||||
|
Unicode Binds
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The current state of PyODBC on a unix backend with FreeTDS and/or
|
||||||
|
EasySoft is poor regarding unicode; different OS platforms and versions of UnixODBC
|
||||||
|
versus IODBC versus FreeTDS/EasySoft versus PyODBC itself dramatically
|
||||||
|
alter how strings are received. The PyODBC dialect attempts to use all the information
|
||||||
|
it knows to determine whether or not a Python unicode literal can be
|
||||||
|
passed directly to the PyODBC driver or not; while SQLAlchemy can encode
|
||||||
|
these to bytestrings first, some users have reported that PyODBC mis-handles
|
||||||
|
bytestrings for certain encodings and requires a Python unicode object,
|
||||||
|
while the author has observed widespread cases where a Python unicode
|
||||||
|
is completely misinterpreted by PyODBC, particularly when dealing with
|
||||||
|
the information schema tables used in table reflection, and the value
|
||||||
|
must first be encoded to a bytestring.
|
||||||
|
|
||||||
|
It is for this reason that whether or not unicode literals for bound
|
||||||
|
parameters be sent to PyODBC can be controlled using the
|
||||||
|
``supports_unicode_binds`` parameter to ``create_engine()``. When
|
||||||
|
left at its default of ``None``, the PyODBC dialect will use its
|
||||||
|
best guess as to whether or not the driver deals with unicode literals
|
||||||
|
well. When ``False``, unicode literals will be encoded first, and when
|
||||||
|
``True`` unicode literals will be passed straight through. This is an interim
|
||||||
|
flag that hopefully should not be needed when the unicode situation stabilizes
|
||||||
|
for unix + PyODBC.
|
||||||
|
|
||||||
|
.. versionadded:: 0.7.7
|
||||||
|
``supports_unicode_binds`` parameter to ``create_engine()``\ .
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -217,7 +245,8 @@ class MSDialect_pyodbc(PyODBCConnector, MSDialect):
|
|||||||
def __init__(self, description_encoding='latin-1', **params):
|
def __init__(self, description_encoding='latin-1', **params):
|
||||||
super(MSDialect_pyodbc, self).__init__(**params)
|
super(MSDialect_pyodbc, self).__init__(**params)
|
||||||
self.description_encoding = description_encoding
|
self.description_encoding = description_encoding
|
||||||
self.use_scope_identity = self.dbapi and \
|
self.use_scope_identity = self.use_scope_identity and \
|
||||||
|
self.dbapi and \
|
||||||
hasattr(self.dbapi.Cursor, 'nextset')
|
hasattr(self.dbapi.Cursor, 'nextset')
|
||||||
self._need_decimal_fix = self.dbapi and \
|
self._need_decimal_fix = self.dbapi and \
|
||||||
self._dbapi_version() < (2, 1, 8)
|
self._dbapi_version() < (2, 1, 8)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# mssql/zxjdbc.py
|
# mssql/zxjdbc.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
# mysql/__init__.py
|
# mysql/__init__.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|
||||||
from sqlalchemy.dialects.mysql import base, mysqldb, oursql, \
|
from sqlalchemy.dialects.mysql import base, mysqldb, oursql, \
|
||||||
pyodbc, zxjdbc, mysqlconnector, pymysql
|
pyodbc, zxjdbc, mysqlconnector, pymysql,\
|
||||||
|
gaerdbms
|
||||||
|
|
||||||
# default dialect
|
# default dialect
|
||||||
base.dialect = mysqldb.dialect
|
base.dialect = mysqldb.dialect
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# mysql/base.py
|
# mysql/base.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -9,29 +9,11 @@
|
|||||||
Supported Versions and Features
|
Supported Versions and Features
|
||||||
-------------------------------
|
-------------------------------
|
||||||
|
|
||||||
SQLAlchemy supports 6 major MySQL versions: 3.23, 4.0, 4.1, 5.0, 5.1 and 6.0,
|
SQLAlchemy supports MySQL starting with version 4.1 through modern releases.
|
||||||
with capabilities increasing with more modern servers.
|
However, no heroic measures are taken to work around major missing
|
||||||
|
SQL features - if your server version does not support sub-selects, for
|
||||||
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
|
|
||||||
example, they won't work in SQLAlchemy either.
|
example, they won't work in SQLAlchemy either.
|
||||||
|
|
||||||
Most available DBAPI drivers are supported; see below.
|
|
||||||
|
|
||||||
===================================== ===============
|
|
||||||
Feature Minimum Version
|
|
||||||
===================================== ===============
|
|
||||||
sqlalchemy.orm 4.1.1
|
|
||||||
Table Reflection 3.23.x
|
|
||||||
DDL Generation 4.1.1
|
|
||||||
utf8/Full Unicode Connections 4.1.1
|
|
||||||
Transactions 3.23.15
|
|
||||||
Two-Phase Transactions 5.0.3
|
|
||||||
Nested Transactions 5.0.3
|
|
||||||
===================================== ===============
|
|
||||||
|
|
||||||
See the official MySQL documentation for detailed information about features
|
See the official MySQL documentation for detailed information about features
|
||||||
supported in any given server release.
|
supported in any given server release.
|
||||||
|
|
||||||
@@ -49,13 +31,16 @@ been idle for eight hours or more. To circumvent having this issue, use the
|
|||||||
|
|
||||||
engine = create_engine('mysql+mysqldb://...', pool_recycle=3600)
|
engine = create_engine('mysql+mysqldb://...', pool_recycle=3600)
|
||||||
|
|
||||||
|
.. _mysql_storage_engines:
|
||||||
|
|
||||||
Storage Engines
|
Storage Engines
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
Most MySQL server installations have a default table type of ``MyISAM``, a
|
Most MySQL server installations have a default table type of ``MyISAM``, a
|
||||||
non-transactional table type. During a transaction, non-transactional storage
|
non-transactional table type. During a transaction, non-transactional storage
|
||||||
engines do not participate and continue to store table changes in autocommit
|
engines do not participate and continue to store table changes in autocommit
|
||||||
mode. For fully atomic transactions, all participating tables must use a
|
mode. For fully atomic transactions as well as support for foreign key
|
||||||
|
constraints, all participating tables must use a
|
||||||
transactional engine such as ``InnoDB``, ``Falcon``, ``SolidDB``, `PBXT`, etc.
|
transactional engine such as ``InnoDB``, ``Falcon``, ``SolidDB``, `PBXT`, etc.
|
||||||
|
|
||||||
Storage engines can be elected when creating tables in SQLAlchemy by supplying
|
Storage engines can be elected when creating tables in SQLAlchemy by supplying
|
||||||
@@ -68,6 +53,10 @@ creation option can be specified in this syntax::
|
|||||||
mysql_charset='utf8'
|
mysql_charset='utf8'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
|
||||||
|
`The InnoDB Storage Engine <http://dev.mysql.com/doc/refman/5.0/en/innodb-storage-engine.html>`_ - on the MySQL website.
|
||||||
|
|
||||||
Case Sensitivity and Table Reflection
|
Case Sensitivity and Table Reflection
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
|
|
||||||
@@ -99,7 +88,7 @@ every new connection. Valid values for this parameter are
|
|||||||
isolation_level="READ UNCOMMITTED"
|
isolation_level="READ UNCOMMITTED"
|
||||||
)
|
)
|
||||||
|
|
||||||
(new in 0.7.6)
|
.. versionadded:: 0.7.6
|
||||||
|
|
||||||
Keys
|
Keys
|
||||||
----
|
----
|
||||||
@@ -185,6 +174,24 @@ available.
|
|||||||
|
|
||||||
update(..., mysql_limit=10)
|
update(..., mysql_limit=10)
|
||||||
|
|
||||||
|
rowcount Support
|
||||||
|
----------------
|
||||||
|
|
||||||
|
SQLAlchemy standardizes the DBAPI ``cursor.rowcount`` attribute to be the
|
||||||
|
usual definition of "number of rows matched by an UPDATE or DELETE" statement.
|
||||||
|
This is in contradiction to the default setting on most MySQL DBAPI drivers,
|
||||||
|
which is "number of rows actually modified/deleted". For this reason, the
|
||||||
|
SQLAlchemy MySQL dialects always set the ``constants.CLIENT.FOUND_ROWS`` flag,
|
||||||
|
or whatever is equivalent for the DBAPI in use, on connect, unless the flag value
|
||||||
|
is overridden using DBAPI-specific options
|
||||||
|
(such as ``client_flag`` for the MySQL-Python driver, ``found_rows`` for the
|
||||||
|
OurSQL driver).
|
||||||
|
|
||||||
|
See also:
|
||||||
|
|
||||||
|
:attr:`.ResultProxy.rowcount`
|
||||||
|
|
||||||
|
|
||||||
CAST Support
|
CAST Support
|
||||||
------------
|
------------
|
||||||
|
|
||||||
@@ -344,9 +351,9 @@ class _FloatType(_NumericType, sqltypes.Float):
|
|||||||
(precision is None and scale is not None) or
|
(precision is None and scale is not None) or
|
||||||
(precision is not None and scale is None)
|
(precision is not None and scale is None)
|
||||||
):
|
):
|
||||||
raise exc.ArgumentError(
|
raise exc.ArgumentError(
|
||||||
"You must specify both precision and scale or omit "
|
"You must specify both precision and scale or omit "
|
||||||
"both altogether.")
|
"both altogether.")
|
||||||
|
|
||||||
super(_FloatType, self).__init__(precision=precision, asdecimal=asdecimal, **kw)
|
super(_FloatType, self).__init__(precision=precision, asdecimal=asdecimal, **kw)
|
||||||
self.scale = scale
|
self.scale = scale
|
||||||
@@ -1273,11 +1280,11 @@ class MySQLCompiler(compiler.SQLCompiler):
|
|||||||
def visit_cast(self, cast, **kwargs):
|
def visit_cast(self, cast, **kwargs):
|
||||||
# No cast until 4, no decimals until 5.
|
# No cast until 4, no decimals until 5.
|
||||||
if not self.dialect._supports_cast:
|
if not self.dialect._supports_cast:
|
||||||
return self.process(cast.clause)
|
return self.process(cast.clause.self_group())
|
||||||
|
|
||||||
type_ = self.process(cast.typeclause)
|
type_ = self.process(cast.typeclause)
|
||||||
if type_ is None:
|
if type_ is None:
|
||||||
return self.process(cast.clause)
|
return self.process(cast.clause.self_group())
|
||||||
|
|
||||||
return 'CAST(%s AS %s)' % (self.process(cast.clause), type_)
|
return 'CAST(%s AS %s)' % (self.process(cast.clause), type_)
|
||||||
|
|
||||||
@@ -1395,8 +1402,12 @@ class MySQLDDLCompiler(compiler.DDLCompiler):
|
|||||||
auto_inc_column is not list(table.primary_key)[0]:
|
auto_inc_column is not list(table.primary_key)[0]:
|
||||||
if constraint_string:
|
if constraint_string:
|
||||||
constraint_string += ", \n\t"
|
constraint_string += ", \n\t"
|
||||||
constraint_string += "KEY `idx_autoinc_%s`(`%s`)" % (auto_inc_column.name, \
|
constraint_string += "KEY %s (%s)" % (
|
||||||
self.preparer.format_column(auto_inc_column))
|
self.preparer.quote(
|
||||||
|
"idx_autoinc_%s" % auto_inc_column.name, None
|
||||||
|
),
|
||||||
|
self.preparer.format_column(auto_inc_column)
|
||||||
|
)
|
||||||
|
|
||||||
return constraint_string
|
return constraint_string
|
||||||
|
|
||||||
@@ -2017,7 +2028,6 @@ class MySQLDialect(default.DefaultDialect):
|
|||||||
|
|
||||||
@reflection.cache
|
@reflection.cache
|
||||||
def get_view_names(self, connection, schema=None, **kw):
|
def get_view_names(self, connection, schema=None, **kw):
|
||||||
charset = self._connection_charset
|
|
||||||
if self.server_version_info < (5, 0, 2):
|
if self.server_version_info < (5, 0, 2):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
if schema is None:
|
if schema is None:
|
||||||
@@ -2028,7 +2038,7 @@ class MySQLDialect(default.DefaultDialect):
|
|||||||
rp = connection.execute("SHOW FULL TABLES FROM %s" %
|
rp = connection.execute("SHOW FULL TABLES FROM %s" %
|
||||||
self.identifier_preparer.quote_identifier(schema))
|
self.identifier_preparer.quote_identifier(schema))
|
||||||
return [row[0] for row in self._compat_fetchall(rp, charset=charset)\
|
return [row[0] for row in self._compat_fetchall(rp, charset=charset)\
|
||||||
if row[1] == 'VIEW']
|
if row[1] in ('VIEW', 'SYSTEM VIEW')]
|
||||||
|
|
||||||
@reflection.cache
|
@reflection.cache
|
||||||
def get_table_options(self, connection, table_name, schema=None, **kw):
|
def get_table_options(self, connection, table_name, schema=None, **kw):
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
# mysql/gaerdbms.py
|
||||||
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
|
#
|
||||||
|
# This module is part of SQLAlchemy and is released under
|
||||||
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
"""Support for Google Cloud SQL on Google App Engine.
|
||||||
|
|
||||||
|
This dialect is based primarily on the :mod:`.mysql.mysqldb` dialect with minimal
|
||||||
|
changes.
|
||||||
|
|
||||||
|
.. versionadded:: 0.7.8
|
||||||
|
|
||||||
|
Connecting
|
||||||
|
----------
|
||||||
|
|
||||||
|
Connect string format::
|
||||||
|
|
||||||
|
mysql+gaerdbms:///<dbname>
|
||||||
|
|
||||||
|
E.g.::
|
||||||
|
|
||||||
|
create_engine('mysql+gaerdbms:///mydb',
|
||||||
|
connect_args={"instance":"instancename"})
|
||||||
|
|
||||||
|
Pooling
|
||||||
|
-------
|
||||||
|
|
||||||
|
Google App Engine connections appear to be randomly recycled,
|
||||||
|
so the dialect does not pool connections. The :class:`.NullPool`
|
||||||
|
implementation is installed within the :class:`.Engine` by
|
||||||
|
default.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sqlalchemy.dialects.mysql.mysqldb import MySQLDialect_mysqldb
|
||||||
|
from sqlalchemy.pool import NullPool
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
class MySQLDialect_gaerdbms(MySQLDialect_mysqldb):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def dbapi(cls):
|
||||||
|
# from django:
|
||||||
|
# http://code.google.com/p/googleappengine/source/
|
||||||
|
# browse/trunk/python/google/storage/speckle/
|
||||||
|
# python/django/backend/base.py#118
|
||||||
|
# see also [ticket:2649]
|
||||||
|
# see also http://stackoverflow.com/q/14224679/34549
|
||||||
|
from google.appengine.api import apiproxy_stub_map
|
||||||
|
|
||||||
|
if apiproxy_stub_map.apiproxy.GetStub('rdbms'):
|
||||||
|
from google.storage.speckle.python.api import rdbms_apiproxy
|
||||||
|
return rdbms_apiproxy
|
||||||
|
else:
|
||||||
|
from google.storage.speckle.python.api import rdbms_googleapi
|
||||||
|
return rdbms_googleapi
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_pool_class(cls, url):
|
||||||
|
# Cloud SQL connections die at any moment
|
||||||
|
return NullPool
|
||||||
|
|
||||||
|
def create_connect_args(self, url):
|
||||||
|
opts = url.translate_connect_args()
|
||||||
|
# 'dsn' and 'instance' are because we are skipping
|
||||||
|
# the traditional google.api.rdbms wrapper
|
||||||
|
|
||||||
|
opts['dsn'] = ''
|
||||||
|
opts['instance'] = url.query['instance']
|
||||||
|
return [], opts
|
||||||
|
|
||||||
|
def _extract_error_code(self, exception):
|
||||||
|
match = re.compile(r"^(\d+):").match(str(exception))
|
||||||
|
# The rdbms api will wrap then re-raise some types of errors
|
||||||
|
# making this regex return no matches.
|
||||||
|
if match:
|
||||||
|
code = match.group(1)
|
||||||
|
else:
|
||||||
|
code = None
|
||||||
|
if code:
|
||||||
|
return int(code)
|
||||||
|
|
||||||
|
dialect = MySQLDialect_gaerdbms
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
# mysql/mysqlconnector.py
|
# mysql/mysqlconnector.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -105,15 +105,11 @@ class MySQLDialect_mysqlconnector(MySQLDialect):
|
|||||||
|
|
||||||
def _get_server_version_info(self, connection):
|
def _get_server_version_info(self, connection):
|
||||||
dbapi_con = connection.connection
|
dbapi_con = connection.connection
|
||||||
|
|
||||||
from mysql.connector.constants import ClientFlag
|
|
||||||
dbapi_con.set_client_flag(ClientFlag.FOUND_ROWS)
|
|
||||||
|
|
||||||
version = dbapi_con.get_server_version()
|
version = dbapi_con.get_server_version()
|
||||||
return tuple(version)
|
return tuple(version)
|
||||||
|
|
||||||
def _detect_charset(self, connection):
|
def _detect_charset(self, connection):
|
||||||
return connection.connection.get_characterset_info()
|
return connection.connection.charset
|
||||||
|
|
||||||
def _extract_error_code(self, exception):
|
def _extract_error_code(self, exception):
|
||||||
return exception.errno
|
return exception.errno
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# mysql/mysqldb.py
|
# mysql/mysqldb.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# mysql/oursql.py
|
# mysql/oursql.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -208,6 +208,7 @@ class MySQLDialect_oursql(MySQLDialect):
|
|||||||
util.coerce_kw_type(opts, 'port', int)
|
util.coerce_kw_type(opts, 'port', int)
|
||||||
util.coerce_kw_type(opts, 'compress', bool)
|
util.coerce_kw_type(opts, 'compress', bool)
|
||||||
util.coerce_kw_type(opts, 'autoping', bool)
|
util.coerce_kw_type(opts, 'autoping', bool)
|
||||||
|
util.coerce_kw_type(opts, 'raise_on_warnings', bool)
|
||||||
|
|
||||||
util.coerce_kw_type(opts, 'default_charset', bool)
|
util.coerce_kw_type(opts, 'default_charset', bool)
|
||||||
if opts.pop('default_charset', False):
|
if opts.pop('default_charset', False):
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# mysql/pymysql.py
|
# mysql/pymysql.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# mysql/pyodbc.py
|
# mysql/pyodbc.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# mysql/zxjdbc.py
|
# mysql/zxjdbc.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# oracle/__init__.py
|
# oracle/__init__.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -12,7 +12,7 @@ from sqlalchemy.dialects.oracle.base import \
|
|||||||
VARCHAR, NVARCHAR, CHAR, DATE, DATETIME, NUMBER,\
|
VARCHAR, NVARCHAR, CHAR, DATE, DATETIME, NUMBER,\
|
||||||
BLOB, BFILE, CLOB, NCLOB, TIMESTAMP, RAW,\
|
BLOB, BFILE, CLOB, NCLOB, TIMESTAMP, RAW,\
|
||||||
FLOAT, DOUBLE_PRECISION, LONG, dialect, INTERVAL,\
|
FLOAT, DOUBLE_PRECISION, LONG, dialect, INTERVAL,\
|
||||||
VARCHAR2, NVARCHAR2
|
VARCHAR2, NVARCHAR2, ROWID
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# oracle/base.py
|
# oracle/base.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -63,10 +63,12 @@ used on the SQLAlchemy side.
|
|||||||
Unicode
|
Unicode
|
||||||
-------
|
-------
|
||||||
|
|
||||||
SQLAlchemy 0.6 uses the "native unicode" mode provided as of cx_oracle 5. cx_oracle 5.0.2
|
.. versionchanged:: 0.6
|
||||||
or greater is recommended for support of NCLOB. If not using cx_oracle 5, the NLS_LANG
|
SQLAlchemy uses the "native unicode" mode provided as of cx_oracle 5.
|
||||||
environment variable needs to be set in order for the oracle client library to use
|
cx_oracle 5.0.2 or greater is recommended for support of NCLOB.
|
||||||
proper encoding, such as "AMERICAN_AMERICA.UTF8".
|
If not using cx_oracle 5, the NLS_LANG environment variable needs
|
||||||
|
to be set in order for the oracle client library to use proper encoding,
|
||||||
|
such as "AMERICAN_AMERICA.UTF8".
|
||||||
|
|
||||||
Also note that Oracle supports unicode data through the NVARCHAR and NCLOB data types.
|
Also note that Oracle supports unicode data through the NVARCHAR and NCLOB data types.
|
||||||
When using the SQLAlchemy Unicode and UnicodeText types, these DDL types will be used
|
When using the SQLAlchemy Unicode and UnicodeText types, these DDL types will be used
|
||||||
@@ -564,7 +566,7 @@ class OracleCompiler(compiler.SQLCompiler):
|
|||||||
if not self.dialect.use_binds_for_limits:
|
if not self.dialect.use_binds_for_limits:
|
||||||
max_row = sql.literal_column("%d" % max_row)
|
max_row = sql.literal_column("%d" % max_row)
|
||||||
limitselect.append_whereclause(
|
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 needed, add the ora_rn, and wrap again with offset.
|
||||||
if select._offset is None:
|
if select._offset is None:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# oracle/cx_oracle.py
|
# oracle/cx_oracle.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -71,8 +71,40 @@ To disable this processing, pass ``auto_convert_lobs=False`` to :func:`create_en
|
|||||||
Two Phase Transaction Support
|
Two Phase Transaction Support
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
||||||
Two Phase transactions are implemented using XA transactions. Success has been reported
|
Two Phase transactions are implemented using XA transactions, and are known
|
||||||
with this feature but it should be regarded as experimental.
|
to work in a rudimental fashion with recent versions of cx_Oracle
|
||||||
|
as of SQLAlchemy 0.8.0b2, 0.7.10. However, the mechanism is not yet
|
||||||
|
considered to be robust and should still be regarded as experimental.
|
||||||
|
|
||||||
|
In particular, the cx_Oracle DBAPI as recently as 5.1.2 has a bug regarding
|
||||||
|
two phase which prevents
|
||||||
|
a particular DBAPI connection from being consistently usable in both
|
||||||
|
prepared transactions as well as traditional DBAPI usage patterns; therefore
|
||||||
|
once a particular connection is used via :meth:`.Connection.begin_prepared`,
|
||||||
|
all subsequent usages of the underlying DBAPI connection must be within
|
||||||
|
the context of prepared transactions.
|
||||||
|
|
||||||
|
The default behavior of :class:`.Engine` is to maintain a pool of DBAPI
|
||||||
|
connections. Therefore, due to the above glitch, a DBAPI connection that has
|
||||||
|
been used in a two-phase operation, and is then returned to the pool, will
|
||||||
|
not be usable in a non-two-phase context. To avoid this situation,
|
||||||
|
the application can make one of several choices:
|
||||||
|
|
||||||
|
* Disable connection pooling using :class:`.NullPool`
|
||||||
|
|
||||||
|
* Ensure that the particular :class:`.Engine` in use is only used
|
||||||
|
for two-phase operations. A :class:`.Engine` bound to an ORM
|
||||||
|
:class:`.Session` which includes ``twophase=True`` will consistently
|
||||||
|
use the two-phase transaction style.
|
||||||
|
|
||||||
|
* For ad-hoc two-phase operations without disabling pooling, the DBAPI
|
||||||
|
connection in use can be evicted from the connection pool using the
|
||||||
|
:class:`.Connection.detach` method.
|
||||||
|
|
||||||
|
.. versionchanged:: 0.8.0b2,0.7.10
|
||||||
|
Support for cx_oracle prepared transactions has been implemented
|
||||||
|
and tested.
|
||||||
|
|
||||||
|
|
||||||
Precision Numerics
|
Precision Numerics
|
||||||
------------------
|
------------------
|
||||||
@@ -98,7 +130,8 @@ to :func:`.create_engine`::
|
|||||||
engine = create_engine("oracle+cx_oracle://dsn",
|
engine = create_engine("oracle+cx_oracle://dsn",
|
||||||
coerce_to_decimal=False)
|
coerce_to_decimal=False)
|
||||||
|
|
||||||
The ``coerce_to_decimal`` flag is new in 0.7.6.
|
.. versionadded:: 0.7.6
|
||||||
|
Add the ``coerce_to_decimal`` flag.
|
||||||
|
|
||||||
Another alternative to performance is to use the
|
Another alternative to performance is to use the
|
||||||
`cdecimal <http://pypi.python.org/pypi/cdecimal/>`_ library;
|
`cdecimal <http://pypi.python.org/pypi/cdecimal/>`_ library;
|
||||||
@@ -128,21 +161,23 @@ environment variable. Upon first connection, the dialect runs a
|
|||||||
test to determine the current "decimal" character, which can be
|
test to determine the current "decimal" character, which can be
|
||||||
a comma "," for european locales. From that point forward the
|
a comma "," for european locales. From that point forward the
|
||||||
outputtypehandler uses that character to represent a decimal
|
outputtypehandler uses that character to represent a decimal
|
||||||
point (this behavior is new in version 0.6.6). Note that
|
point. Note that cx_oracle 5.0.3 or greater is required
|
||||||
cx_oracle 5.0.3 or greater is required when dealing with
|
when dealing with numerics with locale settings that don't use
|
||||||
numerics with locale settings that don't use a period "." as the
|
a period "." as the decimal character.
|
||||||
decimal character.
|
|
||||||
|
.. versionchanged:: 0.6.6
|
||||||
|
The outputtypehandler uses a comma "," character to represent
|
||||||
|
a decimal point.
|
||||||
|
|
||||||
.. _OCI: http://www.oracle.com/technetwork/database/features/oci/index.html
|
.. _OCI: http://www.oracle.com/technetwork/database/features/oci/index.html
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from sqlalchemy.dialects.oracle.base import OracleCompiler, OracleDialect, \
|
from sqlalchemy.dialects.oracle.base import OracleCompiler, OracleDialect, \
|
||||||
RESERVED_WORDS, OracleExecutionContext
|
OracleExecutionContext
|
||||||
from sqlalchemy.dialects.oracle import base as oracle
|
from sqlalchemy.dialects.oracle import base as oracle
|
||||||
from sqlalchemy.engine import base
|
from sqlalchemy.engine import base
|
||||||
from sqlalchemy import types as sqltypes, util, exc, processors
|
from sqlalchemy import types as sqltypes, util, exc, processors
|
||||||
from datetime import datetime
|
|
||||||
import random
|
import random
|
||||||
import collections
|
import collections
|
||||||
from sqlalchemy.util.compat import decimal
|
from sqlalchemy.util.compat import decimal
|
||||||
@@ -248,6 +283,13 @@ class _OracleText(_LOBMixin, sqltypes.Text):
|
|||||||
def get_dbapi_type(self, dbapi):
|
def get_dbapi_type(self, dbapi):
|
||||||
return dbapi.CLOB
|
return dbapi.CLOB
|
||||||
|
|
||||||
|
class _OracleLong(oracle.LONG):
|
||||||
|
# a raw LONG is a text type, but does *not*
|
||||||
|
# get the LobMixin with cx_oracle.
|
||||||
|
|
||||||
|
def get_dbapi_type(self, dbapi):
|
||||||
|
return dbapi.LONG_STRING
|
||||||
|
|
||||||
class _OracleString(_NativeUnicodeMixin, sqltypes.String):
|
class _OracleString(_NativeUnicodeMixin, sqltypes.String):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -296,13 +338,13 @@ class _OracleRowid(oracle.ROWID):
|
|||||||
return dbapi.ROWID
|
return dbapi.ROWID
|
||||||
|
|
||||||
class OracleCompiler_cx_oracle(OracleCompiler):
|
class OracleCompiler_cx_oracle(OracleCompiler):
|
||||||
def bindparam_string(self, name):
|
def bindparam_string(self, name, **kw):
|
||||||
if self.preparer._bindparam_requires_quotes(name):
|
if self.preparer._bindparam_requires_quotes(name):
|
||||||
quoted_name = '"%s"' % name
|
quoted_name = '"%s"' % name
|
||||||
self._quoted_bind_names[name] = quoted_name
|
self._quoted_bind_names[name] = quoted_name
|
||||||
return OracleCompiler.bindparam_string(self, quoted_name)
|
return OracleCompiler.bindparam_string(self, quoted_name, **kw)
|
||||||
else:
|
else:
|
||||||
return OracleCompiler.bindparam_string(self, name)
|
return OracleCompiler.bindparam_string(self, name, **kw)
|
||||||
|
|
||||||
|
|
||||||
class OracleExecutionContext_cx_oracle(OracleExecutionContext):
|
class OracleExecutionContext_cx_oracle(OracleExecutionContext):
|
||||||
@@ -333,7 +375,7 @@ class OracleExecutionContext_cx_oracle(OracleExecutionContext):
|
|||||||
# on String, including that outparams/RETURNING
|
# on String, including that outparams/RETURNING
|
||||||
# breaks for varchars
|
# breaks for varchars
|
||||||
self.set_input_sizes(quoted_bind_names,
|
self.set_input_sizes(quoted_bind_names,
|
||||||
exclude_types=self.dialect._cx_oracle_string_types
|
exclude_types=self.dialect._cx_oracle_exclude_setinputsizes
|
||||||
)
|
)
|
||||||
|
|
||||||
# if a single execute, check for outparams
|
# if a single execute, check for outparams
|
||||||
@@ -467,6 +509,11 @@ class OracleDialect_cx_oracle(OracleDialect):
|
|||||||
sqltypes.String : _OracleString,
|
sqltypes.String : _OracleString,
|
||||||
sqltypes.UnicodeText : _OracleUnicodeText,
|
sqltypes.UnicodeText : _OracleUnicodeText,
|
||||||
sqltypes.CHAR : _OracleChar,
|
sqltypes.CHAR : _OracleChar,
|
||||||
|
|
||||||
|
# a raw LONG is a text type, but does *not*
|
||||||
|
# get the LobMixin with cx_oracle.
|
||||||
|
oracle.LONG: _OracleLong,
|
||||||
|
|
||||||
sqltypes.Integer : _OracleInteger, # this is only needed for OUT parameters.
|
sqltypes.Integer : _OracleInteger, # this is only needed for OUT parameters.
|
||||||
# it would be nice if we could not use it otherwise.
|
# it would be nice if we could not use it otherwise.
|
||||||
oracle.RAW: _OracleRaw,
|
oracle.RAW: _OracleRaw,
|
||||||
@@ -503,6 +550,7 @@ class OracleDialect_cx_oracle(OracleDialect):
|
|||||||
getattr(self.dbapi, name, None) for name in names
|
getattr(self.dbapi, name, None) for name in names
|
||||||
]).difference([None])
|
]).difference([None])
|
||||||
|
|
||||||
|
self._cx_oracle_exclude_setinputsizes = types("STRING", "UNICODE")
|
||||||
self._cx_oracle_string_types = types("STRING", "UNICODE", "NCLOB", "CLOB")
|
self._cx_oracle_string_types = types("STRING", "UNICODE", "NCLOB", "CLOB")
|
||||||
self._cx_oracle_unicode_types = types("UNICODE", "NCLOB")
|
self._cx_oracle_unicode_types = types("UNICODE", "NCLOB")
|
||||||
self._cx_oracle_binary_types = types("BFILE", "CLOB", "NCLOB", "BLOB")
|
self._cx_oracle_binary_types = types("BFILE", "CLOB", "NCLOB", "BLOB")
|
||||||
@@ -551,13 +599,13 @@ class OracleDialect_cx_oracle(OracleDialect):
|
|||||||
# expect encoded strings or unicodes, etc.
|
# expect encoded strings or unicodes, etc.
|
||||||
self.dbapi_type_map = {
|
self.dbapi_type_map = {
|
||||||
self.dbapi.CLOB: oracle.CLOB(),
|
self.dbapi.CLOB: oracle.CLOB(),
|
||||||
self.dbapi.NCLOB:oracle.NCLOB(),
|
self.dbapi.NCLOB: oracle.NCLOB(),
|
||||||
self.dbapi.BLOB: oracle.BLOB(),
|
self.dbapi.BLOB: oracle.BLOB(),
|
||||||
self.dbapi.BINARY: oracle.RAW(),
|
self.dbapi.BINARY: oracle.RAW(),
|
||||||
}
|
}
|
||||||
@classmethod
|
@classmethod
|
||||||
def dbapi(cls):
|
def dbapi(cls):
|
||||||
import cx_Oracle
|
cx_Oracle = __import__('cx_Oracle')
|
||||||
return cx_Oracle
|
return cx_Oracle
|
||||||
|
|
||||||
def initialize(self, connection):
|
def initialize(self, connection):
|
||||||
@@ -739,15 +787,23 @@ class OracleDialect_cx_oracle(OracleDialect):
|
|||||||
connection.connection.begin(*xid)
|
connection.connection.begin(*xid)
|
||||||
|
|
||||||
def do_prepare_twophase(self, connection, xid):
|
def do_prepare_twophase(self, connection, xid):
|
||||||
connection.connection.prepare()
|
result = connection.connection.prepare()
|
||||||
|
connection.info['cx_oracle_prepared'] = result
|
||||||
|
|
||||||
def do_rollback_twophase(self, connection, xid, is_prepared=True, recover=False):
|
def do_rollback_twophase(self, connection, xid, is_prepared=True,
|
||||||
|
recover=False):
|
||||||
self.do_rollback(connection.connection)
|
self.do_rollback(connection.connection)
|
||||||
|
|
||||||
def do_commit_twophase(self, connection, xid, is_prepared=True, recover=False):
|
def do_commit_twophase(self, connection, xid, is_prepared=True,
|
||||||
self.do_commit(connection.connection)
|
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):
|
def do_recover_twophase(self, connection):
|
||||||
pass
|
connection.info.pop('cx_oracle_prepared', None)
|
||||||
|
|
||||||
dialect = OracleDialect_cx_oracle
|
dialect = OracleDialect_cx_oracle
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
# oracle/zxjdbc.py
|
# oracle/zxjdbc.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# dialects/postgres.py
|
# dialects/postgres.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# postgresql/__init__.py
|
# postgresql/__init__.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# postgresql/base.py
|
# postgresql/base.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -82,9 +82,10 @@ to inspect the actual value using::
|
|||||||
|
|
||||||
SHOW search_path;
|
SHOW search_path;
|
||||||
|
|
||||||
Prior to version 0.7.3, cross-schema foreign keys when the schemas
|
.. versionchanged:: 0.7.3
|
||||||
were also in the ``search_path`` could make an incorrect assumption
|
Prior to this version, cross-schema foreign keys when the schemas
|
||||||
if the schemas were explicitly stated on each :class:`.Table`.
|
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:
|
Background on PG's ``search_path`` is at:
|
||||||
http://www.postgresql.org/docs/9.0/static/ddl-schemas.html#DDL-SCHEMAS-PATH
|
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
|
PostgreSQL allows the specification of an *operator class* for each column of
|
||||||
an index (see http://www.postgresql.org/docs/8.3/interactive/indexes-opclass.html).
|
an index (see http://www.postgresql.org/docs/8.3/interactive/indexes-opclass.html).
|
||||||
The :class:`.Index` construct allows these to be specified via the ``postgresql_ops``
|
The :class:`.Index` construct allows these to be specified via the ``postgresql_ops``
|
||||||
keyword argument (new as of SQLAlchemy 0.7.2)::
|
keyword argument::
|
||||||
|
|
||||||
Index('my_index', my_table.c.id, my_table.c.data,
|
Index('my_index', my_table.c.id, my_table.c.data,
|
||||||
postgresql_ops={
|
postgresql_ops={
|
||||||
@@ -145,6 +146,9 @@ keyword argument (new as of SQLAlchemy 0.7.2)::
|
|||||||
'id': 'int4_ops'
|
'id': 'int4_ops'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
.. versionadded:: 0.7.2
|
||||||
|
``postgresql_ops`` keyword argument to :class:`.Index` construct.
|
||||||
|
|
||||||
Note that the keys in the ``postgresql_ops`` dictionary are the "key" name of
|
Note that the keys in the ``postgresql_ops`` dictionary are the "key" name of
|
||||||
the :class:`.Column`, i.e. the name used to access it from the ``.c`` collection
|
the :class:`.Column`, i.e. the name used to access it from the ``.c`` collection
|
||||||
of :class:`.Table`, which can be configured to be different than the actual
|
of :class:`.Table`, which can be configured to be different than the actual
|
||||||
@@ -348,21 +352,24 @@ class ARRAY(sqltypes.MutableType, sqltypes.Concatenable, sqltypes.TypeEngine):
|
|||||||
class should be considered mutable - this enables
|
class should be considered mutable - this enables
|
||||||
"mutable types" mode in the ORM. Be sure to read the
|
"mutable types" mode in the ORM. Be sure to read the
|
||||||
notes for :class:`.MutableType` regarding ORM
|
notes for :class:`.MutableType` regarding ORM
|
||||||
performance implications (default changed from ``True`` in
|
performance implications.
|
||||||
0.7.0).
|
|
||||||
|
|
||||||
.. note::
|
.. versionchanged:: 0.7.0
|
||||||
|
Default changed from ``True``\ .
|
||||||
|
|
||||||
This functionality is now superseded by the
|
.. versionchanged:: 0.7
|
||||||
``sqlalchemy.ext.mutable`` extension described in
|
This functionality is now superseded by the
|
||||||
:ref:`mutable_toplevel`.
|
``sqlalchemy.ext.mutable`` extension described in
|
||||||
|
:ref:`mutable_toplevel`.
|
||||||
|
|
||||||
:param as_tuple=False: Specify whether return results
|
:param as_tuple=False: Specify whether return results
|
||||||
should be converted to tuples from lists. DBAPIs such
|
should be converted to tuples from lists. DBAPIs such
|
||||||
as psycopg2 return lists by default. When tuples are
|
as psycopg2 return lists by default. When tuples are
|
||||||
returned, the results are hashable. This flag can only
|
returned, the results are hashable. This flag can only
|
||||||
be set to ``True`` when ``mutable`` is set to
|
be set to ``True`` when ``mutable`` is set to
|
||||||
``False``. (new in 0.6.5)
|
``False``.
|
||||||
|
|
||||||
|
.. versionadded:: 0.6.5
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if isinstance(item_type, ARRAY):
|
if isinstance(item_type, ARRAY):
|
||||||
@@ -489,7 +496,8 @@ class ENUM(sqltypes.Enum):
|
|||||||
the :meth:`~.postgresql.ENUM.create` and
|
the :meth:`~.postgresql.ENUM.create` and
|
||||||
:meth:`~.postgresql.ENUM.drop` methods can
|
:meth:`~.postgresql.ENUM.drop` methods can
|
||||||
be used to emit SQL to a target bind.
|
be used to emit SQL to a target bind.
|
||||||
(new in 0.7.4)
|
|
||||||
|
.. versionadded:: 0.7.4
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.create_type = kw.pop("create_type", True)
|
self.create_type = kw.pop("create_type", True)
|
||||||
@@ -675,6 +683,10 @@ class PGCompiler(compiler.SQLCompiler):
|
|||||||
def for_update_clause(self, select):
|
def for_update_clause(self, select):
|
||||||
if select.for_update == 'nowait':
|
if select.for_update == 'nowait':
|
||||||
return " FOR UPDATE NOWAIT"
|
return " FOR UPDATE NOWAIT"
|
||||||
|
elif select.for_update == 'read':
|
||||||
|
return " FOR SHARE"
|
||||||
|
elif select.for_update == 'read_nowait':
|
||||||
|
return " FOR SHARE NOWAIT"
|
||||||
else:
|
else:
|
||||||
return super(PGCompiler, self).for_update_clause(select)
|
return super(PGCompiler, self).for_update_clause(select)
|
||||||
|
|
||||||
@@ -1040,7 +1052,7 @@ class PGDialect(default.DefaultDialect):
|
|||||||
if is_prepared:
|
if is_prepared:
|
||||||
if recover:
|
if recover:
|
||||||
#FIXME: ugly hack to get out of transaction
|
#FIXME: ugly hack to get out of transaction
|
||||||
# context when commiting recoverable transactions
|
# context when committing recoverable transactions
|
||||||
# Must find out a way how to make the dbapi not
|
# Must find out a way how to make the dbapi not
|
||||||
# open a transaction.
|
# open a transaction.
|
||||||
connection.execute("ROLLBACK")
|
connection.execute("ROLLBACK")
|
||||||
@@ -1439,20 +1451,33 @@ class PGDialect(default.DefaultDialect):
|
|||||||
table_oid = self.get_table_oid(connection, table_name, schema,
|
table_oid = self.get_table_oid(connection, table_name, schema,
|
||||||
info_cache=kw.get('info_cache'))
|
info_cache=kw.get('info_cache'))
|
||||||
|
|
||||||
PK_SQL = """
|
if self.server_version_info < (8, 4):
|
||||||
SELECT a.attname
|
# unnest() and generate_subscripts() both introduced in
|
||||||
|
# version 8.4
|
||||||
|
PK_SQL = """
|
||||||
|
SELECT a.attname
|
||||||
FROM
|
FROM
|
||||||
pg_class t
|
pg_class t
|
||||||
join pg_index ix on t.oid = ix.indrelid
|
join pg_index ix on t.oid = ix.indrelid
|
||||||
join pg_attribute a
|
join pg_attribute a
|
||||||
on t.oid=a.attrelid and a.attnum=ANY(ix.indkey)
|
on t.oid=a.attrelid and a.attnum=ANY(ix.indkey)
|
||||||
WHERE
|
WHERE
|
||||||
t.oid = :table_oid and
|
t.oid = :table_oid and ix.indisprimary = 't'
|
||||||
ix.indisprimary = 't'
|
ORDER BY a.attnum
|
||||||
ORDER BY
|
"""
|
||||||
a.attnum
|
else:
|
||||||
"""
|
PK_SQL = """
|
||||||
t = sql.text(PK_SQL, typemap={'attname':sqltypes.Unicode})
|
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)
|
c = connection.execute(t, table_oid=table_oid)
|
||||||
primary_keys = [r[0] for r in c.fetchall()]
|
primary_keys = [r[0] for r in c.fetchall()]
|
||||||
return primary_keys
|
return primary_keys
|
||||||
@@ -1610,7 +1635,6 @@ class PGDialect(default.DefaultDialect):
|
|||||||
e.enumlabel as "label"
|
e.enumlabel as "label"
|
||||||
FROM pg_catalog.pg_type t
|
FROM pg_catalog.pg_type t
|
||||||
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
|
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
|
||||||
LEFT JOIN pg_catalog.pg_constraint r ON t.oid = r.contypid
|
|
||||||
LEFT JOIN pg_catalog.pg_enum e ON t.oid = e.enumtypid
|
LEFT JOIN pg_catalog.pg_enum e ON t.oid = e.enumtypid
|
||||||
WHERE t.typtype = 'e'
|
WHERE t.typtype = 'e'
|
||||||
ORDER BY "name", e.oid -- e.oid gives us label order
|
ORDER BY "name", e.oid -- e.oid gives us label order
|
||||||
@@ -1625,8 +1649,8 @@ class PGDialect(default.DefaultDialect):
|
|||||||
for enum in c.fetchall():
|
for enum in c.fetchall():
|
||||||
if enum['visible']:
|
if enum['visible']:
|
||||||
# 'visible' just means whether or not the enum is in a
|
# 'visible' just means whether or not the enum is in a
|
||||||
# schema that's on the search path -- or not overriden by
|
# schema that's on the search path -- or not overridden by
|
||||||
# a schema with higher presedence. If it's not visible,
|
# a schema with higher precedence. If it's not visible,
|
||||||
# it will be prefixed with the schema-name when it's used.
|
# it will be prefixed with the schema-name when it's used.
|
||||||
name = enum['name']
|
name = enum['name']
|
||||||
else:
|
else:
|
||||||
@@ -1652,7 +1676,6 @@ class PGDialect(default.DefaultDialect):
|
|||||||
n.nspname as "schema"
|
n.nspname as "schema"
|
||||||
FROM pg_catalog.pg_type t
|
FROM pg_catalog.pg_type t
|
||||||
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
|
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
|
||||||
LEFT JOIN pg_catalog.pg_constraint r ON t.oid = r.contypid
|
|
||||||
WHERE t.typtype = 'd'
|
WHERE t.typtype = 'd'
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -1665,8 +1688,8 @@ class PGDialect(default.DefaultDialect):
|
|||||||
attype = re.search('([^\(]+)', domain['attype']).group(1)
|
attype = re.search('([^\(]+)', domain['attype']).group(1)
|
||||||
if domain['visible']:
|
if domain['visible']:
|
||||||
# 'visible' just means whether or not the domain is in a
|
# 'visible' just means whether or not the domain is in a
|
||||||
# schema that's on the search path -- or not overriden by
|
# schema that's on the search path -- or not overridden by
|
||||||
# a schema with higher presedence. If it's not visible,
|
# a schema with higher precedence. If it's not visible,
|
||||||
# it will be prefixed with the schema-name when it's used.
|
# it will be prefixed with the schema-name when it's used.
|
||||||
name = domain['name']
|
name = domain['name']
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# postgresql/pg8000.py
|
# postgresql/pg8000.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# postgresql/psycopg2.py
|
# postgresql/psycopg2.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -96,8 +96,9 @@ on all new connections based on the value passed to
|
|||||||
engine = create_engine("postgresql://user:pass@host/dbname", client_encoding='utf8')
|
engine = create_engine("postgresql://user:pass@host/dbname", client_encoding='utf8')
|
||||||
|
|
||||||
This overrides the encoding specified in the Postgresql client configuration.
|
This overrides the encoding specified in the Postgresql client configuration.
|
||||||
The psycopg2-specific ``client_encoding`` parameter to :func:`.create_engine` is new as of
|
|
||||||
SQLAlchemy 0.7.3.
|
.. versionadded:: 0.7.3
|
||||||
|
The psycopg2-specific ``client_encoding`` parameter to :func:`.create_engine`.
|
||||||
|
|
||||||
SQLAlchemy can also be instructed to skip the usage of the psycopg2
|
SQLAlchemy can also be instructed to skip the usage of the psycopg2
|
||||||
``UNICODE`` extension and to instead utilize it's own unicode encode/decode
|
``UNICODE`` extension and to instead utilize it's own unicode encode/decode
|
||||||
@@ -371,7 +372,8 @@ class PGDialect_psycopg2(PGDialect):
|
|||||||
# these error messages from libpq: interfaces/libpq/fe-misc.c.
|
# these error messages from libpq: interfaces/libpq/fe-misc.c.
|
||||||
# TODO: these are sent through gettext in libpq and we can't
|
# TODO: these are sent through gettext in libpq and we can't
|
||||||
# check within other locales - consider using connection.closed
|
# check within other locales - consider using connection.closed
|
||||||
return 'closed the connection' in str(e) or \
|
return 'terminating connection' in str(e) or \
|
||||||
|
'closed the connection' in str(e) or \
|
||||||
'connection not open' in str(e) or \
|
'connection not open' in str(e) or \
|
||||||
'could not receive data from server' in str(e)
|
'could not receive data from server' in str(e)
|
||||||
elif isinstance(e, self.dbapi.InterfaceError):
|
elif isinstance(e, self.dbapi.InterfaceError):
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# postgresql/pypostgresql.py
|
# postgresql/pypostgresql.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -40,7 +40,7 @@ class PGDialect_pypostgresql(PGDialect):
|
|||||||
default_paramstyle = 'pyformat'
|
default_paramstyle = 'pyformat'
|
||||||
|
|
||||||
# requires trunk version to support sane rowcounts
|
# requires trunk version to support sane rowcounts
|
||||||
# TODO: use dbapi version information to set this flag appropariately
|
# TODO: use dbapi version information to set this flag appropriately
|
||||||
supports_sane_rowcount = True
|
supports_sane_rowcount = True
|
||||||
supports_sane_multi_rowcount = False
|
supports_sane_multi_rowcount = False
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# postgresql/zxjdbc.py
|
# postgresql/zxjdbc.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# sqlite/__init__.py
|
# sqlite/__init__.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# sqlite/base.py
|
# sqlite/base.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -53,6 +53,74 @@ connection. Valid values for this parameter are ``SERIALIZABLE`` and
|
|||||||
See the section :ref:`pysqlite_serializable` for an important workaround
|
See the section :ref:`pysqlite_serializable` for an important workaround
|
||||||
when using serializable isolation with Pysqlite.
|
when using serializable isolation with Pysqlite.
|
||||||
|
|
||||||
|
Database Locking Behavior / Concurrency
|
||||||
|
---------------------------------------
|
||||||
|
|
||||||
|
Note that SQLite is not designed for a high level of concurrency. The database
|
||||||
|
itself, being a file, is locked completely during write operations and within
|
||||||
|
transactions, meaning exactly one connection has exclusive access to the database
|
||||||
|
during this period - all other connections will be blocked during this time.
|
||||||
|
|
||||||
|
The Python DBAPI specification also calls for a connection model that is always
|
||||||
|
in a transaction; there is no BEGIN method, only commit and rollback. This implies
|
||||||
|
that a SQLite DBAPI driver would technically allow only serialized access to a
|
||||||
|
particular database file at all times. The pysqlite driver attempts to ameliorate this by
|
||||||
|
deferring the actual BEGIN statement until the first DML (INSERT, UPDATE, or
|
||||||
|
DELETE) is received within a transaction. While this breaks serializable isolation,
|
||||||
|
it at least delays the exclusive locking inherent in SQLite's design.
|
||||||
|
|
||||||
|
SQLAlchemy's default mode of usage with the ORM is known
|
||||||
|
as "autocommit=False", which means the moment the :class:`.Session` begins to be
|
||||||
|
used, a transaction is begun. As the :class:`.Session` is used, the autoflush
|
||||||
|
feature, also on by default, will flush out pending changes to the database
|
||||||
|
before each query. The effect of this is that a :class:`.Session` used in its
|
||||||
|
default mode will often emit DML early on, long before the transaction is actually
|
||||||
|
committed. This again will have the effect of serializing access to the SQLite
|
||||||
|
database. If highly concurrent reads are desired against the SQLite database,
|
||||||
|
it is advised that the autoflush feature be disabled, and potentially even
|
||||||
|
that autocommit be re-enabled, which has the effect of each SQL statement and
|
||||||
|
flush committing changes immediately.
|
||||||
|
|
||||||
|
For more information on SQLite's lack of concurrency by design, please
|
||||||
|
see `Situations Where Another RDBMS May Work Better - High Concurrency <http://www.sqlite.org/whentouse.html>`_
|
||||||
|
near the bottom of the page.
|
||||||
|
|
||||||
|
.. _sqlite_foreign_keys:
|
||||||
|
|
||||||
|
Foreign Key Support
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
SQLite supports FOREIGN KEY syntax when emitting CREATE statements for tables,
|
||||||
|
however by default these constraints have no effect on the operation
|
||||||
|
of the table.
|
||||||
|
|
||||||
|
Constraint checking on SQLite has three prerequisites:
|
||||||
|
|
||||||
|
* At least version 3.6.19 of SQLite must be in use
|
||||||
|
* The SQLite libary must be compiled *without* the SQLITE_OMIT_FOREIGN_KEY
|
||||||
|
or SQLITE_OMIT_TRIGGER symbols enabled.
|
||||||
|
* The ``PRAGMA foreign_keys = ON`` statement must be emitted on all connections
|
||||||
|
before use.
|
||||||
|
|
||||||
|
SQLAlchemy allows for the ``PRAGMA`` statement to be emitted automatically
|
||||||
|
for new connections through the usage of events::
|
||||||
|
|
||||||
|
from sqlalchemy.engine import Engine
|
||||||
|
from sqlalchemy import event
|
||||||
|
|
||||||
|
@event.listens_for(Engine, "connect")
|
||||||
|
def set_sqlite_pragma(dbapi_connection, connection_record):
|
||||||
|
cursor = dbapi_connection.cursor()
|
||||||
|
cursor.execute("PRAGMA foreign_keys=ON")
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
|
||||||
|
`SQLite Foreign Key Support <http://www.sqlite.org/foreignkeys.html>`_ -
|
||||||
|
on the SQLite web site.
|
||||||
|
|
||||||
|
:ref:`event_toplevel` - SQLAlchemy event API.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import datetime, re
|
import datetime, re
|
||||||
@@ -103,13 +171,13 @@ class DATETIME(_DateTimeMixin, sqltypes.DateTime):
|
|||||||
regexp=re.compile("(\d+)/(\d+)/(\d+) (\d+)-(\d+)-(\d+)(?:-(\d+))?")
|
regexp=re.compile("(\d+)/(\d+)/(\d+) (\d+)-(\d+)-(\d+)(?:-(\d+))?")
|
||||||
)
|
)
|
||||||
|
|
||||||
:param storage_format: format string which will be appled to the
|
:param storage_format: format string which will be applied to the
|
||||||
tuple ``(value.year, value.month, value.day, value.hour,
|
tuple ``(value.year, value.month, value.day, value.hour,
|
||||||
value.minute, value.second, value.microsecond)``, given a
|
value.minute, value.second, value.microsecond)``, given a
|
||||||
Python datetime.datetime() object.
|
Python datetime.datetime() object.
|
||||||
|
|
||||||
:param regexp: regular expression which will be applied to
|
:param regexp: regular expression which will be applied to
|
||||||
incoming result rows. The resulting match object is appled to
|
incoming result rows. The resulting match object is applied to
|
||||||
the Python datetime() constructor via ``*map(int,
|
the Python datetime() constructor via ``*map(int,
|
||||||
match_obj.groups(0))``.
|
match_obj.groups(0))``.
|
||||||
"""
|
"""
|
||||||
@@ -164,12 +232,12 @@ class DATE(_DateTimeMixin, sqltypes.Date):
|
|||||||
regexp=re.compile("(\d+)/(\d+)/(\d+)")
|
regexp=re.compile("(\d+)/(\d+)/(\d+)")
|
||||||
)
|
)
|
||||||
|
|
||||||
:param storage_format: format string which will be appled to the
|
:param storage_format: format string which will be applied to the
|
||||||
tuple ``(value.year, value.month, value.day)``,
|
tuple ``(value.year, value.month, value.day)``,
|
||||||
given a Python datetime.date() object.
|
given a Python datetime.date() object.
|
||||||
|
|
||||||
:param regexp: regular expression which will be applied to
|
:param regexp: regular expression which will be applied to
|
||||||
incoming result rows. The resulting match object is appled to
|
incoming result rows. The resulting match object is applied to
|
||||||
the Python date() constructor via ``*map(int,
|
the Python date() constructor via ``*map(int,
|
||||||
match_obj.groups(0))``.
|
match_obj.groups(0))``.
|
||||||
|
|
||||||
@@ -221,12 +289,12 @@ class TIME(_DateTimeMixin, sqltypes.Time):
|
|||||||
regexp=re.compile("(\d+)-(\d+)-(\d+)-(?:-(\d+))?")
|
regexp=re.compile("(\d+)-(\d+)-(\d+)-(?:-(\d+))?")
|
||||||
)
|
)
|
||||||
|
|
||||||
:param storage_format: format string which will be appled
|
:param storage_format: format string which will be applied
|
||||||
to the tuple ``(value.hour, value.minute, value.second,
|
to the tuple ``(value.hour, value.minute, value.second,
|
||||||
value.microsecond)``, given a Python datetime.time() object.
|
value.microsecond)``, given a Python datetime.time() object.
|
||||||
|
|
||||||
:param regexp: regular expression which will be applied to
|
:param regexp: regular expression which will be applied to
|
||||||
incoming result rows. The resulting match object is appled to
|
incoming result rows. The resulting match object is applied to
|
||||||
the Python time() constructor via ``*map(int,
|
the Python time() constructor via ``*map(int,
|
||||||
match_obj.groups(0))``.
|
match_obj.groups(0))``.
|
||||||
|
|
||||||
@@ -302,6 +370,9 @@ class SQLiteCompiler(compiler.SQLCompiler):
|
|||||||
def visit_now_func(self, fn, **kw):
|
def visit_now_func(self, fn, **kw):
|
||||||
return "CURRENT_TIMESTAMP"
|
return "CURRENT_TIMESTAMP"
|
||||||
|
|
||||||
|
def visit_localtimestamp_func(self, func, **kw):
|
||||||
|
return 'DATETIME(CURRENT_TIMESTAMP, "localtime")'
|
||||||
|
|
||||||
def visit_true(self, expr, **kw):
|
def visit_true(self, expr, **kw):
|
||||||
return '1'
|
return '1'
|
||||||
|
|
||||||
@@ -441,6 +512,22 @@ class SQLiteIdentifierPreparer(compiler.IdentifierPreparer):
|
|||||||
result = self.quote_schema(index.table.schema, index.table.quote_schema) + "." + result
|
result = self.quote_schema(index.table.schema, index.table.quote_schema) + "." + result
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
class SQLiteExecutionContext(default.DefaultExecutionContext):
|
||||||
|
@util.memoized_property
|
||||||
|
def _preserve_raw_colnames(self):
|
||||||
|
return self.execution_options.get("sqlite_raw_colnames", False)
|
||||||
|
|
||||||
|
def _translate_colname(self, colname):
|
||||||
|
# adjust for dotted column names. SQLite
|
||||||
|
# in the case of UNION may store col names as
|
||||||
|
# "tablename.colname"
|
||||||
|
# in cursor.description
|
||||||
|
if not self._preserve_raw_colnames and "." in colname:
|
||||||
|
return colname.split(".")[1], colname
|
||||||
|
else:
|
||||||
|
return colname, None
|
||||||
|
|
||||||
|
|
||||||
class SQLiteDialect(default.DefaultDialect):
|
class SQLiteDialect(default.DefaultDialect):
|
||||||
name = 'sqlite'
|
name = 'sqlite'
|
||||||
supports_alter = False
|
supports_alter = False
|
||||||
@@ -451,6 +538,7 @@ class SQLiteDialect(default.DefaultDialect):
|
|||||||
supports_cast = True
|
supports_cast = True
|
||||||
|
|
||||||
default_paramstyle = 'qmark'
|
default_paramstyle = 'qmark'
|
||||||
|
execution_ctx_cls = SQLiteExecutionContext
|
||||||
statement_compiler = SQLiteCompiler
|
statement_compiler = SQLiteCompiler
|
||||||
ddl_compiler = SQLiteDDLCompiler
|
ddl_compiler = SQLiteDDLCompiler
|
||||||
type_compiler = SQLiteTypeCompiler
|
type_compiler = SQLiteTypeCompiler
|
||||||
@@ -462,6 +550,8 @@ class SQLiteDialect(default.DefaultDialect):
|
|||||||
supports_cast = True
|
supports_cast = True
|
||||||
supports_default_values = True
|
supports_default_values = True
|
||||||
|
|
||||||
|
_broken_fk_pragma_quotes = False
|
||||||
|
|
||||||
def __init__(self, isolation_level=None, native_datetime=False, **kwargs):
|
def __init__(self, isolation_level=None, native_datetime=False, **kwargs):
|
||||||
default.DefaultDialect.__init__(self, **kwargs)
|
default.DefaultDialect.__init__(self, **kwargs)
|
||||||
self.isolation_level = isolation_level
|
self.isolation_level = isolation_level
|
||||||
@@ -478,6 +568,12 @@ class SQLiteDialect(default.DefaultDialect):
|
|||||||
self.supports_cast = \
|
self.supports_cast = \
|
||||||
self.dbapi.sqlite_version_info >= (3, 2, 3)
|
self.dbapi.sqlite_version_info >= (3, 2, 3)
|
||||||
|
|
||||||
|
# see http://www.sqlalchemy.org/trac/ticket/2568
|
||||||
|
# as well as http://www.sqlite.org/src/info/600482d161
|
||||||
|
self._broken_fk_pragma_quotes = \
|
||||||
|
self.dbapi.sqlite_version_info < (3, 6, 14)
|
||||||
|
|
||||||
|
|
||||||
_isolation_lookup = {
|
_isolation_lookup = {
|
||||||
'READ UNCOMMITTED':1,
|
'READ UNCOMMITTED':1,
|
||||||
'SERIALIZABLE':0
|
'SERIALIZABLE':0
|
||||||
@@ -525,16 +621,6 @@ class SQLiteDialect(default.DefaultDialect):
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _translate_colname(self, colname):
|
|
||||||
# adjust for dotted column names. SQLite
|
|
||||||
# in the case of UNION may store col names as
|
|
||||||
# "tablename.colname"
|
|
||||||
# in cursor.description
|
|
||||||
if "." in colname:
|
|
||||||
return colname.split(".")[1], colname
|
|
||||||
else:
|
|
||||||
return colname, None
|
|
||||||
|
|
||||||
@reflection.cache
|
@reflection.cache
|
||||||
def get_table_names(self, connection, schema=None, **kw):
|
def get_table_names(self, connection, schema=None, **kw):
|
||||||
if schema is not None:
|
if schema is not None:
|
||||||
@@ -633,43 +719,50 @@ class SQLiteDialect(default.DefaultDialect):
|
|||||||
c = _pragma_cursor(
|
c = _pragma_cursor(
|
||||||
connection.execute("%stable_info(%s)" %
|
connection.execute("%stable_info(%s)" %
|
||||||
(pragma, qtable)))
|
(pragma, qtable)))
|
||||||
found_table = False
|
|
||||||
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({
|
rows = c.fetchall()
|
||||||
'name' : name,
|
columns = []
|
||||||
'type' : coltype,
|
for row in rows:
|
||||||
'nullable' : nullable,
|
(name, type_, nullable, default, primary_key) = \
|
||||||
'default' : default,
|
(row[1], row[2].upper(), not row[3],
|
||||||
'autoincrement':default is None,
|
row[4], row[5])
|
||||||
'primary_key': primary_key
|
|
||||||
})
|
columns.append(self._get_column_info(name, type_, nullable,
|
||||||
|
default, primary_key))
|
||||||
return columns
|
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
|
@reflection.cache
|
||||||
def get_primary_keys(self, connection, table_name, schema=None, **kw):
|
def get_primary_keys(self, connection, table_name, schema=None, **kw):
|
||||||
cols = self.get_columns(connection, table_name, schema, **kw)
|
cols = self.get_columns(connection, table_name, schema, **kw)
|
||||||
@@ -687,7 +780,8 @@ class SQLiteDialect(default.DefaultDialect):
|
|||||||
else:
|
else:
|
||||||
pragma = "PRAGMA "
|
pragma = "PRAGMA "
|
||||||
qtable = quote(table_name)
|
qtable = quote(table_name)
|
||||||
c = _pragma_cursor(connection.execute("%sforeign_key_list(%s)" % (pragma, qtable)))
|
statement = "%sforeign_key_list(%s)" % (pragma, qtable)
|
||||||
|
c = _pragma_cursor(connection.execute(statement))
|
||||||
fkeys = []
|
fkeys = []
|
||||||
fks = {}
|
fks = {}
|
||||||
while True:
|
while True:
|
||||||
@@ -695,34 +789,38 @@ class SQLiteDialect(default.DefaultDialect):
|
|||||||
if row is None:
|
if row is None:
|
||||||
break
|
break
|
||||||
(numerical_id, rtbl, lcol, rcol) = (row[0], row[2], row[3], row[4])
|
(numerical_id, rtbl, lcol, rcol) = (row[0], row[2], row[3], row[4])
|
||||||
# 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',
|
self._parse_fk(fks, fkeys, numerical_id, rtbl, lcol, rcol)
|
||||||
# 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)
|
|
||||||
return fkeys
|
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
|
@reflection.cache
|
||||||
def get_indexes(self, connection, table_name, schema=None, **kw):
|
def get_indexes(self, connection, table_name, schema=None, **kw):
|
||||||
quote = self.identifier_preparer.quote_identifier
|
quote = self.identifier_preparer.quote_identifier
|
||||||
@@ -762,4 +860,5 @@ def _pragma_cursor(cursor):
|
|||||||
|
|
||||||
if cursor.closed:
|
if cursor.closed:
|
||||||
cursor.fetchone = lambda: None
|
cursor.fetchone = lambda: None
|
||||||
|
cursor.fetchall = lambda: []
|
||||||
return cursor
|
return cursor
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
# sqlite/pysqlite.py
|
# sqlite/pysqlite.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -97,13 +97,21 @@ Threading/Pooling Behavior
|
|||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
Pysqlite's default behavior is to prohibit the usage of a single connection
|
Pysqlite's default behavior is to prohibit the usage of a single connection
|
||||||
in more than one thread. This is controlled by the ``check_same_thread``
|
in more than one thread. This is originally intended to work with older versions
|
||||||
Pysqlite flag. This default is intended to work with older versions
|
|
||||||
of SQLite that did not support multithreaded operation under
|
of SQLite that did not support multithreaded operation under
|
||||||
various circumstances. In particular, older SQLite versions
|
various circumstances. In particular, older SQLite versions
|
||||||
did not allow a ``:memory:`` database to be used in multiple threads
|
did not allow a ``:memory:`` database to be used in multiple threads
|
||||||
under any circumstances.
|
under any circumstances.
|
||||||
|
|
||||||
|
Pysqlite does include a now-undocumented flag known as
|
||||||
|
``check_same_thread`` which will disable this check, however note that pysqlite
|
||||||
|
connections are still not safe to use in concurrently in multiple threads.
|
||||||
|
In particular, any statement execution calls would need to be externally
|
||||||
|
mutexed, as Pysqlite does not provide for thread-safe propagation of error
|
||||||
|
messages among other things. So while even ``:memory:`` databases can be
|
||||||
|
shared among threads in modern SQLite, Pysqlite doesn't provide enough
|
||||||
|
thread-safety to make this usage worth it.
|
||||||
|
|
||||||
SQLAlchemy sets up pooling to work with Pysqlite's default behavior:
|
SQLAlchemy sets up pooling to work with Pysqlite's default behavior:
|
||||||
|
|
||||||
* When a ``:memory:`` SQLite database is specified, the dialect by default will use
|
* When a ``:memory:`` SQLite database is specified, the dialect by default will use
|
||||||
@@ -118,16 +126,11 @@ SQLAlchemy sets up pooling to work with Pysqlite's default behavior:
|
|||||||
prevents a connection from being used again in a different thread and works
|
prevents a connection from being used again in a different thread and works
|
||||||
best with SQLite's coarse-grained file locking.
|
best with SQLite's coarse-grained file locking.
|
||||||
|
|
||||||
.. note::
|
.. versionchanged:: 0.7
|
||||||
|
Default selection of :class:`.NullPool` for SQLite file-based databases.
|
||||||
|
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
|
Using a Memory Database in Multiple Threads
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# sybase/__init__.py
|
# sybase/__init__.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -10,8 +10,12 @@
|
|||||||
|
|
||||||
"""Support for Sybase Adaptive Server Enterprise (ASE).
|
"""Support for Sybase Adaptive Server Enterprise (ASE).
|
||||||
|
|
||||||
Note that this dialect is no longer specific to Sybase iAnywhere.
|
.. note::
|
||||||
ASE is the primary support platform.
|
|
||||||
|
The Sybase dialect functions on current SQLAlchemy versions
|
||||||
|
but is not regularly tested, and may have many issues and
|
||||||
|
caveats not currently handled. In particular, the table
|
||||||
|
and database reflection features are not implemented.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# sybase/mxodbc.py
|
# sybase/mxodbc.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# sybase/pyodbc.py
|
# sybase/pyodbc.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class SybaseExecutionContext_pysybase(SybaseExecutionContext):
|
|||||||
|
|
||||||
|
|
||||||
class SybaseSQLCompiler_pysybase(SybaseSQLCompiler):
|
class SybaseSQLCompiler_pysybase(SybaseSQLCompiler):
|
||||||
def bindparam_string(self, name):
|
def bindparam_string(self, name, **kw):
|
||||||
return "@" + name
|
return "@" + name
|
||||||
|
|
||||||
class SybaseDialect_pysybase(SybaseDialect):
|
class SybaseDialect_pysybase(SybaseDialect):
|
||||||
@@ -82,10 +82,10 @@ class SybaseDialect_pysybase(SybaseDialect):
|
|||||||
cursor.execute(statement, param)
|
cursor.execute(statement, param)
|
||||||
|
|
||||||
def _get_server_version_info(self, connection):
|
def _get_server_version_info(self, connection):
|
||||||
vers = connection.scalar("select @@version_number")
|
vers = connection.scalar("select @@version_number")
|
||||||
# i.e. 15500, 15000, 12500 == (15, 5, 0, 0), (15, 0, 0, 0),
|
# i.e. 15500, 15000, 12500 == (15, 5, 0, 0), (15, 0, 0, 0),
|
||||||
# (12, 5, 0, 0)
|
# (12, 5, 0, 0)
|
||||||
return (vers / 1000, vers % 1000 / 100, vers % 100 / 10, vers % 10)
|
return (vers / 1000, vers % 1000 / 100, vers % 100 / 10, vers % 10)
|
||||||
|
|
||||||
def is_disconnect(self, e, connection, cursor):
|
def is_disconnect(self, e, connection, cursor):
|
||||||
if isinstance(e, (self.dbapi.OperationalError,
|
if isinstance(e, (self.dbapi.OperationalError,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# engine/__init__.py
|
# engine/__init__.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -310,15 +310,26 @@ def create_engine(*args, **kwargs):
|
|||||||
behavior of the pool, which is whether ``rollback()``,
|
behavior of the pool, which is whether ``rollback()``,
|
||||||
``commit()``, or nothing is called upon connections
|
``commit()``, or nothing is called upon connections
|
||||||
being returned to the pool. See the docstring for
|
being returned to the pool. See the docstring for
|
||||||
``reset_on_return`` at :class:`.Pool`. (new as of 0.7.6)
|
``reset_on_return`` at :class:`.Pool`.
|
||||||
|
|
||||||
|
.. versionadded:: 0.7.6
|
||||||
|
|
||||||
:param pool_timeout=30: number of seconds to wait before giving
|
:param pool_timeout=30: number of seconds to wait before giving
|
||||||
up on getting a connection from the pool. This is only used
|
up on getting a connection from the pool. This is only used
|
||||||
with :class:`~sqlalchemy.pool.QueuePool`.
|
with :class:`~sqlalchemy.pool.QueuePool`.
|
||||||
|
|
||||||
:param strategy='plain': selects alternate engine implementations.
|
:param strategy='plain': selects alternate engine implementations.
|
||||||
Currently available is the ``threadlocal``
|
Currently available are:
|
||||||
strategy, which is described in :ref:`threadlocal_strategy`.
|
|
||||||
|
* the ``threadlocal`` strategy, which is described in
|
||||||
|
:ref:`threadlocal_strategy`;
|
||||||
|
* the ``mock`` strategy, which dispatches all statement
|
||||||
|
execution to a function passed as the argument ``executor``.
|
||||||
|
See `example in the FAQ <http://www.sqlalchemy.org/trac/wiki/FAQ#HowcanIgettheCREATETABLEDROPTABLEoutputasastring>`_.
|
||||||
|
|
||||||
|
:param executor=None: a function taking arguments
|
||||||
|
``(sql, *multiparams, **params)``, to which the ``mock`` strategy will
|
||||||
|
dispatch all statement execution. Used only by ``strategy='mock'``.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# engine/base.py
|
# engine/base.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -663,13 +663,10 @@ class ExecutionContext(object):
|
|||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def get_rowcount(self):
|
def get_rowcount(self):
|
||||||
"""Return the number of rows produced (by a SELECT query)
|
"""Return the DBAPI ``cursor.rowcount`` value, or in some
|
||||||
or affected (by an INSERT/UPDATE/DELETE statement).
|
cases an interpreted value.
|
||||||
|
|
||||||
Note that this row count may not be properly implemented
|
See :attr:`.ResultProxy.rowcount` for details on this.
|
||||||
in some dialects; this is indicated by the
|
|
||||||
``supports_sane_rowcount`` and ``supports_sane_multi_rowcount``
|
|
||||||
dialect attributes.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -734,7 +731,7 @@ class Compiled(object):
|
|||||||
def construct_params(self, params=None):
|
def construct_params(self, params=None):
|
||||||
"""Return the bind params for this compiled object.
|
"""Return the bind params for this compiled object.
|
||||||
|
|
||||||
:param params: a dict of string/object pairs whos values will
|
:param params: a dict of string/object pairs whose values will
|
||||||
override bind values compiled in to the
|
override bind values compiled in to the
|
||||||
statement.
|
statement.
|
||||||
"""
|
"""
|
||||||
@@ -978,10 +975,10 @@ class Connection(Connectable):
|
|||||||
:ref:`postgresql_toplevel` - see those dialect's documentation
|
:ref:`postgresql_toplevel` - see those dialect's documentation
|
||||||
for further info.
|
for further info.
|
||||||
|
|
||||||
Note that this option necessarily affects the underying
|
Note that this option necessarily affects the underlying
|
||||||
DBAPI connection for the lifespan of the originating
|
DBAPI connection for the lifespan of the originating
|
||||||
:class:`.Connection`, and is not per-execution. This
|
:class:`.Connection`, and is not per-execution. This
|
||||||
setting is not removed until the underying DBAPI connection
|
setting is not removed until the underlying DBAPI connection
|
||||||
is returned to the connection pool, i.e.
|
is returned to the connection pool, i.e.
|
||||||
the :meth:`.Connection.close` method is called.
|
the :meth:`.Connection.close` method is called.
|
||||||
|
|
||||||
@@ -995,7 +992,9 @@ class Connection(Connectable):
|
|||||||
containing percent signs (and possibly other characters)
|
containing percent signs (and possibly other characters)
|
||||||
that is neutral regarding whether it's executed by the DBAPI
|
that is neutral regarding whether it's executed by the DBAPI
|
||||||
or piped into a script that's later invoked by
|
or piped into a script that's later invoked by
|
||||||
command line tools. New in 0.7.6.
|
command line tools.
|
||||||
|
|
||||||
|
.. versionadded:: 0.7.6
|
||||||
|
|
||||||
:param stream_results: Available on: Connection, statement.
|
:param stream_results: Available on: Connection, statement.
|
||||||
Indicate to the dialect that results should be
|
Indicate to the dialect that results should be
|
||||||
@@ -1074,7 +1073,7 @@ class Connection(Connectable):
|
|||||||
"""Returns self.
|
"""Returns self.
|
||||||
|
|
||||||
This ``Connectable`` interface method returns self, allowing
|
This ``Connectable`` interface method returns self, allowing
|
||||||
Connections to be used interchangably with Engines in most
|
Connections to be used interchangeably with Engines in most
|
||||||
situations that require a bind.
|
situations that require a bind.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -1084,7 +1083,7 @@ class Connection(Connectable):
|
|||||||
"""Returns self.
|
"""Returns self.
|
||||||
|
|
||||||
This ``Connectable`` interface method returns self, allowing
|
This ``Connectable`` interface method returns self, allowing
|
||||||
Connections to be used interchangably with Engines in most
|
Connections to be used interchangeably with Engines in most
|
||||||
situations that require a bind.
|
situations that require a bind.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -1123,7 +1122,7 @@ class Connection(Connectable):
|
|||||||
def detach(self):
|
def detach(self):
|
||||||
"""Detach the underlying DB-API connection from its connection pool.
|
"""Detach the underlying DB-API connection from its connection pool.
|
||||||
|
|
||||||
This Connection instance will remain useable. When closed,
|
This Connection instance will remain usable. When closed,
|
||||||
the DB-API connection will be literally closed and not
|
the DB-API connection will be literally closed and not
|
||||||
returned to its pool. The pool will typically lazily create a
|
returned to its pool. The pool will typically lazily create a
|
||||||
new connection to replace the detached connection.
|
new connection to replace the detached connection.
|
||||||
@@ -1471,7 +1470,8 @@ class Connection(Connectable):
|
|||||||
elif len(multiparams) == 1:
|
elif len(multiparams) == 1:
|
||||||
zero = multiparams[0]
|
zero = multiparams[0]
|
||||||
if isinstance(zero, (list, tuple)):
|
if isinstance(zero, (list, tuple)):
|
||||||
if not zero or hasattr(zero[0], '__iter__'):
|
if not zero or hasattr(zero[0], '__iter__') and \
|
||||||
|
not hasattr(zero[0], 'strip'):
|
||||||
return zero
|
return zero
|
||||||
else:
|
else:
|
||||||
return [zero]
|
return [zero]
|
||||||
@@ -1480,7 +1480,8 @@ class Connection(Connectable):
|
|||||||
else:
|
else:
|
||||||
return [[zero]]
|
return [[zero]]
|
||||||
else:
|
else:
|
||||||
if hasattr(multiparams[0], '__iter__'):
|
if hasattr(multiparams[0], '__iter__') and \
|
||||||
|
not hasattr(multiparams[0], 'strip'):
|
||||||
return multiparams
|
return multiparams
|
||||||
else:
|
else:
|
||||||
return [multiparams]
|
return [multiparams]
|
||||||
@@ -1714,7 +1715,6 @@ class Connection(Connectable):
|
|||||||
# create a resultproxy, get rowcount/implicit RETURNING
|
# create a resultproxy, get rowcount/implicit RETURNING
|
||||||
# rows, close cursor if no further results pending
|
# rows, close cursor if no further results pending
|
||||||
result = context.get_result_proxy()
|
result = context.get_result_proxy()
|
||||||
|
|
||||||
if context.isinsert:
|
if context.isinsert:
|
||||||
if context._is_implicit_returning:
|
if context._is_implicit_returning:
|
||||||
context._fetch_implicit_returning(result)
|
context._fetch_implicit_returning(result)
|
||||||
@@ -1806,10 +1806,19 @@ class Connection(Connectable):
|
|||||||
(statement is not None and context is None)
|
(statement is not None and context is None)
|
||||||
|
|
||||||
if should_wrap and context:
|
if should_wrap and context:
|
||||||
|
if self._has_events:
|
||||||
|
self.engine.dispatch.dbapi_error(self,
|
||||||
|
cursor,
|
||||||
|
statement,
|
||||||
|
parameters,
|
||||||
|
context,
|
||||||
|
e)
|
||||||
context.handle_dbapi_exception(e)
|
context.handle_dbapi_exception(e)
|
||||||
|
|
||||||
is_disconnect = isinstance(e, self.dialect.dbapi.Error) and \
|
is_disconnect = isinstance(e, self.dialect.dbapi.Error) and \
|
||||||
self.dialect.is_disconnect(e, self.__connection, cursor)
|
self.dialect.is_disconnect(e, self.__connection, cursor)
|
||||||
|
|
||||||
|
|
||||||
if is_disconnect:
|
if is_disconnect:
|
||||||
self.invalidate(e)
|
self.invalidate(e)
|
||||||
self.engine.dispose()
|
self.engine.dispose()
|
||||||
@@ -2130,7 +2139,7 @@ class Engine(Connectable, log.Identified):
|
|||||||
:class:`~sqlalchemy.engine.base.Dialect` together to provide a source
|
:class:`~sqlalchemy.engine.base.Dialect` together to provide a source
|
||||||
of database connectivity and behavior.
|
of database connectivity and behavior.
|
||||||
|
|
||||||
An :class:`.Engine` object is instantiated publically using the
|
An :class:`.Engine` object is instantiated publicly using the
|
||||||
:func:`~sqlalchemy.create_engine` function.
|
:func:`~sqlalchemy.create_engine` function.
|
||||||
|
|
||||||
See also:
|
See also:
|
||||||
@@ -2178,7 +2187,7 @@ class Engine(Connectable, log.Identified):
|
|||||||
The given keys/values in \**opt are added to the
|
The given keys/values in \**opt are added to the
|
||||||
default execution options that will be used for
|
default execution options that will be used for
|
||||||
all connections. The initial contents of this dictionary
|
all connections. The initial contents of this dictionary
|
||||||
can be sent via the ``execution_options`` paramter
|
can be sent via the ``execution_options`` parameter
|
||||||
to :func:`.create_engine`.
|
to :func:`.create_engine`.
|
||||||
|
|
||||||
See :meth:`.Connection.execution_options` for more
|
See :meth:`.Connection.execution_options` for more
|
||||||
@@ -2232,7 +2241,7 @@ class Engine(Connectable, log.Identified):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
self.pool.dispose()
|
self.pool.dispose()
|
||||||
self.pool = self.pool.recreate()
|
self.pool = self.pool._replace()
|
||||||
|
|
||||||
@util.deprecated("0.7", "Use the create() method on the given schema "
|
@util.deprecated("0.7", "Use the create() method on the given schema "
|
||||||
"object directly, i.e. :meth:`.Table.create`, "
|
"object directly, i.e. :meth:`.Table.create`, "
|
||||||
@@ -2334,7 +2343,7 @@ class Engine(Connectable, log.Identified):
|
|||||||
close the :class:`.Connection` when that :class:`.ResultProxy`
|
close the :class:`.Connection` when that :class:`.ResultProxy`
|
||||||
has exhausted all result rows.
|
has exhausted all result rows.
|
||||||
|
|
||||||
New in 0.7.6.
|
.. versionadded:: 0.7.6
|
||||||
|
|
||||||
See also:
|
See also:
|
||||||
|
|
||||||
@@ -2346,7 +2355,11 @@ class Engine(Connectable, log.Identified):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
conn = self.contextual_connect(close_with_result=close_with_result)
|
conn = self.contextual_connect(close_with_result=close_with_result)
|
||||||
trans = conn.begin()
|
try:
|
||||||
|
trans = conn.begin()
|
||||||
|
except:
|
||||||
|
conn.close()
|
||||||
|
raise
|
||||||
return Engine._trans_ctx(conn, trans, close_with_result)
|
return Engine._trans_ctx(conn, trans, close_with_result)
|
||||||
|
|
||||||
def transaction(self, callable_, *args, **kwargs):
|
def transaction(self, callable_, *args, **kwargs):
|
||||||
@@ -2710,7 +2723,7 @@ class ResultMetaData(object):
|
|||||||
context = parent.context
|
context = parent.context
|
||||||
dialect = context.dialect
|
dialect = context.dialect
|
||||||
typemap = dialect.dbapi_type_map
|
typemap = dialect.dbapi_type_map
|
||||||
translate_colname = dialect._translate_colname
|
translate_colname = context._translate_colname
|
||||||
|
|
||||||
# high precedence key values.
|
# high precedence key values.
|
||||||
primary_keymap = {}
|
primary_keymap = {}
|
||||||
@@ -2907,18 +2920,41 @@ class ResultProxy(object):
|
|||||||
def rowcount(self):
|
def rowcount(self):
|
||||||
"""Return the 'rowcount' for this result.
|
"""Return the 'rowcount' for this result.
|
||||||
|
|
||||||
The 'rowcount' reports the number of rows affected
|
The 'rowcount' reports the number of rows *matched*
|
||||||
by an UPDATE or DELETE statement. It has *no* other
|
by the WHERE criterion of an UPDATE or DELETE statement.
|
||||||
uses and is not intended to provide the number of rows
|
|
||||||
present from a SELECT.
|
|
||||||
|
|
||||||
Note that this row count may not be properly implemented in some
|
.. note::
|
||||||
dialects; this is indicated by
|
|
||||||
:meth:`~sqlalchemy.engine.base.ResultProxy.supports_sane_rowcount()`
|
Notes regarding :attr:`.ResultProxy.rowcount`:
|
||||||
and
|
|
||||||
:meth:`~sqlalchemy.engine.base.ResultProxy.supports_sane_multi_rowcount()`.
|
|
||||||
``rowcount()`` also may not work at this time for a statement that
|
* This attribute returns the number of rows *matched*,
|
||||||
uses ``returning()``.
|
which is not necessarily the same as the number of rows
|
||||||
|
that were actually *modified* - an UPDATE statement, for example,
|
||||||
|
may have no net change on a given row if the SET values
|
||||||
|
given are the same as those present in the row already.
|
||||||
|
Such a row would be matched but not modified.
|
||||||
|
On backends that feature both styles, such as MySQL,
|
||||||
|
rowcount is configured by default to return the match
|
||||||
|
count in all cases.
|
||||||
|
|
||||||
|
* :attr:`.ResultProxy.rowcount` is *only* useful in conjunction
|
||||||
|
with an UPDATE or DELETE statement. Contrary to what the Python
|
||||||
|
DBAPI says, it does *not* return the
|
||||||
|
number of rows available from the results of a SELECT statement
|
||||||
|
as DBAPIs cannot support this functionality when rows are
|
||||||
|
unbuffered.
|
||||||
|
|
||||||
|
* :attr:`.ResultProxy.rowcount` may not be fully implemented by
|
||||||
|
all dialects. In particular, most DBAPIs do not support an
|
||||||
|
aggregate rowcount result from an executemany call.
|
||||||
|
The :meth:`.ResultProxy.supports_sane_rowcount` and
|
||||||
|
:meth:`.ResultProxy.supports_sane_multi_rowcount` methods
|
||||||
|
will report from the dialect if each usage is known to be
|
||||||
|
supported.
|
||||||
|
|
||||||
|
* Statements that use RETURNING may not return a correct
|
||||||
|
rowcount.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
@@ -3098,12 +3134,20 @@ class ResultProxy(object):
|
|||||||
return self.context.prefetch_cols
|
return self.context.prefetch_cols
|
||||||
|
|
||||||
def supports_sane_rowcount(self):
|
def supports_sane_rowcount(self):
|
||||||
"""Return ``supports_sane_rowcount`` from the dialect."""
|
"""Return ``supports_sane_rowcount`` from the dialect.
|
||||||
|
|
||||||
|
See :attr:`.ResultProxy.rowcount` for background.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
return self.dialect.supports_sane_rowcount
|
return self.dialect.supports_sane_rowcount
|
||||||
|
|
||||||
def supports_sane_multi_rowcount(self):
|
def supports_sane_multi_rowcount(self):
|
||||||
"""Return ``supports_sane_multi_rowcount`` from the dialect."""
|
"""Return ``supports_sane_multi_rowcount`` from the dialect.
|
||||||
|
|
||||||
|
See :attr:`.ResultProxy.rowcount` for background.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
return self.dialect.supports_sane_multi_rowcount
|
return self.dialect.supports_sane_multi_rowcount
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# engine/default.py
|
# engine/default.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -96,10 +96,6 @@ class DefaultDialect(base.Dialect):
|
|||||||
# and denormalize_name() must be provided.
|
# and denormalize_name() must be provided.
|
||||||
requires_name_normalize = False
|
requires_name_normalize = False
|
||||||
|
|
||||||
# a hook for SQLite's translation of
|
|
||||||
# result column names
|
|
||||||
_translate_colname = None
|
|
||||||
|
|
||||||
reflection_options = ()
|
reflection_options = ()
|
||||||
|
|
||||||
def __init__(self, convert_unicode=False, assert_unicode=False,
|
def __init__(self, convert_unicode=False, assert_unicode=False,
|
||||||
@@ -264,7 +260,7 @@ class DefaultDialect(base.Dialect):
|
|||||||
return insp.reflecttable(table, include_columns, exclude_columns)
|
return insp.reflecttable(table, include_columns, exclude_columns)
|
||||||
|
|
||||||
def get_pk_constraint(self, conn, table_name, schema=None, **kw):
|
def get_pk_constraint(self, conn, table_name, schema=None, **kw):
|
||||||
"""Compatiblity method, adapts the result of get_primary_keys()
|
"""Compatibility method, adapts the result of get_primary_keys()
|
||||||
for those dialects which don't implement get_pk_constraint().
|
for those dialects which don't implement get_pk_constraint().
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -341,7 +337,7 @@ class DefaultDialect(base.Dialect):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def reset_isolation_level(self, dbapi_conn):
|
def reset_isolation_level(self, dbapi_conn):
|
||||||
# default_isolation_level is read from the first conenction
|
# default_isolation_level is read from the first connection
|
||||||
# after the initial set of 'isolation_level', if any, so is
|
# after the initial set of 'isolation_level', if any, so is
|
||||||
# the configured default of this dialect.
|
# the configured default of this dialect.
|
||||||
self.set_isolation_level(dbapi_conn, self.default_isolation_level)
|
self.set_isolation_level(dbapi_conn, self.default_isolation_level)
|
||||||
@@ -355,9 +351,15 @@ class DefaultExecutionContext(base.ExecutionContext):
|
|||||||
result_map = None
|
result_map = None
|
||||||
compiled = None
|
compiled = None
|
||||||
statement = None
|
statement = None
|
||||||
|
postfetch_cols = None
|
||||||
|
prefetch_cols = None
|
||||||
_is_implicit_returning = False
|
_is_implicit_returning = False
|
||||||
_is_explicit_returning = False
|
_is_explicit_returning = False
|
||||||
|
|
||||||
|
# a hook for SQLite's translation of
|
||||||
|
# result column names
|
||||||
|
_translate_colname = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _init_ddl(cls, dialect, connection, dbapi_connection, compiled_ddl):
|
def _init_ddl(cls, dialect, connection, dbapi_connection, compiled_ddl):
|
||||||
"""Initialize execution context for a DDLElement construct."""
|
"""Initialize execution context for a DDLElement construct."""
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# engine/reflection.py
|
# engine/reflection.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -379,7 +379,7 @@ class Inspector(object):
|
|||||||
|
|
||||||
coltype = col_d['type']
|
coltype = col_d['type']
|
||||||
col_kw = {
|
col_kw = {
|
||||||
'nullable':col_d['nullable'],
|
'nullable': col_d['nullable'],
|
||||||
}
|
}
|
||||||
for k in ('autoincrement', 'quote', 'info', 'key'):
|
for k in ('autoincrement', 'quote', 'info', 'key'):
|
||||||
if k in col_d:
|
if k in col_d:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# engine/strategies.py
|
# engine/strategies.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -24,7 +24,7 @@ strategies = {}
|
|||||||
|
|
||||||
|
|
||||||
class EngineStrategy(object):
|
class EngineStrategy(object):
|
||||||
"""An adaptor that processes input arguements and produces an Engine.
|
"""An adaptor that processes input arguments and produces an Engine.
|
||||||
|
|
||||||
Provides a ``create`` method that receives input arguments and
|
Provides a ``create`` method that receives input arguments and
|
||||||
produces an instance of base.Engine or a subclass.
|
produces an instance of base.Engine or a subclass.
|
||||||
@@ -41,7 +41,7 @@ class EngineStrategy(object):
|
|||||||
|
|
||||||
|
|
||||||
class DefaultEngineStrategy(EngineStrategy):
|
class DefaultEngineStrategy(EngineStrategy):
|
||||||
"""Base class for built-in stratgies."""
|
"""Base class for built-in strategies."""
|
||||||
|
|
||||||
def create(self, name_or_url, **kwargs):
|
def create(self, name_or_url, **kwargs):
|
||||||
# create url.URL object
|
# create url.URL object
|
||||||
@@ -180,7 +180,7 @@ PlainEngineStrategy()
|
|||||||
|
|
||||||
|
|
||||||
class ThreadLocalEngineStrategy(DefaultEngineStrategy):
|
class ThreadLocalEngineStrategy(DefaultEngineStrategy):
|
||||||
"""Strategy for configuring an Engine with thredlocal behavior."""
|
"""Strategy for configuring an Engine with threadlocal behavior."""
|
||||||
|
|
||||||
name = 'threadlocal'
|
name = 'threadlocal'
|
||||||
engine_cls = threadlocal.TLEngine
|
engine_cls = threadlocal.TLEngine
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# engine/threadlocal.py
|
# engine/threadlocal.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# engine/url.py
|
# engine/url.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
+111
-15
@@ -1,5 +1,5 @@
|
|||||||
# sqlalchemy/event.py
|
# sqlalchemy/event.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
"""Base event API."""
|
"""Base event API."""
|
||||||
|
|
||||||
from sqlalchemy import util, exc
|
from sqlalchemy import util, exc
|
||||||
|
import weakref
|
||||||
|
|
||||||
CANCEL = util.symbol('CANCEL')
|
CANCEL = util.symbol('CANCEL')
|
||||||
NO_RETVAL = util.symbol('NO_RETVAL')
|
NO_RETVAL = util.symbol('NO_RETVAL')
|
||||||
@@ -37,7 +38,7 @@ def listen(target, identifier, fn, *args, **kw):
|
|||||||
tgt.dispatch._listen(tgt, identifier, fn, *args, **kw)
|
tgt.dispatch._listen(tgt, identifier, fn, *args, **kw)
|
||||||
return
|
return
|
||||||
raise exc.InvalidRequestError("No such event '%s' for target '%s'" %
|
raise exc.InvalidRequestError("No such event '%s' for target '%s'" %
|
||||||
(identifier,target))
|
(identifier, target))
|
||||||
|
|
||||||
def listens_for(target, identifier, *args, **kw):
|
def listens_for(target, identifier, *args, **kw):
|
||||||
"""Decorate a function as a listener for the given target + identifier.
|
"""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 evt_cls in _registrars[identifier]:
|
||||||
for tgt in evt_cls._accept_with(target):
|
for tgt in evt_cls._accept_with(target):
|
||||||
tgt.dispatch._remove(identifier, tgt, fn, *args, **kw)
|
tgt.dispatch._remove(identifier, tgt, fn)
|
||||||
return
|
return
|
||||||
|
|
||||||
_registrars = util.defaultdict(list)
|
_registrars = util.defaultdict(list)
|
||||||
@@ -120,7 +121,8 @@ class _Dispatch(object):
|
|||||||
object."""
|
object."""
|
||||||
|
|
||||||
for ls in _event_descriptors(other):
|
for ls in _event_descriptors(other):
|
||||||
getattr(self, ls.name)._update(ls, only_propagate=only_propagate)
|
getattr(self, ls.name).\
|
||||||
|
for_modify(self)._update(ls, only_propagate=only_propagate)
|
||||||
|
|
||||||
def _event_descriptors(target):
|
def _event_descriptors(target):
|
||||||
return [getattr(target, k) for k in dir(target) if _is_event_name(k)]
|
return [getattr(target, k) for k in dir(target) if _is_event_name(k)]
|
||||||
@@ -180,9 +182,11 @@ class Events(object):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def _listen(cls, target, identifier, fn, propagate=False, insert=False):
|
def _listen(cls, target, identifier, fn, propagate=False, insert=False):
|
||||||
if insert:
|
if insert:
|
||||||
getattr(target.dispatch, identifier).insert(fn, target, propagate)
|
getattr(target.dispatch, identifier).\
|
||||||
|
for_modify(target.dispatch).insert(fn, target, propagate)
|
||||||
else:
|
else:
|
||||||
getattr(target.dispatch, identifier).append(fn, target, propagate)
|
getattr(target.dispatch, identifier).\
|
||||||
|
for_modify(target.dispatch).append(fn, target, propagate)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _remove(cls, target, identifier, fn):
|
def _remove(cls, target, identifier, fn):
|
||||||
@@ -200,7 +204,12 @@ class _DispatchDescriptor(object):
|
|||||||
def __init__(self, fn):
|
def __init__(self, fn):
|
||||||
self.__name__ = fn.__name__
|
self.__name__ = fn.__name__
|
||||||
self.__doc__ = fn.__doc__
|
self.__doc__ = fn.__doc__
|
||||||
self._clslevel = util.defaultdict(list)
|
self._clslevel = weakref.WeakKeyDictionary()
|
||||||
|
self._empty_listeners = weakref.WeakKeyDictionary()
|
||||||
|
|
||||||
|
def _contains(self, cls, evt):
|
||||||
|
return cls in self._clslevel and \
|
||||||
|
evt in self._clslevel[cls]
|
||||||
|
|
||||||
def insert(self, obj, target, propagate):
|
def insert(self, obj, target, propagate):
|
||||||
assert isinstance(target, type), \
|
assert isinstance(target, type), \
|
||||||
@@ -212,6 +221,8 @@ class _DispatchDescriptor(object):
|
|||||||
if cls is not target and cls not in self._clslevel:
|
if cls is not target and cls not in self._clslevel:
|
||||||
self.update_subclass(cls)
|
self.update_subclass(cls)
|
||||||
else:
|
else:
|
||||||
|
if cls not in self._clslevel:
|
||||||
|
self._clslevel[cls] = []
|
||||||
self._clslevel[cls].insert(0, obj)
|
self._clslevel[cls].insert(0, obj)
|
||||||
|
|
||||||
def append(self, obj, target, propagate):
|
def append(self, obj, target, propagate):
|
||||||
@@ -225,9 +236,13 @@ class _DispatchDescriptor(object):
|
|||||||
if cls is not target and cls not in self._clslevel:
|
if cls is not target and cls not in self._clslevel:
|
||||||
self.update_subclass(cls)
|
self.update_subclass(cls)
|
||||||
else:
|
else:
|
||||||
|
if cls not in self._clslevel:
|
||||||
|
self._clslevel[cls] = []
|
||||||
self._clslevel[cls].append(obj)
|
self._clslevel[cls].append(obj)
|
||||||
|
|
||||||
def update_subclass(self, target):
|
def update_subclass(self, target):
|
||||||
|
if target not in self._clslevel:
|
||||||
|
self._clslevel[target] = []
|
||||||
clslevel = self._clslevel[target]
|
clslevel = self._clslevel[target]
|
||||||
for cls in target.__mro__[1:]:
|
for cls in target.__mro__[1:]:
|
||||||
if cls in self._clslevel:
|
if cls in self._clslevel:
|
||||||
@@ -242,7 +257,8 @@ class _DispatchDescriptor(object):
|
|||||||
while stack:
|
while stack:
|
||||||
cls = stack.pop(0)
|
cls = stack.pop(0)
|
||||||
stack.extend(cls.__subclasses__())
|
stack.extend(cls.__subclasses__())
|
||||||
self._clslevel[cls].remove(obj)
|
if cls in self._clslevel:
|
||||||
|
self._clslevel[cls].remove(obj)
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
"""Clear all class level listeners"""
|
"""Clear all class level listeners"""
|
||||||
@@ -250,18 +266,91 @@ class _DispatchDescriptor(object):
|
|||||||
for dispatcher in self._clslevel.values():
|
for dispatcher in self._clslevel.values():
|
||||||
dispatcher[:] = []
|
dispatcher[:] = []
|
||||||
|
|
||||||
|
def for_modify(self, obj):
|
||||||
|
"""Return an event collection which can be modified.
|
||||||
|
|
||||||
|
For _DispatchDescriptor at the class level of
|
||||||
|
a dispatcher, this returns self.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self
|
||||||
|
|
||||||
def __get__(self, obj, cls):
|
def __get__(self, obj, cls):
|
||||||
if obj is None:
|
if obj is None:
|
||||||
return self
|
return self
|
||||||
obj.__dict__[self.__name__] = result = \
|
elif obj._parent_cls in self._empty_listeners:
|
||||||
_ListenerCollection(self, obj._parent_cls)
|
ret = self._empty_listeners[obj._parent_cls]
|
||||||
|
else:
|
||||||
|
self._empty_listeners[obj._parent_cls] = ret = \
|
||||||
|
_EmptyListener(self, obj._parent_cls)
|
||||||
|
# assigning it to __dict__ means
|
||||||
|
# memoized for fast re-access. but more memory.
|
||||||
|
obj.__dict__[self.__name__] = ret
|
||||||
|
return ret
|
||||||
|
|
||||||
|
class _EmptyListener(object):
|
||||||
|
"""Serves as a class-level interface to the events
|
||||||
|
served by a _DispatchDescriptor, when there are no
|
||||||
|
instance-level events present.
|
||||||
|
|
||||||
|
Is replaced by _ListenerCollection when instance-level
|
||||||
|
events are added.
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __init__(self, parent, target_cls):
|
||||||
|
if target_cls not in parent._clslevel:
|
||||||
|
parent.update_subclass(target_cls)
|
||||||
|
self.parent = parent
|
||||||
|
self.parent_listeners = parent._clslevel[target_cls]
|
||||||
|
self.name = parent.__name__
|
||||||
|
self.propagate = frozenset()
|
||||||
|
self.listeners = ()
|
||||||
|
|
||||||
|
def for_modify(self, obj):
|
||||||
|
"""Return an event collection which can be modified.
|
||||||
|
|
||||||
|
For _EmptyListener at the instance level of
|
||||||
|
a dispatcher, this generates a new
|
||||||
|
_ListenerCollection, applies it to the instance,
|
||||||
|
and returns it.
|
||||||
|
|
||||||
|
"""
|
||||||
|
obj.__dict__[self.name] = result = _ListenerCollection(
|
||||||
|
self.parent, obj._parent_cls)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def _needs_modify(self, *args, **kw):
|
||||||
|
raise NotImplementedError("need to call for_modify()")
|
||||||
|
|
||||||
|
exec_once = insert = append = remove = clear = _needs_modify
|
||||||
|
|
||||||
|
def __call__(self, *args, **kw):
|
||||||
|
"""Execute this event."""
|
||||||
|
|
||||||
|
for fn in self.parent_listeners:
|
||||||
|
fn(*args, **kw)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.parent_listeners)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self.parent_listeners)
|
||||||
|
|
||||||
|
def __getitem__(self, index):
|
||||||
|
return (self.parent_listeners)[index]
|
||||||
|
|
||||||
|
def __nonzero__(self):
|
||||||
|
return bool(self.parent_listeners)
|
||||||
|
|
||||||
|
|
||||||
class _ListenerCollection(object):
|
class _ListenerCollection(object):
|
||||||
"""Instance-level attributes on instances of :class:`._Dispatch`.
|
"""Instance-level attributes on instances of :class:`._Dispatch`.
|
||||||
|
|
||||||
Represents a collection of listeners.
|
Represents a collection of listeners.
|
||||||
|
|
||||||
|
As of 0.7.9, _ListenerCollection is only first
|
||||||
|
created via the _EmptyListener.for_modify() method.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_exec_once = False
|
_exec_once = False
|
||||||
@@ -274,6 +363,15 @@ class _ListenerCollection(object):
|
|||||||
self.listeners = []
|
self.listeners = []
|
||||||
self.propagate = set()
|
self.propagate = set()
|
||||||
|
|
||||||
|
def for_modify(self, obj):
|
||||||
|
"""Return an event collection which can be modified.
|
||||||
|
|
||||||
|
For _ListenerCollection at the instance level of
|
||||||
|
a dispatcher, this returns self.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self
|
||||||
|
|
||||||
def exec_once(self, *args, **kw):
|
def exec_once(self, *args, **kw):
|
||||||
"""Execute this event, but only if it has not been
|
"""Execute this event, but only if it has not been
|
||||||
executed already for this collection."""
|
executed already for this collection."""
|
||||||
@@ -294,11 +392,9 @@ class _ListenerCollection(object):
|
|||||||
# but this allows class-level listeners to be added
|
# but this allows class-level listeners to be added
|
||||||
# at any point.
|
# at any point.
|
||||||
#
|
#
|
||||||
# alternatively, _DispatchDescriptor could notify
|
# In the absense of instance-level listeners,
|
||||||
# all _ListenerCollection objects, but then we move
|
# we stay with the _EmptyListener object when called
|
||||||
# to a higher memory model, i.e.weakrefs to all _ListenerCollection
|
# at the instance level.
|
||||||
# objects, the _DispatchDescriptor collection repeated
|
|
||||||
# for all instances.
|
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
return len(self.parent_listeners + self.listeners)
|
return len(self.parent_listeners + self.listeners)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# sqlalchemy/events.py
|
# sqlalchemy/events.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -401,6 +401,36 @@ class ConnectionEvents(event.Events):
|
|||||||
parameters, context, executemany):
|
parameters, context, executemany):
|
||||||
"""Intercept low-level cursor execute() events."""
|
"""Intercept low-level cursor execute() events."""
|
||||||
|
|
||||||
|
def dbapi_error(self, conn, cursor, statement, parameters,
|
||||||
|
context, exception):
|
||||||
|
"""Intercept a raw DBAPI error.
|
||||||
|
|
||||||
|
This event is called with the DBAPI exception instance
|
||||||
|
received from the DBAPI itself, *before* SQLAlchemy wraps the
|
||||||
|
exception with it's own exception wrappers, and before any
|
||||||
|
other operations are performed on the DBAPI cursor; the
|
||||||
|
existing transaction remains in effect as well as any state
|
||||||
|
on the cursor.
|
||||||
|
|
||||||
|
The use case here is to inject low-level exception handling
|
||||||
|
into an :class:`.Engine`, typically for logging and
|
||||||
|
debugging purposes. In general, user code should **not** modify
|
||||||
|
any state or throw any exceptions here as this will
|
||||||
|
interfere with SQLAlchemy's cleanup and error handling
|
||||||
|
routines.
|
||||||
|
|
||||||
|
Subsequent to this hook, SQLAlchemy may attempt any
|
||||||
|
number of operations on the connection/cursor, including
|
||||||
|
closing the cursor, rolling back of the transaction in the
|
||||||
|
case of connectionless execution, and disposing of the entire
|
||||||
|
connection pool if a "disconnect" was detected. The
|
||||||
|
exception is then wrapped in a SQLAlchemy DBAPI exception
|
||||||
|
wrapper and re-thrown.
|
||||||
|
|
||||||
|
.. versionadded:: 0.7.7
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
def begin(self, conn):
|
def begin(self, conn):
|
||||||
"""Intercept begin() events."""
|
"""Intercept begin() events."""
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# sqlalchemy/exc.py
|
# sqlalchemy/exc.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# ext/__init__.py
|
# ext/__init__.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# ext/associationproxy.py
|
# ext/associationproxy.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -141,7 +141,7 @@ class AssociationProxy(object):
|
|||||||
"""The 'remote' :class:`.MapperProperty` referenced by this
|
"""The 'remote' :class:`.MapperProperty` referenced by this
|
||||||
:class:`.AssociationProxy`.
|
:class:`.AssociationProxy`.
|
||||||
|
|
||||||
New in 0.7.3.
|
.. versionadded:: 0.7.3
|
||||||
|
|
||||||
See also:
|
See also:
|
||||||
|
|
||||||
@@ -157,7 +157,7 @@ class AssociationProxy(object):
|
|||||||
"""The 'local' :class:`.MapperProperty` referenced by this
|
"""The 'local' :class:`.MapperProperty` referenced by this
|
||||||
:class:`.AssociationProxy`.
|
:class:`.AssociationProxy`.
|
||||||
|
|
||||||
New in 0.7.3.
|
.. versionadded:: 0.7.3
|
||||||
|
|
||||||
See also:
|
See also:
|
||||||
|
|
||||||
@@ -177,7 +177,7 @@ class AssociationProxy(object):
|
|||||||
|
|
||||||
sess.query(Parent).join(*Parent.proxied.attr)
|
sess.query(Parent).join(*Parent.proxied.attr)
|
||||||
|
|
||||||
New in 0.7.3.
|
.. versionadded:: 0.7.3
|
||||||
|
|
||||||
See also:
|
See also:
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# ext/compiler.py
|
# ext/compiler.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# ext/declarative.py
|
# ext/declarative.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -86,16 +86,6 @@ CREATE statements for all tables::
|
|||||||
engine = create_engine('sqlite://')
|
engine = create_engine('sqlite://')
|
||||||
Base.metadata.create_all(engine)
|
Base.metadata.create_all(engine)
|
||||||
|
|
||||||
The usual techniques of associating :class:`.MetaData:` with :class:`.Engine`
|
|
||||||
apply, such as assigning to the ``bind`` attribute::
|
|
||||||
|
|
||||||
Base.metadata.bind = create_engine('sqlite://')
|
|
||||||
|
|
||||||
To associate the engine with the :func:`declarative_base` at time
|
|
||||||
of construction, the ``bind`` argument is accepted::
|
|
||||||
|
|
||||||
Base = declarative_base(bind=create_engine('sqlite://'))
|
|
||||||
|
|
||||||
:func:`declarative_base` can also receive a pre-existing
|
:func:`declarative_base` can also receive a pre-existing
|
||||||
:class:`.MetaData` object, which allows a
|
:class:`.MetaData` object, which allows a
|
||||||
declarative setup to be associated with an already
|
declarative setup to be associated with an already
|
||||||
@@ -400,8 +390,8 @@ table::
|
|||||||
|
|
||||||
Note that above, the ``Engineer.id`` attribute, since it shares the
|
Note that above, the ``Engineer.id`` attribute, since it shares the
|
||||||
same attribute name as the ``Person.id`` attribute, will in fact
|
same attribute name as the ``Person.id`` attribute, will in fact
|
||||||
represent the ``people.id`` and ``engineers.id`` columns together, and
|
represent the ``people.id`` and ``engineers.id`` columns together,
|
||||||
will render inside a query as ``"people.id"``.
|
with the "Engineer.id" column taking precedence if queried directly.
|
||||||
To provide the ``Engineer`` class with an attribute that represents
|
To provide the ``Engineer`` class with an attribute that represents
|
||||||
only the ``engineers.id`` column, give it a different attribute name::
|
only the ``engineers.id`` column, give it a different attribute name::
|
||||||
|
|
||||||
@@ -412,6 +402,11 @@ only the ``engineers.id`` column, give it a different attribute name::
|
|||||||
primary_key=True)
|
primary_key=True)
|
||||||
primary_language = Column(String(50))
|
primary_language = Column(String(50))
|
||||||
|
|
||||||
|
|
||||||
|
.. versionchanged:: 0.7 joined table inheritance favors the subclass
|
||||||
|
column over that of the superclass, such as querying above
|
||||||
|
for ``Engineer.id``. Prior to 0.7 this was the reverse.
|
||||||
|
|
||||||
Single Table Inheritance
|
Single Table Inheritance
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
@@ -506,10 +501,12 @@ before the class is built::
|
|||||||
Using the Concrete Helpers
|
Using the Concrete Helpers
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
New helper classes released in 0.7.3 provides a simpler pattern for concrete inheritance.
|
Helper classes provides a simpler pattern for concrete inheritance.
|
||||||
With these objects, the ``__declare_last__`` helper is used to configure the "polymorphic"
|
With these objects, the ``__declare_last__`` helper is used to configure the "polymorphic"
|
||||||
loader for the mapper after all subclasses have been declared.
|
loader for the mapper after all subclasses have been declared.
|
||||||
|
|
||||||
|
.. versionadded:: 0.7.3
|
||||||
|
|
||||||
An abstract base can be declared using the :class:`.AbstractConcreteBase` class::
|
An abstract base can be declared using the :class:`.AbstractConcreteBase` class::
|
||||||
|
|
||||||
from sqlalchemy.ext.declarative import AbstractConcreteBase
|
from sqlalchemy.ext.declarative import AbstractConcreteBase
|
||||||
@@ -671,8 +668,7 @@ keys, as a :class:`.ForeignKey` itself contains references to columns
|
|||||||
which can't be properly recreated at this level. For columns that
|
which can't be properly recreated at this level. For columns that
|
||||||
have foreign keys, as well as for the variety of mapper-level constructs
|
have foreign keys, as well as for the variety of mapper-level constructs
|
||||||
that require destination-explicit context, the
|
that require destination-explicit context, the
|
||||||
:func:`~.declared_attr` decorator (renamed from ``sqlalchemy.util.classproperty`` in 0.6.5)
|
:func:`~.declared_attr` decorator is provided so that
|
||||||
is provided so that
|
|
||||||
patterns common to many classes can be defined as callables::
|
patterns common to many classes can be defined as callables::
|
||||||
|
|
||||||
from sqlalchemy.ext.declarative import declared_attr
|
from sqlalchemy.ext.declarative import declared_attr
|
||||||
@@ -691,6 +687,9 @@ point at which the ``User`` class is constructed, and the declarative
|
|||||||
extension can use the resulting :class:`.Column` object as returned by
|
extension can use the resulting :class:`.Column` object as returned by
|
||||||
the method without the need to copy it.
|
the method without the need to copy it.
|
||||||
|
|
||||||
|
.. versionchanged:: > 0.6.5
|
||||||
|
Rename 0.6.5 ``sqlalchemy.util.classproperty`` into :func:`~.declared_attr`.
|
||||||
|
|
||||||
Columns generated by :func:`~.declared_attr` can also be
|
Columns generated by :func:`~.declared_attr` can also be
|
||||||
referenced by ``__mapper_args__`` to a limited degree, currently
|
referenced by ``__mapper_args__`` to a limited degree, currently
|
||||||
by ``polymorphic_on`` and ``version_id_col``, by specifying the
|
by ``polymorphic_on`` and ``version_id_col``, by specifying the
|
||||||
@@ -713,7 +712,7 @@ Mixing in Relationships
|
|||||||
|
|
||||||
Relationships created by :func:`~sqlalchemy.orm.relationship` are provided
|
Relationships created by :func:`~sqlalchemy.orm.relationship` are provided
|
||||||
with declarative mixin classes exclusively using the
|
with declarative mixin classes exclusively using the
|
||||||
:func:`.declared_attr` approach, eliminating any ambiguity
|
:class:`.declared_attr` approach, eliminating any ambiguity
|
||||||
which could arise when copying a relationship and its possibly column-bound
|
which could arise when copying a relationship and its possibly column-bound
|
||||||
contents. Below is an example which combines a foreign key column and a
|
contents. Below is an example which combines a foreign key column and a
|
||||||
relationship so that two classes ``Foo`` and ``Bar`` can both be configured to
|
relationship so that two classes ``Foo`` and ``Bar`` can both be configured to
|
||||||
@@ -744,7 +743,7 @@ reference a common target class via many-to-one::
|
|||||||
primaryjoin, order_by etc. expressions should use the string forms
|
primaryjoin, order_by etc. expressions should use the string forms
|
||||||
for these arguments, so that they are evaluated as late as possible.
|
for these arguments, so that they are evaluated as late as possible.
|
||||||
To reference the mixin class in these expressions, use the given ``cls``
|
To reference the mixin class in these expressions, use the given ``cls``
|
||||||
to get it's name::
|
to get its name::
|
||||||
|
|
||||||
class RefTargetMixin(object):
|
class RefTargetMixin(object):
|
||||||
@declared_attr
|
@declared_attr
|
||||||
@@ -764,7 +763,7 @@ Like :func:`~sqlalchemy.orm.relationship`, all
|
|||||||
:class:`~sqlalchemy.orm.interfaces.MapperProperty` subclasses such as
|
:class:`~sqlalchemy.orm.interfaces.MapperProperty` subclasses such as
|
||||||
:func:`~sqlalchemy.orm.deferred`, :func:`~sqlalchemy.orm.column_property`,
|
:func:`~sqlalchemy.orm.deferred`, :func:`~sqlalchemy.orm.column_property`,
|
||||||
etc. ultimately involve references to columns, and therefore, when
|
etc. ultimately involve references to columns, and therefore, when
|
||||||
used with declarative mixins, have the :func:`.declared_attr`
|
used with declarative mixins, have the :class:`.declared_attr`
|
||||||
requirement so that no reliance on copying is needed::
|
requirement so that no reliance on copying is needed::
|
||||||
|
|
||||||
class SomethingMixin(object):
|
class SomethingMixin(object):
|
||||||
@@ -879,7 +878,7 @@ In the case of ``__table_args__`` or ``__mapper_args__``
|
|||||||
specified with declarative mixins, you may want to combine
|
specified with declarative mixins, you may want to combine
|
||||||
some parameters from several mixins with those you wish to
|
some parameters from several mixins with those you wish to
|
||||||
define on the class iteself. The
|
define on the class iteself. The
|
||||||
:func:`.declared_attr` decorator can be used
|
:class:`.declared_attr` decorator can be used
|
||||||
here to create user-defined collation routines that pull
|
here to create user-defined collation routines that pull
|
||||||
from multiple collections::
|
from multiple collections::
|
||||||
|
|
||||||
@@ -928,7 +927,7 @@ Special Directives
|
|||||||
``__declare_last__()``
|
``__declare_last__()``
|
||||||
~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
The ``__declare_last__()`` hook, introduced in 0.7.3, allows definition of
|
The ``__declare_last__()`` hook allows definition of
|
||||||
a class level function that is automatically called by the :meth:`.MapperEvents.after_configured`
|
a class level function that is automatically called by the :meth:`.MapperEvents.after_configured`
|
||||||
event, which occurs after mappings are assumed to be completed and the 'configure' step
|
event, which occurs after mappings are assumed to be completed and the 'configure' step
|
||||||
has finished::
|
has finished::
|
||||||
@@ -939,12 +938,14 @@ has finished::
|
|||||||
""
|
""
|
||||||
# do something with mappings
|
# do something with mappings
|
||||||
|
|
||||||
|
.. versionadded:: 0.7.3
|
||||||
|
|
||||||
.. _declarative_abstract:
|
.. _declarative_abstract:
|
||||||
|
|
||||||
``__abstract__``
|
``__abstract__``
|
||||||
~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
``__abstract__`` is introduced in 0.7.3 and causes declarative to skip the production
|
``__abstract__`` causes declarative to skip the production
|
||||||
of a table or mapper for the class entirely. A class can be added within a hierarchy
|
of a table or mapper for the class entirely. A class can be added within a hierarchy
|
||||||
in the same way as mixin (see :ref:`declarative_mixins`), allowing subclasses to extend
|
in the same way as mixin (see :ref:`declarative_mixins`), allowing subclasses to extend
|
||||||
just from the special class::
|
just from the special class::
|
||||||
@@ -982,6 +983,8 @@ The tables themselves can then be created perhaps within distinct databases::
|
|||||||
DefaultBase.metadata.create_all(some_engine)
|
DefaultBase.metadata.create_all(some_engine)
|
||||||
OtherBase.metadata_create_all(some_other_engine)
|
OtherBase.metadata_create_all(some_other_engine)
|
||||||
|
|
||||||
|
.. versionadded:: 0.7.3
|
||||||
|
|
||||||
Class Constructor
|
Class Constructor
|
||||||
=================
|
=================
|
||||||
|
|
||||||
@@ -1322,11 +1325,9 @@ def _as_declarative(cls, classname, dict_):
|
|||||||
if k in inherited_mapper._props:
|
if k in inherited_mapper._props:
|
||||||
p = inherited_mapper._props[k]
|
p = inherited_mapper._props[k]
|
||||||
if isinstance(p, ColumnProperty):
|
if isinstance(p, ColumnProperty):
|
||||||
# note here we place the superclass column
|
# note here we place the subclass column
|
||||||
# first. this corresponds to the
|
# first. See [ticket:1892] for background.
|
||||||
# append() in mapper._configure_property().
|
our_stuff[k] = [col] + p.columns
|
||||||
# change this ordering when we do [ticket:1892]
|
|
||||||
our_stuff[k] = p.columns + [col]
|
|
||||||
|
|
||||||
|
|
||||||
cls.__mapper__ = mapper_cls(cls,
|
cls.__mapper__ = mapper_cls(cls,
|
||||||
@@ -1336,11 +1337,9 @@ def _as_declarative(cls, classname, dict_):
|
|||||||
|
|
||||||
class DeclarativeMeta(type):
|
class DeclarativeMeta(type):
|
||||||
def __init__(cls, classname, bases, dict_):
|
def __init__(cls, classname, bases, dict_):
|
||||||
if '_decl_class_registry' in cls.__dict__:
|
if '_decl_class_registry' not in cls.__dict__:
|
||||||
return type.__init__(cls, classname, bases, dict_)
|
|
||||||
else:
|
|
||||||
_as_declarative(cls, classname, cls.__dict__)
|
_as_declarative(cls, classname, cls.__dict__)
|
||||||
return type.__init__(cls, classname, bases, dict_)
|
type.__init__(cls, classname, bases, dict_)
|
||||||
|
|
||||||
def __setattr__(cls, key, value):
|
def __setattr__(cls, key, value):
|
||||||
if '__mapper__' in cls.__dict__:
|
if '__mapper__' in cls.__dict__:
|
||||||
@@ -1493,11 +1492,10 @@ class declared_attr(property):
|
|||||||
"""Mark a class-level method as representing the definition of
|
"""Mark a class-level method as representing the definition of
|
||||||
a mapped property or special declarative member name.
|
a mapped property or special declarative member name.
|
||||||
|
|
||||||
.. note::
|
.. versionchanged:: 0.6.{2,3,4}
|
||||||
|
``@declared_attr`` is available as
|
||||||
@declared_attr is available as
|
``sqlalchemy.util.classproperty`` for SQLAlchemy versions
|
||||||
``sqlalchemy.util.classproperty`` for SQLAlchemy versions
|
0.6.2, 0.6.3, 0.6.4.
|
||||||
0.6.2, 0.6.3, 0.6.4.
|
|
||||||
|
|
||||||
@declared_attr turns the attribute into a scalar-like
|
@declared_attr turns the attribute into a scalar-like
|
||||||
property that can be invoked from the uninstantiated class.
|
property that can be invoked from the uninstantiated class.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# ext/horizontal_shard.py
|
# ext/horizontal_shard.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -10,7 +10,7 @@ Defines a rudimental 'horizontal sharding' system which allows a Session to
|
|||||||
distribute queries and persistence operations across multiple databases.
|
distribute queries and persistence operations across multiple databases.
|
||||||
|
|
||||||
For a usage example, see the :ref:`examples_sharding` example included in
|
For a usage example, see the :ref:`examples_sharding` example included in
|
||||||
the source distrbution.
|
the source distribution.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# ext/hybrid.py
|
# ext/hybrid.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -179,9 +179,16 @@ The ``length(self, value)`` method is now called upon set::
|
|||||||
Working with Relationships
|
Working with Relationships
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
There's no essential difference when creating hybrids that work with related objects as
|
There's no essential difference when creating hybrids that work with
|
||||||
opposed to column-based data. The need for distinct expressions tends to be greater.
|
related objects as opposed to column-based data. The need for distinct
|
||||||
Consider the following declarative mapping which relates a ``User`` to a ``SavingsAccount``::
|
expressions tends to be greater. Two variants of we'll illustrate
|
||||||
|
are the "join-dependent" hybrid, and the "correlated subquery" hybrid.
|
||||||
|
|
||||||
|
Join-Dependent Relationship Hybrid
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Consider the following declarative
|
||||||
|
mapping which relates a ``User`` to a ``SavingsAccount``::
|
||||||
|
|
||||||
from sqlalchemy import Column, Integer, ForeignKey, Numeric, String
|
from sqlalchemy import Column, Integer, ForeignKey, Numeric, String
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
@@ -222,30 +229,88 @@ Consider the following declarative mapping which relates a ``User`` to a ``Savin
|
|||||||
def balance(cls):
|
def balance(cls):
|
||||||
return SavingsAccount.balance
|
return SavingsAccount.balance
|
||||||
|
|
||||||
The above hybrid property ``balance`` works with the first ``SavingsAccount`` entry in the list of
|
The above hybrid property ``balance`` works with the first
|
||||||
accounts for this user. The in-Python getter/setter methods can treat ``accounts`` as a Python
|
``SavingsAccount`` entry in the list of accounts for this user. The
|
||||||
|
in-Python getter/setter methods can treat ``accounts`` as a Python
|
||||||
list available on ``self``.
|
list available on ``self``.
|
||||||
|
|
||||||
However, at the expression level, we can't travel along relationships to column attributes
|
However, at the expression level, it's expected that the ``User`` class will be used
|
||||||
directly since SQLAlchemy is explicit about joins. So here, it's expected that the ``User`` class will be
|
in an appropriate context such that an appropriate join to
|
||||||
used in an appropriate context such that an appropriate join to ``SavingsAccount`` will be present::
|
``SavingsAccount`` will be present::
|
||||||
|
|
||||||
>>> print Session().query(User, User.balance).join(User.accounts).filter(User.balance > 5000)
|
>>> print Session().query(User, User.balance).\\
|
||||||
SELECT "user".id AS user_id, "user".name AS user_name, account.balance AS account_balance
|
... join(User.accounts).filter(User.balance > 5000)
|
||||||
|
SELECT "user".id AS user_id, "user".name AS user_name,
|
||||||
|
account.balance AS account_balance
|
||||||
FROM "user" JOIN account ON "user".id = account.user_id
|
FROM "user" JOIN account ON "user".id = account.user_id
|
||||||
WHERE account.balance > :balance_1
|
WHERE account.balance > :balance_1
|
||||||
|
|
||||||
Note however, that while the instance level accessors need to worry about whether ``self.accounts``
|
Note however, that while the instance level accessors need to worry
|
||||||
is even present, this issue expresses itself differently at the SQL expression level, where we basically
|
about whether ``self.accounts`` is even present, this issue expresses
|
||||||
|
itself differently at the SQL expression level, where we basically
|
||||||
would use an outer join::
|
would use an outer join::
|
||||||
|
|
||||||
>>> from sqlalchemy import or_
|
>>> from sqlalchemy import or_
|
||||||
>>> print (Session().query(User, User.balance).outerjoin(User.accounts).
|
>>> print (Session().query(User, User.balance).outerjoin(User.accounts).
|
||||||
... filter(or_(User.balance < 5000, User.balance == None)))
|
... filter(or_(User.balance < 5000, User.balance == None)))
|
||||||
SELECT "user".id AS user_id, "user".name AS user_name, account.balance AS account_balance
|
SELECT "user".id AS user_id, "user".name AS user_name,
|
||||||
|
account.balance AS account_balance
|
||||||
FROM "user" LEFT OUTER JOIN account ON "user".id = account.user_id
|
FROM "user" LEFT OUTER JOIN account ON "user".id = account.user_id
|
||||||
WHERE account.balance < :balance_1 OR account.balance IS NULL
|
WHERE account.balance < :balance_1 OR account.balance IS NULL
|
||||||
|
|
||||||
|
Correlated Subquery Relationship Hybrid
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
We can, of course, forego being dependent on the enclosing query's usage
|
||||||
|
of joins in favor of the correlated
|
||||||
|
subquery, which can portably be packed into a single colunn expression.
|
||||||
|
A correlated subquery is more portable, but often performs more poorly
|
||||||
|
at the SQL level.
|
||||||
|
Using the same technique illustrated at :ref:`mapper_column_property_sql_expressions`,
|
||||||
|
we can adjust our ``SavingsAccount`` example to aggregate the balances for
|
||||||
|
*all* accounts, and use a correlated subquery for the column expression::
|
||||||
|
|
||||||
|
from sqlalchemy import Column, Integer, ForeignKey, Numeric, String
|
||||||
|
from sqlalchemy.orm import relationship
|
||||||
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
from sqlalchemy.ext.hybrid import hybrid_property
|
||||||
|
from sqlalchemy import select, func
|
||||||
|
|
||||||
|
Base = declarative_base()
|
||||||
|
|
||||||
|
class SavingsAccount(Base):
|
||||||
|
__tablename__ = 'account'
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
user_id = Column(Integer, ForeignKey('user.id'), nullable=False)
|
||||||
|
balance = Column(Numeric(15, 5))
|
||||||
|
|
||||||
|
class User(Base):
|
||||||
|
__tablename__ = 'user'
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
name = Column(String(100), nullable=False)
|
||||||
|
|
||||||
|
accounts = relationship("SavingsAccount", backref="owner")
|
||||||
|
|
||||||
|
@hybrid_property
|
||||||
|
def balance(self):
|
||||||
|
return sum(acc.balance for acc in self.accounts)
|
||||||
|
|
||||||
|
@balance.expression
|
||||||
|
def balance(cls):
|
||||||
|
return select([func.sum(SavingsAccount.balance)]).\\
|
||||||
|
where(SavingsAccount.user_id==cls.id).\\
|
||||||
|
label('total_balance')
|
||||||
|
|
||||||
|
The above recipe will give us the ``balance`` column which renders
|
||||||
|
a correlated SELECT::
|
||||||
|
|
||||||
|
>>> print s.query(User).filter(User.balance > 400)
|
||||||
|
SELECT "user".id AS user_id, "user".name AS user_name
|
||||||
|
FROM "user"
|
||||||
|
WHERE (SELECT sum(account.balance) AS sum_1
|
||||||
|
FROM account
|
||||||
|
WHERE account.user_id = "user".id) > :param_1
|
||||||
|
|
||||||
.. _hybrid_custom_comparators:
|
.. _hybrid_custom_comparators:
|
||||||
|
|
||||||
Building Custom Comparators
|
Building Custom Comparators
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# ext/mutable.py
|
# ext/mutable.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -180,7 +180,7 @@ not picklable, due to the fact that they contain weakrefs and function
|
|||||||
callbacks. In our case, this is a good thing, since if this dictionary were
|
callbacks. In our case, this is a good thing, since if this dictionary were
|
||||||
picklable, it could lead to an excessively large pickle size for our value
|
picklable, it could lead to an excessively large pickle size for our value
|
||||||
objects that are pickled by themselves outside of the context of the parent.
|
objects that are pickled by themselves outside of the context of the parent.
|
||||||
The developer responsiblity here is only to provide a ``__getstate__`` method
|
The developer responsibility here is only to provide a ``__getstate__`` method
|
||||||
that excludes the :meth:`~.MutableBase._parents` collection from the pickle
|
that excludes the :meth:`~.MutableBase._parents` collection from the pickle
|
||||||
stream::
|
stream::
|
||||||
|
|
||||||
@@ -217,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
|
or more columns from the underlying mapped table. The usual example is that of
|
||||||
a geometric "point", and is introduced in :ref:`mapper_composite`.
|
a geometric "point", and is introduced in :ref:`mapper_composite`.
|
||||||
|
|
||||||
As of SQLAlchemy 0.7, the internals of :func:`.orm.composite` have been
|
.. versionchanged:: 0.7
|
||||||
greatly simplified and in-place mutation detection is no longer enabled by
|
The internals of :func:`.orm.composite` have been
|
||||||
default; instead, the user-defined value must detect changes on its own and
|
greatly simplified and in-place mutation detection is no longer enabled by
|
||||||
propagate them to all owning parents. The :mod:`sqlalchemy.ext.mutable`
|
default; instead, the user-defined value must detect changes on its own and
|
||||||
extension provides the helper class :class:`.MutableComposite`, which is a
|
propagate them to all owning parents. The :mod:`sqlalchemy.ext.mutable`
|
||||||
slight variant on the :class:`.Mutable` class.
|
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
|
As is the case with :class:`.Mutable`, the user-defined composite class
|
||||||
subclasses :class:`.MutableComposite` as a mixin, and detects and delivers
|
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
|
>>> assert v1 in sess.dirty
|
||||||
True
|
True
|
||||||
|
|
||||||
|
Coercing Mutable Composites
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
The :meth:`.MutableBase.coerce` method is also supported on composite types.
|
||||||
|
In the case of :class:`.MutableComposite`, the :meth:`.MutableBase.coerce`
|
||||||
|
method is only called for attribute set operations, not load operations.
|
||||||
|
Overriding the :meth:`.MutableBase.coerce` method is essentially equivalent
|
||||||
|
to using a :func:`.validates` validation routine for all attributes which
|
||||||
|
make use of the custom composite type::
|
||||||
|
|
||||||
|
class Point(MutableComposite):
|
||||||
|
# other Point methods
|
||||||
|
# ...
|
||||||
|
|
||||||
|
def coerce(cls, key, value):
|
||||||
|
if isinstance(value, tuple):
|
||||||
|
value = Point(*value)
|
||||||
|
elif not isinstance(value, Point):
|
||||||
|
raise ValueError("tuple or Point expected")
|
||||||
|
return value
|
||||||
|
|
||||||
|
.. versionadded:: 0.7.10,0.8.0b2
|
||||||
|
Support for the :meth:`.MutableBase.coerce` method in conjunction with
|
||||||
|
objects of type :class:`.MutableComposite`.
|
||||||
|
|
||||||
Supporting Pickling
|
Supporting Pickling
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
@@ -327,7 +353,7 @@ pickling process of the parent's object-relational state so that the
|
|||||||
"""
|
"""
|
||||||
from sqlalchemy.orm.attributes import flag_modified
|
from sqlalchemy.orm.attributes import flag_modified
|
||||||
from sqlalchemy import event, types
|
from sqlalchemy import event, types
|
||||||
from sqlalchemy.orm import mapper, object_mapper
|
from sqlalchemy.orm import mapper, object_mapper, Mapper
|
||||||
from sqlalchemy.util import memoized_property
|
from sqlalchemy.util import memoized_property
|
||||||
import weakref
|
import weakref
|
||||||
|
|
||||||
@@ -348,9 +374,27 @@ class MutableBase(object):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def coerce(cls, key, value):
|
def coerce(cls, key, value):
|
||||||
"""Given a value, coerce it into this type.
|
"""Given a value, coerce it into the target type.
|
||||||
|
|
||||||
|
Can be overridden by custom subclasses to coerce incoming
|
||||||
|
data into a particular type.
|
||||||
|
|
||||||
|
By default, raises ``ValueError``.
|
||||||
|
|
||||||
|
This method is called in different scenarios depending on if
|
||||||
|
the parent class is of type :class:`.Mutable` or of type
|
||||||
|
:class:`.MutableComposite`. In the case of the former, it is called
|
||||||
|
for both attribute-set operations as well as during ORM loading
|
||||||
|
operations. For the latter, it is only called during attribute-set
|
||||||
|
operations; the mechanics of the :func:`.composite` construct
|
||||||
|
handle coercion during load operations.
|
||||||
|
|
||||||
|
|
||||||
|
:param key: string name of the ORM-mapped attribute being set.
|
||||||
|
:param value: the incoming value.
|
||||||
|
:return: the method should return the coerced value, or raise
|
||||||
|
``ValueError`` if the coercion cannot be completed.
|
||||||
|
|
||||||
By default raises ValueError.
|
|
||||||
"""
|
"""
|
||||||
if value is None:
|
if value is None:
|
||||||
return None
|
return None
|
||||||
@@ -511,11 +555,6 @@ class Mutable(MutableBase):
|
|||||||
|
|
||||||
return sqltype
|
return sqltype
|
||||||
|
|
||||||
class _MutableCompositeMeta(type):
|
|
||||||
def __init__(cls, classname, bases, dict_):
|
|
||||||
cls._setup_listeners()
|
|
||||||
return type.__init__(cls, classname, bases, dict_)
|
|
||||||
|
|
||||||
class MutableComposite(MutableBase):
|
class MutableComposite(MutableBase):
|
||||||
"""Mixin that defines transparent propagation of change
|
"""Mixin that defines transparent propagation of change
|
||||||
events on a SQLAlchemy "composite" object to its
|
events on a SQLAlchemy "composite" object to its
|
||||||
@@ -532,7 +571,6 @@ class MutableComposite(MutableBase):
|
|||||||
in memory usage.
|
in memory usage.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
__metaclass__ = _MutableCompositeMeta
|
|
||||||
|
|
||||||
def changed(self):
|
def changed(self):
|
||||||
"""Subclasses should call this method whenever change events occur."""
|
"""Subclasses should call this method whenever change events occur."""
|
||||||
@@ -545,19 +583,14 @@ class MutableComposite(MutableBase):
|
|||||||
prop._attribute_keys):
|
prop._attribute_keys):
|
||||||
setattr(parent, attr_name, value)
|
setattr(parent, attr_name, value)
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _setup_listeners(cls):
|
|
||||||
"""Associate this wrapper with all future mapped composites
|
|
||||||
of the given type.
|
|
||||||
|
|
||||||
This is a convenience method that calls ``associate_with_attribute`` automatically.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def 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()
|
||||||
@@ -1,64 +1,77 @@
|
|||||||
# ext/orderinglist.py
|
# ext/orderinglist.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|
||||||
"""A custom list that manages index/position information for its children.
|
"""A custom list that manages index/position information for contained
|
||||||
|
elements.
|
||||||
|
|
||||||
:author: Jason Kirtland
|
:author: Jason Kirtland
|
||||||
|
|
||||||
``orderinglist`` is a helper for mutable ordered relationships. It will intercept
|
``orderinglist`` is a helper for mutable ordered relationships. It will
|
||||||
list operations performed on a relationship collection and automatically
|
intercept list operations performed on a :func:`.relationship`-managed
|
||||||
synchronize changes in list position with an attribute on the related objects.
|
collection and
|
||||||
(See :ref:`advdatamapping_entitycollections` for more information on the general pattern.)
|
automatically synchronize changes in list position onto a target scalar
|
||||||
|
attribute.
|
||||||
|
|
||||||
Example: Two tables that store slides in a presentation. Each slide
|
Example: A ``slide`` table, where each row refers to zero or more entries
|
||||||
has a number of bullet points, displayed in order by the 'position'
|
in a related ``bullet`` table. The bullets within a slide are
|
||||||
column on the bullets table. These bullets can be inserted and re-ordered
|
displayed in order based on the value of the ``position`` column in the
|
||||||
by your end users, and you need to update the 'position' column of all
|
``bullet`` table. As entries are reordered in memory, the value of the
|
||||||
affected rows when changes are made.
|
``position`` attribute should be updated to reflect the new sort order::
|
||||||
|
|
||||||
.. sourcecode:: python+sql
|
|
||||||
|
|
||||||
slides_table = Table('Slides', metadata,
|
Base = declarative_base()
|
||||||
Column('id', Integer, primary_key=True),
|
|
||||||
Column('name', String))
|
|
||||||
|
|
||||||
bullets_table = Table('Bullets', metadata,
|
class Slide(Base):
|
||||||
Column('id', Integer, primary_key=True),
|
__tablename__ = 'slide'
|
||||||
Column('slide_id', Integer, ForeignKey('Slides.id')),
|
|
||||||
Column('position', Integer),
|
|
||||||
Column('text', String))
|
|
||||||
|
|
||||||
class Slide(object):
|
id = Column(Integer, primary_key=True)
|
||||||
pass
|
name = Column(String)
|
||||||
class Bullet(object):
|
|
||||||
pass
|
|
||||||
|
|
||||||
mapper(Slide, slides_table, properties={
|
bullets = relationship("Bullet", order_by="Bullet.position")
|
||||||
'bullets': relationship(Bullet, order_by=[bullets_table.c.position])
|
|
||||||
})
|
|
||||||
mapper(Bullet, bullets_table)
|
|
||||||
|
|
||||||
The standard relationship mapping will produce a list-like attribute on each Slide
|
class Bullet(Base):
|
||||||
containing all related Bullets, but coping with changes in ordering is totally
|
__tablename__ = 'bullet'
|
||||||
your responsibility. If you insert a Bullet into that list, there is no
|
id = Column(Integer, primary_key=True)
|
||||||
magic- it won't have a position attribute unless you assign it it one, and
|
slide_id = Column(Integer, ForeignKey('slide.id'))
|
||||||
you'll need to manually renumber all the subsequent Bullets in the list to
|
position = Column(Integer)
|
||||||
accommodate the insert.
|
text = Column(String)
|
||||||
|
|
||||||
An ``orderinglist`` can automate this and manage the 'position' attribute on all
|
The standard relationship mapping will produce a list-like attribute on each
|
||||||
related bullets for you.
|
``Slide`` containing all related ``Bullet`` objects,
|
||||||
|
but coping with changes in ordering is not handled automatically.
|
||||||
|
When appending a ``Bullet`` into ``Slide.bullets``, the ``Bullet.position``
|
||||||
|
attribute will remain unset until manually assigned. When the ``Bullet``
|
||||||
|
is inserted into the middle of the list, the following ``Bullet`` objects
|
||||||
|
will also need to be renumbered.
|
||||||
|
|
||||||
.. sourcecode:: python+sql
|
The :class:`.OrderingList` object automates this task, managing the
|
||||||
|
``position`` attribute on all ``Bullet`` objects in the collection. It is
|
||||||
|
constructed using the :func:`.ordering_list` factory::
|
||||||
|
|
||||||
mapper(Slide, slides_table, properties={
|
from sqlalchemy.ext.orderinglist import ordering_list
|
||||||
'bullets': relationship(Bullet,
|
|
||||||
collection_class=ordering_list('position'),
|
Base = declarative_base()
|
||||||
order_by=[bullets_table.c.position])
|
|
||||||
})
|
class Slide(Base):
|
||||||
mapper(Bullet, bullets_table)
|
__tablename__ = 'slide'
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
name = Column(String)
|
||||||
|
|
||||||
|
bullets = relationship("Bullet", order_by="Bullet.position",
|
||||||
|
collection_class=ordering_list('position'))
|
||||||
|
|
||||||
|
class Bullet(Base):
|
||||||
|
__tablename__ = 'bullet'
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
slide_id = Column(Integer, ForeignKey('slide.id'))
|
||||||
|
position = Column(Integer)
|
||||||
|
text = Column(String)
|
||||||
|
|
||||||
|
With the above mapping the ``Bullet.position`` attribute is managed::
|
||||||
|
|
||||||
s = Slide()
|
s = Slide()
|
||||||
s.bullets.append(Bullet())
|
s.bullets.append(Bullet())
|
||||||
@@ -69,73 +82,87 @@ related bullets for you.
|
|||||||
s.bullets[2].position
|
s.bullets[2].position
|
||||||
>>> 2
|
>>> 2
|
||||||
|
|
||||||
Use the ``ordering_list`` function to set up the ``collection_class`` on relationships
|
The :class:`.OrderingList` construct only works with **changes** to a collection,
|
||||||
(as in the mapper example above). This implementation depends on the list
|
and not the initial load from the database, and requires that the list be
|
||||||
starting in the proper order, so be SURE to put an order_by on your relationship.
|
sorted when loaded. Therefore, be sure to
|
||||||
|
specify ``order_by`` on the :func:`.relationship` against the target ordering
|
||||||
|
attribute, so that the ordering is correct when first loaded.
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
``ordering_list`` only provides limited functionality when a primary
|
:class:`.OrderingList` only provides limited functionality when a primary
|
||||||
key column or unique column is the target of the sort. Since changing the order of
|
key column or unique column is the target of the sort. Since changing the
|
||||||
entries often means that two rows must trade values, this is not possible when
|
order of entries often means that two rows must trade values, this is not
|
||||||
the value is constrained by a primary key or unique constraint, since one of the rows
|
possible when the value is constrained by a primary key or unique
|
||||||
would temporarily have to point to a third available value so that the other row
|
constraint, since one of the rows would temporarily have to point to a
|
||||||
could take its old value. ``ordering_list`` doesn't do any of this for you,
|
third available value so that the other row could take its old
|
||||||
|
value. :class:`.OrderingList` doesn't do any of this for you,
|
||||||
nor does SQLAlchemy itself.
|
nor does SQLAlchemy itself.
|
||||||
|
|
||||||
``ordering_list`` takes the name of the related object's ordering attribute as
|
:func:`.ordering_list` takes the name of the related object's ordering attribute as
|
||||||
an argument. By default, the zero-based integer index of the object's
|
an argument. By default, the zero-based integer index of the object's
|
||||||
position in the ``ordering_list`` is synchronized with the ordering attribute:
|
position in the :func:`.ordering_list` is synchronized with the ordering attribute:
|
||||||
index 0 will get position 0, index 1 position 1, etc. To start numbering at 1
|
index 0 will get position 0, index 1 position 1, etc. To start numbering at 1
|
||||||
or some other integer, provide ``count_from=1``.
|
or some other integer, provide ``count_from=1``.
|
||||||
|
|
||||||
Ordering values are not limited to incrementing integers. Almost any scheme
|
|
||||||
can implemented by supplying a custom ``ordering_func`` that maps a Python list
|
|
||||||
index to any value you require.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from sqlalchemy.orm.collections import collection
|
from sqlalchemy.orm.collections import collection
|
||||||
from sqlalchemy import util
|
from sqlalchemy import util
|
||||||
|
|
||||||
__all__ = [ 'ordering_list' ]
|
__all__ = ['ordering_list']
|
||||||
|
|
||||||
|
|
||||||
def ordering_list(attr, count_from=None, **kw):
|
def ordering_list(attr, count_from=None, **kw):
|
||||||
"""Prepares an OrderingList factory for use in mapper definitions.
|
"""Prepares an :class:`OrderingList` factory for use in mapper definitions.
|
||||||
|
|
||||||
Returns an object suitable for use as an argument to a Mapper relationship's
|
Returns an object suitable for use as an argument to a Mapper
|
||||||
``collection_class`` option. Arguments are:
|
relationship's ``collection_class`` option. e.g.::
|
||||||
|
|
||||||
attr
|
from sqlalchemy.ext.orderinglist import ordering_list
|
||||||
|
|
||||||
|
class Slide(Base):
|
||||||
|
__tablename__ = 'slide'
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
name = Column(String)
|
||||||
|
|
||||||
|
bullets = relationship("Bullet", order_by="Bullet.position",
|
||||||
|
collection_class=ordering_list('position'))
|
||||||
|
|
||||||
|
:param attr:
|
||||||
Name of the mapped attribute to use for storage and retrieval of
|
Name of the mapped attribute to use for storage and retrieval of
|
||||||
ordering information
|
ordering information
|
||||||
|
|
||||||
count_from (optional)
|
:param count_from:
|
||||||
Set up an integer-based ordering, starting at ``count_from``. For
|
Set up an integer-based ordering, starting at ``count_from``. For
|
||||||
example, ``ordering_list('pos', count_from=1)`` would create a 1-based
|
example, ``ordering_list('pos', count_from=1)`` would create a 1-based
|
||||||
list in SQL, storing the value in the 'pos' column. Ignored if
|
list in SQL, storing the value in the 'pos' column. Ignored if
|
||||||
``ordering_func`` is supplied.
|
``ordering_func`` is supplied.
|
||||||
|
|
||||||
Passes along any keyword arguments to ``OrderingList`` constructor.
|
Additional arguments are passed to the :class:`.OrderingList` constructor.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
kw = _unsugar_count_from(count_from=count_from, **kw)
|
kw = _unsugar_count_from(count_from=count_from, **kw)
|
||||||
return lambda: OrderingList(attr, **kw)
|
return lambda: OrderingList(attr, **kw)
|
||||||
|
|
||||||
|
|
||||||
# Ordering utility functions
|
# Ordering utility functions
|
||||||
|
|
||||||
|
|
||||||
def count_from_0(index, collection):
|
def count_from_0(index, collection):
|
||||||
"""Numbering function: consecutive integers starting at 0."""
|
"""Numbering function: consecutive integers starting at 0."""
|
||||||
|
|
||||||
return index
|
return index
|
||||||
|
|
||||||
|
|
||||||
def count_from_1(index, collection):
|
def count_from_1(index, collection):
|
||||||
"""Numbering function: consecutive integers starting at 1."""
|
"""Numbering function: consecutive integers starting at 1."""
|
||||||
|
|
||||||
return index + 1
|
return index + 1
|
||||||
|
|
||||||
|
|
||||||
def count_from_n_factory(start):
|
def count_from_n_factory(start):
|
||||||
"""Numbering function: consecutive integers starting at arbitrary start."""
|
"""Numbering function: consecutive integers starting at arbitrary start."""
|
||||||
|
|
||||||
@@ -147,8 +174,9 @@ def count_from_n_factory(start):
|
|||||||
pass
|
pass
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
||||||
def _unsugar_count_from(**kw):
|
def _unsugar_count_from(**kw):
|
||||||
"""Builds counting functions from keywrod arguments.
|
"""Builds counting functions from keyword arguments.
|
||||||
|
|
||||||
Keyword argument filter, prepares a simple ``ordering_func`` from a
|
Keyword argument filter, prepares a simple ``ordering_func`` from a
|
||||||
``count_from`` argument, otherwise passes ``ordering_func`` on unchanged.
|
``count_from`` argument, otherwise passes ``ordering_func`` on unchanged.
|
||||||
@@ -164,12 +192,13 @@ def _unsugar_count_from(**kw):
|
|||||||
kw['ordering_func'] = count_from_n_factory(count_from)
|
kw['ordering_func'] = count_from_n_factory(count_from)
|
||||||
return kw
|
return kw
|
||||||
|
|
||||||
|
|
||||||
class OrderingList(list):
|
class OrderingList(list):
|
||||||
"""A custom list that manages position information for its children.
|
"""A custom list that manages position information for its children.
|
||||||
|
|
||||||
See the module and __init__ documentation for more details. The
|
The :class:`.OrderingList` object is normally set up using the
|
||||||
``ordering_list`` factory function is used to configure ``OrderingList``
|
:func:`.ordering_list` factory function, used in conjunction with
|
||||||
collections in ``mapper`` relationship definitions.
|
the :func:`.relationship` function.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -188,9 +217,10 @@ class OrderingList(list):
|
|||||||
Name of the attribute that stores the object's order in the
|
Name of the attribute that stores the object's order in the
|
||||||
relationship.
|
relationship.
|
||||||
|
|
||||||
:param ordering_func: Optional. A function that maps the position in the Python list to a
|
:param ordering_func: Optional. A function that maps the position in
|
||||||
value to store in the ``ordering_attr``. Values returned are
|
the Python list to a value to store in the
|
||||||
usually (but need not be!) integers.
|
``ordering_attr``. Values returned are usually (but need not be!)
|
||||||
|
integers.
|
||||||
|
|
||||||
An ``ordering_func`` is called with two positional parameters: the
|
An ``ordering_func`` is called with two positional parameters: the
|
||||||
index of the element in the list, and the list itself.
|
index of the element in the list, and the list itself.
|
||||||
@@ -215,7 +245,7 @@ class OrderingList(list):
|
|||||||
making changes, any of whom happen to load this collection even in
|
making changes, any of whom happen to load this collection even in
|
||||||
passing, all of the sessions would try to "clean up" the numbering
|
passing, all of the sessions would try to "clean up" the numbering
|
||||||
in their commits, possibly causing all but one to fail with a
|
in their commits, possibly causing all but one to fail with a
|
||||||
concurrent modification error. Spooky action at a distance.
|
concurrent modification error.
|
||||||
|
|
||||||
Recommend leaving this with the default of False, and just call
|
Recommend leaving this with the default of False, and just call
|
||||||
``reorder()`` if you're doing ``append()`` operations with
|
``reorder()`` if you're doing ``append()`` operations with
|
||||||
@@ -314,9 +344,24 @@ class OrderingList(list):
|
|||||||
self._reorder()
|
self._reorder()
|
||||||
# end Py2K
|
# end Py2K
|
||||||
|
|
||||||
|
def __reduce__(self):
|
||||||
|
return _reconstitute, (self.__class__, self.__dict__, list(self))
|
||||||
|
|
||||||
for func_name, func in locals().items():
|
for func_name, func in locals().items():
|
||||||
if (util.callable(func) and func.func_name == func_name and
|
if (util.callable(func) and func.func_name == func_name and
|
||||||
not func.__doc__ and hasattr(list, func_name)):
|
not func.__doc__ and hasattr(list, func_name)):
|
||||||
func.__doc__ = getattr(list, func_name).__doc__
|
func.__doc__ = getattr(list, func_name).__doc__
|
||||||
del func_name, func
|
del func_name, func
|
||||||
|
|
||||||
|
|
||||||
|
def _reconstitute(cls, dict_, items):
|
||||||
|
""" Reconstitute an :class:`.OrderingList`.
|
||||||
|
|
||||||
|
This is the adjoint to :meth:`.OrderingList.__reduce__`. It is used for
|
||||||
|
unpickling :class:`.OrderingList` objects.
|
||||||
|
|
||||||
|
"""
|
||||||
|
obj = cls.__new__(cls)
|
||||||
|
obj.__dict__.update(dict_)
|
||||||
|
list.extend(obj, items)
|
||||||
|
return obj
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
# ext/serializer.py
|
# ext/serializer.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,22 +1,17 @@
|
|||||||
# ext/sqlsoup.py
|
# ext/sqlsoup.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
.. note::
|
.. versionchanged:: 0.8
|
||||||
|
SQLSoup is now its own project. Documentation
|
||||||
SQLSoup is now its own project. Documentation
|
and project status are available at:
|
||||||
and project status are available at:
|
http://pypi.python.org/pypi/sqlsoup and
|
||||||
|
http://readthedocs.org/docs/sqlsoup\ .
|
||||||
http://pypi.python.org/pypi/sqlsoup
|
SQLSoup will no longer be included with SQLAlchemy.
|
||||||
|
|
||||||
http://readthedocs.org/docs/sqlsoup
|
|
||||||
|
|
||||||
SQLSoup will no longer be included with SQLAlchemy as of
|
|
||||||
version 0.8.
|
|
||||||
|
|
||||||
|
|
||||||
Introduction
|
Introduction
|
||||||
@@ -256,8 +251,8 @@ accepts in normal mapper definition:
|
|||||||
Advanced Use
|
Advanced Use
|
||||||
============
|
============
|
||||||
|
|
||||||
Sessions, Transations and Application Integration
|
Sessions, Transactions and Application Integration
|
||||||
-------------------------------------------------
|
---------------------------------------------------
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
@@ -599,7 +594,7 @@ class SqlSoup(object):
|
|||||||
self.session.flush()
|
self.session.flush()
|
||||||
|
|
||||||
def rollback(self):
|
def rollback(self):
|
||||||
"""Rollback the current transction.
|
"""Rollback the current transaction.
|
||||||
|
|
||||||
See :meth:`.Session.rollback`.
|
See :meth:`.Session.rollback`.
|
||||||
|
|
||||||
@@ -642,7 +637,7 @@ class SqlSoup(object):
|
|||||||
This is the "master" method that can be used to create any
|
This is the "master" method that can be used to create any
|
||||||
configuration.
|
configuration.
|
||||||
|
|
||||||
(new in 0.6.6)
|
.. versionadded:: 0.6.6
|
||||||
|
|
||||||
:param attrname: String attribute name which will be
|
:param attrname: String attribute name which will be
|
||||||
established as an attribute on this :class:.`.SqlSoup`
|
established as an attribute on this :class:.`.SqlSoup`
|
||||||
@@ -723,8 +718,9 @@ class SqlSoup(object):
|
|||||||
def map(self, selectable, base=None, **mapper_args):
|
def map(self, selectable, base=None, **mapper_args):
|
||||||
"""Map a selectable directly.
|
"""Map a selectable directly.
|
||||||
|
|
||||||
The class and its mapping are not cached and will
|
.. versionchanged:: 0.6.6
|
||||||
be discarded once dereferenced (as of 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 selectable: an :func:`.expression.select` construct.
|
||||||
:param base: a Python class which will be used as the
|
:param base: a Python class which will be used as the
|
||||||
@@ -749,8 +745,9 @@ class SqlSoup(object):
|
|||||||
"""Map a selectable directly, wrapping the
|
"""Map a selectable directly, wrapping the
|
||||||
selectable in a subquery with labels.
|
selectable in a subquery with labels.
|
||||||
|
|
||||||
The class and its mapping are not cached and will
|
.. versionchanged:: 0.6.6
|
||||||
be discarded once dereferenced (as of 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 selectable: an :func:`.expression.select` construct.
|
||||||
:param base: a Python class which will be used as the
|
:param base: a Python class which will be used as the
|
||||||
@@ -773,8 +770,9 @@ class SqlSoup(object):
|
|||||||
base=None, **mapper_args):
|
base=None, **mapper_args):
|
||||||
"""Create an :func:`.expression.join` and map to it.
|
"""Create an :func:`.expression.join` and map to it.
|
||||||
|
|
||||||
The class and its mapping are not cached and will
|
.. versionchanged:: 0.6.6
|
||||||
be discarded once dereferenced (as of 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 left: a mapped class or table object.
|
||||||
:param right: a mapped class or table object.
|
:param right: a mapped class or table object.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# sqlalchemy/interfaces.py
|
# sqlalchemy/interfaces.py
|
||||||
# Copyright (C) 2007-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2007-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
# Copyright (C) 2007 Jason Kirtland jek@discorporate.us
|
# Copyright (C) 2007 Jason Kirtland jek@discorporate.us
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# orm/__init__.py
|
# orm/__init__.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -180,10 +180,8 @@ def create_session(bind=None, **kwargs):
|
|||||||
def relationship(argument, secondary=None, **kwargs):
|
def relationship(argument, secondary=None, **kwargs):
|
||||||
"""Provide a relationship of a primary Mapper to a secondary Mapper.
|
"""Provide a relationship of a primary Mapper to a secondary Mapper.
|
||||||
|
|
||||||
.. note::
|
.. versionchanged:: 0.6
|
||||||
|
:func:`relationship` is historically known as :func:`relation`.
|
||||||
:func:`relationship` is historically known as
|
|
||||||
:func:`relation` prior to version 0.6.
|
|
||||||
|
|
||||||
This corresponds to a parent-child or associative table relationship. The
|
This corresponds to a parent-child or associative table relationship. The
|
||||||
constructed class is an instance of :class:`.RelationshipProperty`.
|
constructed class is an instance of :class:`.RelationshipProperty`.
|
||||||
@@ -299,11 +297,13 @@ def relationship(argument, secondary=None, **kwargs):
|
|||||||
|
|
||||||
* ``delete-orphan`` - if an item of the child's type is
|
* ``delete-orphan`` - if an item of the child's type is
|
||||||
detached from its parent, mark it for deletion.
|
detached from its parent, mark it for deletion.
|
||||||
As of version 0.7, this option does not prevent
|
|
||||||
a new instance of the child object from being persisted
|
.. versionchanged:: 0.7
|
||||||
without a parent to start with; to constrain against
|
This option does not prevent
|
||||||
that case, ensure the child's foreign key column(s)
|
a new instance of the child object from being persisted
|
||||||
is configured as NOT NULL
|
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`
|
* ``refresh-expire`` - cascade the :meth:`.Session.expire`
|
||||||
and :meth:`~sqlalchemy.orm.session.Session.refresh` operations
|
and :meth:`~sqlalchemy.orm.session.Session.refresh` operations
|
||||||
@@ -390,12 +390,14 @@ def relationship(argument, secondary=None, **kwargs):
|
|||||||
:param innerjoin=False:
|
:param innerjoin=False:
|
||||||
when ``True``, joined eager loads will use an inner join to join
|
when ``True``, joined eager loads will use an inner join to join
|
||||||
against related tables instead of an outer join. The purpose
|
against related tables instead of an outer join. The purpose
|
||||||
of this option is strictly one of performance, as inner joins
|
of this option is generally one of performance, as inner joins
|
||||||
generally perform better than outer joins. This flag can
|
generally perform better than outer joins. Another reason can be
|
||||||
be set to ``True`` when the relationship references an object
|
the use of ``with_lockmode``, which does not support outer joins.
|
||||||
via many-to-one using local foreign keys that are not nullable,
|
|
||||||
or when the reference is one-to-one or a collection that is
|
This flag can be set to ``True`` when the relationship references an
|
||||||
guaranteed to have one or at least one entry.
|
object via many-to-one using local foreign keys that are not nullable,
|
||||||
|
or when the reference is one-to-one or a collection that is guaranteed
|
||||||
|
to have one or at least one entry.
|
||||||
|
|
||||||
:param join_depth:
|
:param join_depth:
|
||||||
when non-``None``, an integer value indicating how many levels
|
when non-``None``, an integer value indicating how many levels
|
||||||
@@ -417,7 +419,9 @@ def relationship(argument, secondary=None, **kwargs):
|
|||||||
|
|
||||||
* ``immediate`` - items should be loaded as the parents are loaded,
|
* ``immediate`` - items should be loaded as the parents are loaded,
|
||||||
using a separate SELECT statement, or identity map fetch for
|
using a separate SELECT statement, or identity map fetch for
|
||||||
simple many-to-one references. (new as of 0.6.5)
|
simple many-to-one references.
|
||||||
|
|
||||||
|
.. versionadded:: 0.6.5
|
||||||
|
|
||||||
* ``joined`` - items should be loaded "eagerly" in the same query as
|
* ``joined`` - items should be loaded "eagerly" in the same query as
|
||||||
that of the parent, using a JOIN or LEFT OUTER JOIN. Whether
|
that of the parent, using a JOIN or LEFT OUTER JOIN. Whether
|
||||||
@@ -441,7 +445,7 @@ def relationship(argument, secondary=None, **kwargs):
|
|||||||
|
|
||||||
* True - a synonym for 'select'
|
* True - a synonym for 'select'
|
||||||
|
|
||||||
* False - a synonyn for 'joined'
|
* False - a synonym for 'joined'
|
||||||
|
|
||||||
* None - a synonym for 'noload'
|
* None - a synonym for 'noload'
|
||||||
|
|
||||||
@@ -668,7 +672,9 @@ def column_property(*cols, **kw):
|
|||||||
flag is available for applications that make use of
|
flag is available for applications that make use of
|
||||||
:func:`.attributes.get_history` or :meth:`.Session.is_modified`
|
:func:`.attributes.get_history` or :meth:`.Session.is_modified`
|
||||||
which also need to know
|
which also need to know
|
||||||
the "previous" value of the attribute. (new in 0.6.6)
|
the "previous" value of the attribute.
|
||||||
|
|
||||||
|
.. versionadded:: 0.6.6
|
||||||
|
|
||||||
:param comparator_factory: a class which extends
|
:param comparator_factory: a class which extends
|
||||||
:class:`.ColumnProperty.Comparator` which provides custom SQL clause
|
:class:`.ColumnProperty.Comparator` which provides custom SQL clause
|
||||||
@@ -700,8 +706,8 @@ def column_property(*cols, **kw):
|
|||||||
Note however that the :class:`.Session` with default expiration
|
Note however that the :class:`.Session` with default expiration
|
||||||
settings still expires
|
settings still expires
|
||||||
all attributes after a :meth:`.Session.commit` call, however.
|
all attributes after a :meth:`.Session.commit` call, however.
|
||||||
New in 0.7.3.
|
|
||||||
|
|
||||||
|
.. versionadded:: 0.7.3
|
||||||
|
|
||||||
:param extension:
|
:param extension:
|
||||||
an
|
an
|
||||||
@@ -719,7 +725,7 @@ def column_property(*cols, **kw):
|
|||||||
def composite(class_, *cols, **kwargs):
|
def composite(class_, *cols, **kwargs):
|
||||||
"""Return a composite column-based property for use with a Mapper.
|
"""Return a composite column-based property for use with a Mapper.
|
||||||
|
|
||||||
See the mapping documention section :ref:`mapper_composite` for a full
|
See the mapping documentation section :ref:`mapper_composite` for a full
|
||||||
usage example.
|
usage example.
|
||||||
|
|
||||||
:param class\_:
|
:param class\_:
|
||||||
@@ -732,8 +738,10 @@ def composite(class_, *cols, **kwargs):
|
|||||||
When ``True``, indicates that the "previous" value for a
|
When ``True``, indicates that the "previous" value for a
|
||||||
scalar attribute should be loaded when replaced, if not
|
scalar attribute should be loaded when replaced, if not
|
||||||
already loaded. See the same flag on :func:`.column_property`.
|
already loaded. See the same flag on :func:`.column_property`.
|
||||||
(This flag becomes meaningful specifically for
|
|
||||||
:func:`.composite` in 0.7 - previously it was a placeholder).
|
.. versionchanged:: 0.7
|
||||||
|
This flag specifically becomes meaningful
|
||||||
|
- previously it was a placeholder.
|
||||||
|
|
||||||
:param group:
|
:param group:
|
||||||
A group name for this property when marked as deferred.
|
A group name for this property when marked as deferred.
|
||||||
@@ -973,7 +981,7 @@ def mapper(class_, local_table=None, *args, **params):
|
|||||||
"polymorphic_identity":"employee"
|
"polymorphic_identity":"employee"
|
||||||
}
|
}
|
||||||
|
|
||||||
As of SQLAlchemy 0.7.4, it may also be specified
|
It may also be specified
|
||||||
as a SQL expression, as in this example where we
|
as a SQL expression, as in this example where we
|
||||||
use the :func:`.case` construct to provide a conditional
|
use the :func:`.case` construct to provide a conditional
|
||||||
approach::
|
approach::
|
||||||
@@ -992,7 +1000,7 @@ def mapper(class_, local_table=None, *args, **params):
|
|||||||
"polymorphic_identity":"employee"
|
"polymorphic_identity":"employee"
|
||||||
}
|
}
|
||||||
|
|
||||||
Also as of 0.7.4, it may also refer to any attribute
|
It may also refer to any attribute
|
||||||
configured with :func:`.column_property`, or to the
|
configured with :func:`.column_property`, or to the
|
||||||
string name of one::
|
string name of one::
|
||||||
|
|
||||||
@@ -1013,6 +1021,11 @@ def mapper(class_, local_table=None, *args, **params):
|
|||||||
"polymorphic_identity":"employee"
|
"polymorphic_identity":"employee"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.. versionchanged:: 0.7.4
|
||||||
|
``polymorphic_on`` may be specified as a SQL expression,
|
||||||
|
or refer to any attribute configured with
|
||||||
|
:func:`.column_property`, or to the string name of one.
|
||||||
|
|
||||||
When setting ``polymorphic_on`` to reference an
|
When setting ``polymorphic_on`` to reference an
|
||||||
attribute or expression that's not present in the
|
attribute or expression that's not present in the
|
||||||
locally mapped :class:`.Table`, yet the value
|
locally mapped :class:`.Table`, yet the value
|
||||||
@@ -1119,11 +1132,10 @@ def synonym(name, map_column=False, descriptor=None,
|
|||||||
comparator_factory=None, doc=None):
|
comparator_factory=None, doc=None):
|
||||||
"""Denote an attribute name as a synonym to a mapped property.
|
"""Denote an attribute name as a synonym to a mapped property.
|
||||||
|
|
||||||
.. note::
|
.. versionchanged:: 0.7
|
||||||
|
:func:`.synonym` is superseded by the :mod:`~sqlalchemy.ext.hybrid`
|
||||||
:func:`.synonym` is superseded as of 0.7 by
|
extension. See the documentation for hybrids
|
||||||
the :mod:`~sqlalchemy.ext.hybrid` extension. See
|
at :ref:`hybrids_toplevel`.
|
||||||
the documentation for hybrids at :ref:`hybrids_toplevel`.
|
|
||||||
|
|
||||||
Used with the ``properties`` dictionary sent to
|
Used with the ``properties`` dictionary sent to
|
||||||
:func:`~sqlalchemy.orm.mapper`::
|
:func:`~sqlalchemy.orm.mapper`::
|
||||||
@@ -1163,11 +1175,10 @@ def comparable_property(comparator_factory, descriptor=None):
|
|||||||
"""Provides a method of applying a :class:`.PropComparator`
|
"""Provides a method of applying a :class:`.PropComparator`
|
||||||
to any Python descriptor attribute.
|
to any Python descriptor attribute.
|
||||||
|
|
||||||
.. note::
|
.. versionchanged:: 0.7
|
||||||
|
:func:`.comparable_property` is superseded by
|
||||||
:func:`.comparable_property` is superseded as of 0.7 by
|
the :mod:`~sqlalchemy.ext.hybrid` extension. See the example
|
||||||
the :mod:`~sqlalchemy.ext.hybrid` extension. See the example
|
at :ref:`hybrid_custom_comparators`.
|
||||||
at :ref:`hybrid_custom_comparators`.
|
|
||||||
|
|
||||||
Allows any Python descriptor to behave like a SQL-enabled
|
Allows any Python descriptor to behave like a SQL-enabled
|
||||||
attribute when used at the class level in queries, allowing
|
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
|
Optional when used in a ``properties={}`` declaration. The Python
|
||||||
descriptor or property to layer comparison behavior on top of.
|
descriptor or property to layer comparison behavior on top of.
|
||||||
|
|
||||||
The like-named descriptor will be automatically retreived from the
|
The like-named descriptor will be automatically retrieved from the
|
||||||
mapped class if left blank in a ``properties`` declaration.
|
mapped class if left blank in a ``properties`` declaration.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -1267,18 +1278,17 @@ def joinedload(*keys, **kw):
|
|||||||
"""Return a ``MapperOption`` that will convert the property of the given
|
"""Return a ``MapperOption`` that will convert the property of the given
|
||||||
name or series of mapped attributes into an joined eager load.
|
name or series of mapped attributes into an joined eager load.
|
||||||
|
|
||||||
.. note::
|
.. versionchanged:: 0.6beta3
|
||||||
|
This function is known as :func:`eagerload` in all versions
|
||||||
This function is known as :func:`eagerload` in all versions
|
of SQLAlchemy prior to version 0.6beta3, including the 0.5 and 0.4
|
||||||
of SQLAlchemy prior to version 0.6beta3, including the 0.5 and 0.4
|
series. :func:`eagerload` will remain available for the foreseeable
|
||||||
series. :func:`eagerload` will remain available for the foreseeable
|
future in order to enable cross-compatibility.
|
||||||
future in order to enable cross-compatibility.
|
|
||||||
|
|
||||||
Used with :meth:`~sqlalchemy.orm.query.Query.options`.
|
Used with :meth:`~sqlalchemy.orm.query.Query.options`.
|
||||||
|
|
||||||
examples::
|
examples::
|
||||||
|
|
||||||
# joined-load the "orders" colleciton on "User"
|
# joined-load the "orders" collection on "User"
|
||||||
query(User).options(joinedload(User.orders))
|
query(User).options(joinedload(User.orders))
|
||||||
|
|
||||||
# joined-load the "keywords" collection on each "Item",
|
# joined-load the "keywords" collection on each "Item",
|
||||||
@@ -1327,12 +1337,11 @@ def joinedload_all(*keys, **kw):
|
|||||||
given dot-separated path or series of mapped attributes
|
given dot-separated path or series of mapped attributes
|
||||||
into an joined eager load.
|
into an joined eager load.
|
||||||
|
|
||||||
.. note::
|
.. versionchanged:: 0.6beta3
|
||||||
|
This function is known as :func:`eagerload_all` in all versions
|
||||||
This function is known as :func:`eagerload_all` in all versions
|
of SQLAlchemy prior to version 0.6beta3, including the 0.5 and 0.4
|
||||||
of SQLAlchemy prior to version 0.6beta3, including the 0.5 and 0.4
|
series. :func:`eagerload_all` will remain available for the
|
||||||
series. :func:`eagerload_all` will remain available for the
|
foreseeable future in order to enable cross-compatibility.
|
||||||
foreseeable future in order to enable cross-compatibility.
|
|
||||||
|
|
||||||
Used with :meth:`~sqlalchemy.orm.query.Query.options`.
|
Used with :meth:`~sqlalchemy.orm.query.Query.options`.
|
||||||
|
|
||||||
@@ -1381,7 +1390,7 @@ def subqueryload(*keys):
|
|||||||
|
|
||||||
examples::
|
examples::
|
||||||
|
|
||||||
# subquery-load the "orders" colleciton on "User"
|
# subquery-load the "orders" collection on "User"
|
||||||
query(User).options(subqueryload(User.orders))
|
query(User).options(subqueryload(User.orders))
|
||||||
|
|
||||||
# subquery-load the "keywords" collection on each "Item",
|
# subquery-load the "keywords" collection on each "Item",
|
||||||
@@ -1478,7 +1487,7 @@ def immediateload(*keys):
|
|||||||
|
|
||||||
See also: :func:`lazyload`, :func:`eagerload`, :func:`subqueryload`
|
See also: :func:`lazyload`, :func:`eagerload`, :func:`subqueryload`
|
||||||
|
|
||||||
New as of verison 0.6.5.
|
.. versionadded:: 0.6.5
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return strategies.EagerLazyOption(keys, lazy='immediate')
|
return strategies.EagerLazyOption(keys, lazy='immediate')
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# orm/attributes.py
|
# orm/attributes.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -413,7 +413,7 @@ class AttributeImpl(object):
|
|||||||
Only applies to object-based attributes.
|
Only applies to object-based attributes.
|
||||||
|
|
||||||
This is an inlining of existing functionality
|
This is an inlining of existing functionality
|
||||||
which roughly correponds to:
|
which roughly corresponds to:
|
||||||
|
|
||||||
get_state_history(
|
get_state_history(
|
||||||
state,
|
state,
|
||||||
@@ -653,7 +653,7 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl):
|
|||||||
if current is not None:
|
if current is not None:
|
||||||
ret = [(instance_state(current), current)]
|
ret = [(instance_state(current), current)]
|
||||||
else:
|
else:
|
||||||
ret = []
|
ret = [(None, None)]
|
||||||
|
|
||||||
if self.key in state.committed_state:
|
if self.key in state.committed_state:
|
||||||
original = state.committed_state[self.key]
|
original = state.committed_state[self.key]
|
||||||
@@ -686,7 +686,7 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl):
|
|||||||
old is not PASSIVE_NO_RESULT and \
|
old is not PASSIVE_NO_RESULT and \
|
||||||
check_old is not old:
|
check_old is not old:
|
||||||
if pop:
|
if pop:
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Object %s not associated with %s on attribute '%s'" % (
|
"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
|
# use easily recognizable names for stack traces
|
||||||
|
|
||||||
|
parent_token = attribute.impl.parent_token
|
||||||
|
|
||||||
|
def _acceptable_key_err(child_state, initiator):
|
||||||
|
raise ValueError(
|
||||||
|
"Object %s not associated with attribute of "
|
||||||
|
"type %s" % (mapperutil.state_str(child_state),
|
||||||
|
manager_of_class(initiator.class_)[initiator.key]))
|
||||||
|
|
||||||
def emit_backref_from_scalar_set_event(state, child, oldchild, initiator):
|
def emit_backref_from_scalar_set_event(state, child, oldchild, initiator):
|
||||||
if oldchild is child:
|
if oldchild is child:
|
||||||
return child
|
return child
|
||||||
@@ -991,35 +999,47 @@ def backref_listeners(attribute, key, uselist):
|
|||||||
if child is not None:
|
if child is not None:
|
||||||
child_state, child_dict = instance_state(child),\
|
child_state, child_dict = instance_state(child),\
|
||||||
instance_dict(child)
|
instance_dict(child)
|
||||||
child_state.manager[key].impl.append(
|
child_impl = child_state.manager[key].impl
|
||||||
child_state,
|
if initiator.parent_token is not parent_token and \
|
||||||
child_dict,
|
initiator.parent_token is not child_impl.parent_token:
|
||||||
state.obj(),
|
_acceptable_key_err(state, initiator)
|
||||||
initiator,
|
child_impl.append(
|
||||||
passive=PASSIVE_NO_FETCH)
|
child_state,
|
||||||
|
child_dict,
|
||||||
|
state.obj(),
|
||||||
|
initiator,
|
||||||
|
passive=PASSIVE_NO_FETCH)
|
||||||
return child
|
return child
|
||||||
|
|
||||||
def emit_backref_from_collection_append_event(state, child, initiator):
|
def emit_backref_from_collection_append_event(state, child, initiator):
|
||||||
child_state, child_dict = instance_state(child), \
|
child_state, child_dict = instance_state(child), \
|
||||||
instance_dict(child)
|
instance_dict(child)
|
||||||
child_state.manager[key].impl.append(
|
child_impl = child_state.manager[key].impl
|
||||||
child_state,
|
if initiator.parent_token is not parent_token and \
|
||||||
child_dict,
|
initiator.parent_token is not child_impl.parent_token:
|
||||||
state.obj(),
|
_acceptable_key_err(state, initiator)
|
||||||
initiator,
|
child_impl.append(
|
||||||
passive=PASSIVE_NO_FETCH)
|
child_state,
|
||||||
|
child_dict,
|
||||||
|
state.obj(),
|
||||||
|
initiator,
|
||||||
|
passive=PASSIVE_NO_FETCH)
|
||||||
return child
|
return child
|
||||||
|
|
||||||
def emit_backref_from_collection_remove_event(state, child, initiator):
|
def emit_backref_from_collection_remove_event(state, child, initiator):
|
||||||
if child is not None:
|
if child is not None:
|
||||||
child_state, child_dict = instance_state(child),\
|
child_state, child_dict = instance_state(child),\
|
||||||
instance_dict(child)
|
instance_dict(child)
|
||||||
child_state.manager[key].impl.pop(
|
child_impl = child_state.manager[key].impl
|
||||||
child_state,
|
# can't think of a path that would produce an initiator
|
||||||
child_dict,
|
# mismatch here, as it would require an existing collection
|
||||||
state.obj(),
|
# mismatch.
|
||||||
initiator,
|
child_impl.pop(
|
||||||
passive=PASSIVE_NO_FETCH)
|
child_state,
|
||||||
|
child_dict,
|
||||||
|
state.obj(),
|
||||||
|
initiator,
|
||||||
|
passive=PASSIVE_NO_FETCH)
|
||||||
|
|
||||||
if uselist:
|
if uselist:
|
||||||
event.listen(attribute, "append",
|
event.listen(attribute, "append",
|
||||||
@@ -1126,7 +1146,7 @@ class History(tuple):
|
|||||||
return cls((), (), ())
|
return cls((), (), ())
|
||||||
else:
|
else:
|
||||||
return cls((), [current], ())
|
return cls((), [current], ())
|
||||||
# dont let ClauseElement expressions here trip things up
|
# don't let ClauseElement expressions here trip things up
|
||||||
elif attribute.is_equal(current, original) is True:
|
elif attribute.is_equal(current, original) is True:
|
||||||
return cls((), [current], ())
|
return cls((), [current], ())
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# orm/collections.py
|
# orm/collections.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -111,22 +111,57 @@ import weakref
|
|||||||
from sqlalchemy.sql import expression
|
from sqlalchemy.sql import expression
|
||||||
from sqlalchemy import schema, util, exc as sa_exc
|
from sqlalchemy import schema, util, exc as sa_exc
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['collection', 'collection_adapter',
|
__all__ = ['collection', 'collection_adapter',
|
||||||
'mapped_collection', 'column_mapped_collection',
|
'mapped_collection', 'column_mapped_collection',
|
||||||
'attribute_mapped_collection']
|
'attribute_mapped_collection']
|
||||||
|
|
||||||
__instrumentation_mutex = util.threading.Lock()
|
__instrumentation_mutex = util.threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
|
class _PlainColumnGetter(object):
|
||||||
|
"""Plain column getter, stores collection of Column objects
|
||||||
|
directly.
|
||||||
|
|
||||||
|
Serializes to a :class:`._SerializableColumnGetterV2`
|
||||||
|
which has more expensive __call__() performance
|
||||||
|
and some rare caveats.
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __init__(self, cols):
|
||||||
|
self.cols = cols
|
||||||
|
self.composite = len(cols) > 1
|
||||||
|
|
||||||
|
def __reduce__(self):
|
||||||
|
return _SerializableColumnGetterV2._reduce_from_cols(self.cols)
|
||||||
|
|
||||||
|
def _cols(self, mapper):
|
||||||
|
return self.cols
|
||||||
|
|
||||||
|
def __call__(self, value):
|
||||||
|
state = instance_state(value)
|
||||||
|
m = _state_mapper(state)
|
||||||
|
|
||||||
|
key = [
|
||||||
|
m._get_state_attr_by_column(state, state.dict, col)
|
||||||
|
for col in self._cols(m)
|
||||||
|
]
|
||||||
|
|
||||||
|
if self.composite:
|
||||||
|
return tuple(key)
|
||||||
|
else:
|
||||||
|
return key[0]
|
||||||
|
|
||||||
class _SerializableColumnGetter(object):
|
class _SerializableColumnGetter(object):
|
||||||
|
"""Column-based getter used in version 0.7.6 only.
|
||||||
|
|
||||||
|
Remains here for pickle compatibility with 0.7.6.
|
||||||
|
|
||||||
|
"""
|
||||||
def __init__(self, colkeys):
|
def __init__(self, colkeys):
|
||||||
self.colkeys = colkeys
|
self.colkeys = colkeys
|
||||||
self.composite = len(colkeys) > 1
|
self.composite = len(colkeys) > 1
|
||||||
|
|
||||||
def __reduce__(self):
|
def __reduce__(self):
|
||||||
return _SerializableColumnGetter, (self.colkeys,)
|
return _SerializableColumnGetter, (self.colkeys,)
|
||||||
|
|
||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
state = instance_state(value)
|
state = instance_state(value)
|
||||||
m = _state_mapper(state)
|
m = _state_mapper(state)
|
||||||
@@ -139,6 +174,48 @@ class _SerializableColumnGetter(object):
|
|||||||
else:
|
else:
|
||||||
return key[0]
|
return key[0]
|
||||||
|
|
||||||
|
class _SerializableColumnGetterV2(_PlainColumnGetter):
|
||||||
|
"""Updated serializable getter which deals with
|
||||||
|
multi-table mapped classes.
|
||||||
|
|
||||||
|
Two extremely unusual cases are not supported.
|
||||||
|
Mappings which have tables across multiple metadata
|
||||||
|
objects, or which are mapped to non-Table selectables
|
||||||
|
linked across inheriting mappers may fail to function
|
||||||
|
here.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, colkeys):
|
||||||
|
self.colkeys = colkeys
|
||||||
|
self.composite = len(colkeys) > 1
|
||||||
|
|
||||||
|
def __reduce__(self):
|
||||||
|
return self.__class__, (self.colkeys,)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _reduce_from_cols(cls, cols):
|
||||||
|
def _table_key(c):
|
||||||
|
if not isinstance(c.table, expression.TableClause):
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return c.table.key
|
||||||
|
colkeys = [(c.key, _table_key(c)) for c in cols]
|
||||||
|
return _SerializableColumnGetterV2, (colkeys,)
|
||||||
|
|
||||||
|
def _cols(self, mapper):
|
||||||
|
cols = []
|
||||||
|
metadata = getattr(mapper.local_table, 'metadata', None)
|
||||||
|
for (ckey, tkey) in self.colkeys:
|
||||||
|
if tkey is None or \
|
||||||
|
metadata is None or \
|
||||||
|
tkey not in metadata:
|
||||||
|
cols.append(mapper.local_table.c[ckey])
|
||||||
|
else:
|
||||||
|
cols.append(metadata.tables[tkey].c[ckey])
|
||||||
|
return cols
|
||||||
|
|
||||||
|
|
||||||
def column_mapped_collection(mapping_spec):
|
def column_mapped_collection(mapping_spec):
|
||||||
"""A dictionary-based collection type with column-based keying.
|
"""A dictionary-based collection type with column-based keying.
|
||||||
|
|
||||||
@@ -155,10 +232,10 @@ def column_mapped_collection(mapping_spec):
|
|||||||
from sqlalchemy.orm.util import _state_mapper
|
from sqlalchemy.orm.util import _state_mapper
|
||||||
from sqlalchemy.orm.attributes import instance_state
|
from sqlalchemy.orm.attributes import instance_state
|
||||||
|
|
||||||
cols = [c.key for c in [
|
cols = [expression._only_column_elements(q, "mapping_spec")
|
||||||
expression._only_column_elements(q, "mapping_spec")
|
for q in util.to_list(mapping_spec)
|
||||||
for q in util.to_list(mapping_spec)]]
|
]
|
||||||
keyfunc = _SerializableColumnGetter(cols)
|
keyfunc = _PlainColumnGetter(cols)
|
||||||
return lambda: MappedCollection(keyfunc)
|
return lambda: MappedCollection(keyfunc)
|
||||||
|
|
||||||
class _SerializableAttrGetter(object):
|
class _SerializableAttrGetter(object):
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# orm/dependency.py
|
# orm/dependency.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# orm/deprecated_interfaces.py
|
# orm/deprecated_interfaces.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -247,10 +247,11 @@ class MapperExtension(object):
|
|||||||
instance population will not proceed, giving this extension an
|
instance population will not proceed, giving this extension an
|
||||||
opportunity to populate the instance itself, if desired.
|
opportunity to populate the instance itself, if desired.
|
||||||
|
|
||||||
As of 0.5, most usages of this hook are obsolete. For a
|
.. deprecated:: 0.5
|
||||||
generic "object has been newly created from a row" hook, use
|
Most usages of this hook are obsolete. For a
|
||||||
``reconstruct_instance()``, or the ``@orm.reconstructor``
|
generic "object has been newly created from a row" hook, use
|
||||||
decorator.
|
``reconstruct_instance()``, or the ``@orm.reconstructor``
|
||||||
|
decorator.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return EXT_CONTINUE
|
return EXT_CONTINUE
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# orm/descriptor_props.py
|
# orm/descriptor_props.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# orm/dynamic.py
|
# orm/dynamic.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -12,7 +12,6 @@ basic add/delete mutation.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from sqlalchemy import log, util
|
from sqlalchemy import log, util
|
||||||
from sqlalchemy import exc as sa_exc
|
|
||||||
from sqlalchemy.orm import exc as orm_exc
|
from sqlalchemy.orm import exc as orm_exc
|
||||||
from sqlalchemy.sql import operators
|
from sqlalchemy.sql import operators
|
||||||
from sqlalchemy.orm import (
|
from sqlalchemy.orm import (
|
||||||
@@ -20,12 +19,16 @@ from sqlalchemy.orm import (
|
|||||||
)
|
)
|
||||||
from sqlalchemy.orm.query import Query
|
from sqlalchemy.orm.query import Query
|
||||||
from sqlalchemy.orm.util import has_identity
|
from sqlalchemy.orm.util import has_identity
|
||||||
from sqlalchemy.orm import attributes, collections
|
from sqlalchemy.orm import collections
|
||||||
|
|
||||||
class DynaLoader(strategies.AbstractRelationshipLoader):
|
class DynaLoader(strategies.AbstractRelationshipLoader):
|
||||||
def init_class_attribute(self, mapper):
|
def init_class_attribute(self, mapper):
|
||||||
self.is_class_level = True
|
self.is_class_level = True
|
||||||
|
if not self.uselist:
|
||||||
|
util.warn(
|
||||||
|
"On relationship %s, 'dynamic' loaders cannot be used with "
|
||||||
|
"many-to-one/one-to-one relationships and/or "
|
||||||
|
"uselist=False." % self.parent_property)
|
||||||
strategies._register_attribute(self,
|
strategies._register_attribute(self,
|
||||||
mapper,
|
mapper,
|
||||||
useobject=True,
|
useobject=True,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# orm/evaluator.py
|
# orm/evaluator.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# orm/events.py
|
# orm/events.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -388,8 +388,16 @@ class MapperEvents(event.Events):
|
|||||||
def mapper_configured(self, mapper, class_):
|
def mapper_configured(self, mapper, class_):
|
||||||
"""Called when the mapper for the class is fully configured.
|
"""Called when the mapper for the class is fully configured.
|
||||||
|
|
||||||
This event is the latest phase of mapper construction.
|
This event is the latest phase of mapper construction, and
|
||||||
The mapper should be in its final state.
|
is invoked when the mapped classes are first used, so that relationships
|
||||||
|
between mappers can be resolved. When the event is called,
|
||||||
|
the mapper should be in its final state.
|
||||||
|
|
||||||
|
While the configuration event normally occurs automatically,
|
||||||
|
it can be forced to occur ahead of time, in the case where the event
|
||||||
|
is needed before any actual mapper usage, by using the
|
||||||
|
:func:`.configure_mappers` function.
|
||||||
|
|
||||||
|
|
||||||
:param mapper: the :class:`.Mapper` which is the target
|
:param mapper: the :class:`.Mapper` which is the target
|
||||||
of this event.
|
of this event.
|
||||||
@@ -1007,7 +1015,7 @@ class SessionEvents(event.Events):
|
|||||||
for the given :class:`.Session` is available via the
|
for the given :class:`.Session` is available via the
|
||||||
:attr:`.Session.transaction` attribute.
|
:attr:`.Session.transaction` attribute.
|
||||||
|
|
||||||
New in 0.7.3.
|
.. versionadded:: 0.7.3
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# orm/exc.py
|
# orm/exc.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -32,7 +32,9 @@ class StaleDataError(sa.exc.SQLAlchemyError):
|
|||||||
the object was previously attached to a different parent
|
the object was previously attached to a different parent
|
||||||
identity which was garbage collected, and a decision
|
identity which was garbage collected, and a decision
|
||||||
cannot be made if the new parent was really the most
|
cannot be made if the new parent was really the most
|
||||||
recent "parent" (new in 0.7.4).
|
recent "parent".
|
||||||
|
|
||||||
|
.. versionadded:: 0.7.4
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# orm/identity.py
|
# orm/identity.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# orm/instrumentation.py
|
# orm/instrumentation.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# orm/interfaces.py
|
# orm/interfaces.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# orm/mapper.py
|
# orm/mapper.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -580,6 +580,12 @@ class Mapper(object):
|
|||||||
self.inherits._inheriting_mappers.add(self)
|
self.inherits._inheriting_mappers.add(self)
|
||||||
self.passive_updates = self.inherits.passive_updates
|
self.passive_updates = self.inherits.passive_updates
|
||||||
self._all_tables = self.inherits._all_tables
|
self._all_tables = self.inherits._all_tables
|
||||||
|
for key, prop in mapper._props.iteritems():
|
||||||
|
if key not in self._props and \
|
||||||
|
not self._should_exclude(key, key, local=False,
|
||||||
|
column=None):
|
||||||
|
self._adapt_inherited_property(key, prop, False)
|
||||||
|
|
||||||
|
|
||||||
def _set_polymorphic_on(self, polymorphic_on):
|
def _set_polymorphic_on(self, polymorphic_on):
|
||||||
self.polymorphic_on = polymorphic_on
|
self.polymorphic_on = polymorphic_on
|
||||||
@@ -678,9 +684,10 @@ class Mapper(object):
|
|||||||
self._reconstructor = method
|
self._reconstructor = method
|
||||||
event.listen(manager, 'load', _event_on_load, raw=True)
|
event.listen(manager, 'load', _event_on_load, raw=True)
|
||||||
elif hasattr(method, '__sa_validators__'):
|
elif hasattr(method, '__sa_validators__'):
|
||||||
|
include_removes = getattr(method, "__sa_include_removes__", False)
|
||||||
for name in method.__sa_validators__:
|
for name in method.__sa_validators__:
|
||||||
self.validators = self.validators.union(
|
self.validators = self.validators.union(
|
||||||
{name : method}
|
{name : (method, include_removes)}
|
||||||
)
|
)
|
||||||
|
|
||||||
manager.info[_INSTRUMENTOR] = self
|
manager.info[_INSTRUMENTOR] = self
|
||||||
@@ -940,7 +947,6 @@ class Mapper(object):
|
|||||||
# with_polymorphic selectable, as when use polymorphic_union.
|
# with_polymorphic selectable, as when use polymorphic_union.
|
||||||
# we'll make a separate ColumnProperty for it.
|
# we'll make a separate ColumnProperty for it.
|
||||||
instrument = True
|
instrument = True
|
||||||
|
|
||||||
key = getattr(col, 'key', None)
|
key = getattr(col, 'key', None)
|
||||||
if key:
|
if key:
|
||||||
if self._should_exclude(col.key, col.key, False, col):
|
if self._should_exclude(col.key, col.key, False, col):
|
||||||
@@ -2291,7 +2297,7 @@ def reconstructor(fn):
|
|||||||
fn.__sa_reconstructor__ = True
|
fn.__sa_reconstructor__ = True
|
||||||
return fn
|
return fn
|
||||||
|
|
||||||
def validates(*names):
|
def validates(*names, **kw):
|
||||||
"""Decorate a method as a 'validator' for one or more named properties.
|
"""Decorate a method as a 'validator' for one or more named properties.
|
||||||
|
|
||||||
Designates a method as a validator, a method which receives the
|
Designates a method as a validator, a method which receives the
|
||||||
@@ -2307,9 +2313,18 @@ def validates(*names):
|
|||||||
an assertion to avoid recursion overflows. This is a reentrant
|
an assertion to avoid recursion overflows. This is a reentrant
|
||||||
condition which is not supported.
|
condition which is not supported.
|
||||||
|
|
||||||
|
:param \*names: list of attribute names to be validated.
|
||||||
|
:param include_removes: if True, "remove" events will be
|
||||||
|
sent as well - the validation function must accept an additional
|
||||||
|
argument "is_remove" which will be a boolean.
|
||||||
|
|
||||||
|
.. versionadded:: 0.7.7
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
include_removes = kw.pop('include_removes', False)
|
||||||
def wrap(fn):
|
def wrap(fn):
|
||||||
fn.__sa_validators__ = names
|
fn.__sa_validators__ = names
|
||||||
|
fn.__sa_include_removes__ = include_removes
|
||||||
return fn
|
return fn
|
||||||
return wrap
|
return wrap
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# orm/persistence.py
|
# orm/persistence.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -466,7 +466,7 @@ def _emit_update_statements(base_mapper, uowtransaction,
|
|||||||
if needs_version_id:
|
if needs_version_id:
|
||||||
clause.clauses.append(mapper.version_id_col ==\
|
clause.clauses.append(mapper.version_id_col ==\
|
||||||
sql.bindparam(mapper.version_id_col._label,
|
sql.bindparam(mapper.version_id_col._label,
|
||||||
type_=col.type))
|
type_=mapper.version_id_col.type))
|
||||||
|
|
||||||
return table.update(clause)
|
return table.update(clause)
|
||||||
|
|
||||||
@@ -748,13 +748,15 @@ def _connections_for_states(base_mapper, uowtransaction, states):
|
|||||||
connection_callable = \
|
connection_callable = \
|
||||||
uowtransaction.session.connection_callable
|
uowtransaction.session.connection_callable
|
||||||
else:
|
else:
|
||||||
connection = uowtransaction.transaction.connection(
|
connection = None
|
||||||
base_mapper)
|
|
||||||
connection_callable = None
|
connection_callable = None
|
||||||
|
|
||||||
for state in _sort_states(states):
|
for state in _sort_states(states):
|
||||||
if connection_callable:
|
if connection_callable:
|
||||||
connection = connection_callable(base_mapper, state.obj())
|
connection = connection_callable(base_mapper, state.obj())
|
||||||
|
elif not connection:
|
||||||
|
connection = uowtransaction.transaction.connection(
|
||||||
|
base_mapper)
|
||||||
|
|
||||||
mapper = _state_mapper(state)
|
mapper = _state_mapper(state)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# orm/properties.py
|
# orm/properties.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -136,7 +136,9 @@ class ColumnProperty(StrategizedProperty):
|
|||||||
|
|
||||||
def merge(self, session, source_state, source_dict, dest_state,
|
def merge(self, session, source_state, source_dict, dest_state,
|
||||||
dest_dict, load, _recursive):
|
dest_dict, load, _recursive):
|
||||||
if self.key in source_dict:
|
if not self.instrument:
|
||||||
|
return
|
||||||
|
elif self.key in source_dict:
|
||||||
value = source_dict[self.key]
|
value = source_dict[self.key]
|
||||||
|
|
||||||
if not load:
|
if not load:
|
||||||
@@ -144,9 +146,8 @@ class ColumnProperty(StrategizedProperty):
|
|||||||
else:
|
else:
|
||||||
impl = dest_state.get_impl(self.key)
|
impl = dest_state.get_impl(self.key)
|
||||||
impl.set(dest_state, dest_dict, value, None)
|
impl.set(dest_state, dest_dict, value, None)
|
||||||
else:
|
elif dest_state.has_identity and self.key not in dest_dict:
|
||||||
if dest_state.has_identity and self.key not in dest_dict:
|
dest_state.expire_attributes(dest_dict, [self.key])
|
||||||
dest_state.expire_attributes(dest_dict, [self.key])
|
|
||||||
|
|
||||||
class Comparator(PropComparator):
|
class Comparator(PropComparator):
|
||||||
@util.memoized_instancemethod
|
@util.memoized_instancemethod
|
||||||
@@ -322,12 +323,6 @@ class RelationshipProperty(StrategizedProperty):
|
|||||||
else:
|
else:
|
||||||
return elem
|
return elem
|
||||||
|
|
||||||
def operate(self, op, *other, **kwargs):
|
|
||||||
return op(self, *other, **kwargs)
|
|
||||||
|
|
||||||
def reverse_operate(self, op, other, **kwargs):
|
|
||||||
return op(self, *other, **kwargs)
|
|
||||||
|
|
||||||
def of_type(self, cls):
|
def of_type(self, cls):
|
||||||
"""Produce a construct that represents a particular 'subtype' of
|
"""Produce a construct that represents a particular 'subtype' of
|
||||||
attribute for the parent class.
|
attribute for the parent class.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# orm/query.py
|
# orm/query.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -77,6 +77,7 @@ class Query(object):
|
|||||||
_group_by = False
|
_group_by = False
|
||||||
_having = None
|
_having = None
|
||||||
_distinct = False
|
_distinct = False
|
||||||
|
_prefixes = None
|
||||||
_offset = None
|
_offset = None
|
||||||
_limit = None
|
_limit = None
|
||||||
_statement = None
|
_statement = None
|
||||||
@@ -454,7 +455,7 @@ class Query(object):
|
|||||||
"""Return the full SELECT statement represented by this :class:`.Query`
|
"""Return the full SELECT statement represented by this :class:`.Query`
|
||||||
represented as a common table expression (CTE).
|
represented as a common table expression (CTE).
|
||||||
|
|
||||||
The :meth:`.Query.cte` method is new in 0.7.6.
|
.. versionadded:: 0.7.6
|
||||||
|
|
||||||
Parameters and usage are the same as those of the
|
Parameters and usage are the same as those of the
|
||||||
:meth:`._SelectBase.cte` method; see that method for
|
:meth:`._SelectBase.cte` method; see that method for
|
||||||
@@ -512,7 +513,7 @@ class Query(object):
|
|||||||
|
|
||||||
Analogous to :meth:`sqlalchemy.sql._SelectBaseMixin.label`.
|
Analogous to :meth:`sqlalchemy.sql._SelectBaseMixin.label`.
|
||||||
|
|
||||||
New in 0.6.5.
|
.. versionadded:: 0.6.5
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -525,7 +526,7 @@ class Query(object):
|
|||||||
|
|
||||||
Analogous to :meth:`sqlalchemy.sql._SelectBaseMixin.as_scalar`.
|
Analogous to :meth:`sqlalchemy.sql._SelectBaseMixin.as_scalar`.
|
||||||
|
|
||||||
New in 0.6.5.
|
.. versionadded:: 0.6.5
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -917,7 +918,8 @@ class Query(object):
|
|||||||
'_order_by', '_group_by',
|
'_order_by', '_group_by',
|
||||||
'_limit', '_offset',
|
'_limit', '_offset',
|
||||||
'_joinpath', '_joinpoint',
|
'_joinpath', '_joinpoint',
|
||||||
'_distinct', '_having'
|
'_distinct', '_having',
|
||||||
|
'_prefixes',
|
||||||
):
|
):
|
||||||
self.__dict__.pop(attr, None)
|
self.__dict__.pop(attr, None)
|
||||||
self._set_select_from(fromclause)
|
self._set_select_from(fromclause)
|
||||||
@@ -979,7 +981,7 @@ class Query(object):
|
|||||||
q = q.join((subq, subq.c.email < Address.email)).\\
|
q = q.join((subq, subq.c.email < Address.email)).\\
|
||||||
limit(1)
|
limit(1)
|
||||||
|
|
||||||
New in 0.6.5.
|
.. versionadded:: 0.6.5
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self._set_entities(entities)
|
self._set_entities(entities)
|
||||||
@@ -1056,7 +1058,7 @@ class Query(object):
|
|||||||
This allows ad-hoc recipes to be created for :class:`.Query`
|
This allows ad-hoc recipes to be created for :class:`.Query`
|
||||||
objects. See the example at :ref:`hybrid_transformers`.
|
objects. See the example at :ref:`hybrid_transformers`.
|
||||||
|
|
||||||
:meth:`~.Query.with_transformation` is new in SQLAlchemy 0.7.4.
|
.. versionadded:: 0.7.4
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return fn(self)
|
return fn(self)
|
||||||
@@ -1103,10 +1105,18 @@ class Query(object):
|
|||||||
``FOR UPDATE`` (standard SQL, supported by most dialects)
|
``FOR UPDATE`` (standard SQL, supported by most dialects)
|
||||||
|
|
||||||
``'update_nowait'`` - passes ``for_update='nowait'``, which
|
``'update_nowait'`` - passes ``for_update='nowait'``, which
|
||||||
translates to ``FOR UPDATE NOWAIT`` (supported by Oracle)
|
translates to ``FOR UPDATE NOWAIT`` (supported by Oracle,
|
||||||
|
PostgreSQL 8.1 upwards)
|
||||||
|
|
||||||
``'read'`` - passes ``for_update='read'``, which translates to
|
``'read'`` - passes ``for_update='read'``, which translates to
|
||||||
``LOCK IN SHARE MODE`` (supported by MySQL).
|
``LOCK IN SHARE MODE`` (for MySQL), and ``FOR SHARE`` (for
|
||||||
|
PostgreSQL)
|
||||||
|
|
||||||
|
``'read_nowait'`` - passes ``for_update='read_nowait'``, which
|
||||||
|
translates to ``FOR SHARE NOWAIT`` (supported by PostgreSQL).
|
||||||
|
|
||||||
|
.. versionadded:: 0.7.7
|
||||||
|
``FOR SHARE`` and ``FOR SHARE NOWAIT`` (PostgreSQL).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self._lockmode = mode
|
self._lockmode = mode
|
||||||
@@ -1140,7 +1150,7 @@ class Query(object):
|
|||||||
|
|
||||||
session.query(MyClass).filter(MyClass.name == 'some name')
|
session.query(MyClass).filter(MyClass.name == 'some name')
|
||||||
|
|
||||||
Multiple criteria are joined together by AND (new in 0.7.5)::
|
Multiple criteria are joined together by AND::
|
||||||
|
|
||||||
session.query(MyClass).\\
|
session.query(MyClass).\\
|
||||||
filter(MyClass.name == 'some name', MyClass.id > 5)
|
filter(MyClass.name == 'some name', MyClass.id > 5)
|
||||||
@@ -1149,6 +1159,9 @@ class Query(object):
|
|||||||
WHERE clause of a select. String expressions are coerced
|
WHERE clause of a select. String expressions are coerced
|
||||||
into SQL expression constructs via the :func:`.text` construct.
|
into SQL expression constructs via the :func:`.text` construct.
|
||||||
|
|
||||||
|
.. versionchanged:: 0.7.5
|
||||||
|
Multiple criteria joined by AND.
|
||||||
|
|
||||||
See also:
|
See also:
|
||||||
|
|
||||||
:meth:`.Query.filter_by` - filter on keyword expressions.
|
:meth:`.Query.filter_by` - filter on keyword expressions.
|
||||||
@@ -1233,7 +1246,7 @@ class Query(object):
|
|||||||
@_generative(_no_statement_condition, _no_limit_offset)
|
@_generative(_no_statement_condition, _no_limit_offset)
|
||||||
def group_by(self, *criterion):
|
def group_by(self, *criterion):
|
||||||
"""apply one or more GROUP BY criterion to the query and return
|
"""apply one or more GROUP BY criterion to the query and return
|
||||||
the newly resulting ``Query``"""
|
the newly resulting :class:`.Query`"""
|
||||||
|
|
||||||
criterion = list(chain(*[_orm_columns(c) for c in criterion]))
|
criterion = list(chain(*[_orm_columns(c) for c in criterion]))
|
||||||
|
|
||||||
@@ -1247,7 +1260,19 @@ class Query(object):
|
|||||||
@_generative(_no_statement_condition, _no_limit_offset)
|
@_generative(_no_statement_condition, _no_limit_offset)
|
||||||
def having(self, criterion):
|
def having(self, criterion):
|
||||||
"""apply a HAVING criterion to the query and return the
|
"""apply a HAVING criterion to the query and return the
|
||||||
newly resulting ``Query``."""
|
newly resulting :class:`.Query`.
|
||||||
|
|
||||||
|
:meth:`having` is used in conjunction with :meth:`group_by`.
|
||||||
|
|
||||||
|
HAVING criterion makes it possible to use filters on aggregate
|
||||||
|
functions like COUNT, SUM, AVG, MAX, and MIN, eg.::
|
||||||
|
|
||||||
|
q = session.query(User.id).\\
|
||||||
|
join(User.addresses).\\
|
||||||
|
group_by(User.id).\\
|
||||||
|
having(func.count(Address.id) > 2)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
if isinstance(criterion, basestring):
|
if isinstance(criterion, basestring):
|
||||||
criterion = sql.text(criterion)
|
criterion = sql.text(criterion)
|
||||||
@@ -1460,18 +1485,15 @@ class Query(object):
|
|||||||
|
|
||||||
q = session.query(User).join(Address, User.id==Address.user_id)
|
q = session.query(User).join(Address, User.id==Address.user_id)
|
||||||
|
|
||||||
.. note::
|
.. versionchanged:: 0.7
|
||||||
|
In SQLAlchemy 0.6 and earlier, the two argument form of
|
||||||
In SQLAlchemy 0.6 and earlier, the two argument form of
|
:meth:`~.Query.join` requires the usage of a tuple:
|
||||||
:meth:`~.Query.join` requires the usage of a tuple::
|
``query(User).join((Address, User.id==Address.user_id))``\ .
|
||||||
|
This calling form is accepted in 0.7 and further, though
|
||||||
query(User).join((Address, User.id==Address.user_id))
|
is not necessary unless multiple join conditions are passed to
|
||||||
|
a single :meth:`~.Query.join` call, which itself is also not
|
||||||
This calling form is accepted in 0.7 and further, though
|
generally necessary as it is now equivalent to multiple
|
||||||
is not necessary unless multiple join conditions are passed to
|
calls (this wasn't always the case).
|
||||||
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**
|
**Advanced Join Targeting and Adaption**
|
||||||
|
|
||||||
@@ -1481,7 +1503,7 @@ class Query(object):
|
|||||||
and :func:`.select` constructs, with either the one or two-argument forms::
|
and :func:`.select` constructs, with either the one or two-argument forms::
|
||||||
|
|
||||||
addresses_q = select([Address.user_id]).\\
|
addresses_q = select([Address.user_id]).\\
|
||||||
filter(Address.email_address.endswith("@bar.com")).\\
|
where(Address.email_address.endswith("@bar.com")).\\
|
||||||
alias()
|
alias()
|
||||||
|
|
||||||
q = session.query(User).\\
|
q = session.query(User).\\
|
||||||
@@ -2057,6 +2079,33 @@ class Query(object):
|
|||||||
else:
|
else:
|
||||||
self._distinct = criterion
|
self._distinct = criterion
|
||||||
|
|
||||||
|
@_generative()
|
||||||
|
def prefix_with(self, *prefixes):
|
||||||
|
"""Apply the prefixes to the query and return the newly resulting
|
||||||
|
``Query``.
|
||||||
|
|
||||||
|
:param \*prefixes: optional prefixes, typically strings,
|
||||||
|
not using any commas. In particular is useful for MySQL keywords.
|
||||||
|
|
||||||
|
e.g.::
|
||||||
|
|
||||||
|
query = sess.query(User.name).\\
|
||||||
|
prefix_with('HIGH_PRIORITY').\\
|
||||||
|
prefix_with('SQL_SMALL_RESULT', 'ALL')
|
||||||
|
|
||||||
|
Would render::
|
||||||
|
|
||||||
|
SELECT HIGH_PRIORITY SQL_SMALL_RESULT ALL users.name AS users_name
|
||||||
|
FROM users
|
||||||
|
|
||||||
|
.. versionadded:: 0.7.7
|
||||||
|
|
||||||
|
"""
|
||||||
|
if self._prefixes:
|
||||||
|
self._prefixes += prefixes
|
||||||
|
else:
|
||||||
|
self._prefixes = prefixes
|
||||||
|
|
||||||
def all(self):
|
def all(self):
|
||||||
"""Return the results represented by this ``Query`` as a list.
|
"""Return the results represented by this ``Query`` as a list.
|
||||||
|
|
||||||
@@ -2125,9 +2174,11 @@ class Query(object):
|
|||||||
unique entity or entities - this is a successful result for one().
|
unique entity or entities - this is a successful result for one().
|
||||||
|
|
||||||
Calling ``one()`` results in an execution of the underlying query.
|
Calling ``one()`` results in an execution of the underlying query.
|
||||||
As of 0.6, ``one()`` fully fetches all results instead of applying
|
|
||||||
any kind of limit, so that the "unique"-ing of entities does not
|
.. versionchanged:: 0.6
|
||||||
conceal multiple object identities.
|
``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)
|
ret = list(self)
|
||||||
@@ -2364,14 +2415,16 @@ class Query(object):
|
|||||||
mapped_entities = [i for i, e in enumerate(self._entities)
|
mapped_entities = [i for i, e in enumerate(self._entities)
|
||||||
if isinstance(e, _MapperEntity)]
|
if isinstance(e, _MapperEntity)]
|
||||||
result = []
|
result = []
|
||||||
|
keys = [ent._label_name for ent in self._entities]
|
||||||
for row in iterator:
|
for row in iterator:
|
||||||
newrow = list(row)
|
newrow = list(row)
|
||||||
for i in mapped_entities:
|
for i in mapped_entities:
|
||||||
newrow[i] = session._merge(
|
if newrow[i] is not None:
|
||||||
attributes.instance_state(newrow[i]),
|
newrow[i] = session._merge(
|
||||||
attributes.instance_dict(newrow[i]),
|
attributes.instance_state(newrow[i]),
|
||||||
load=load, _recursive={})
|
attributes.instance_dict(newrow[i]),
|
||||||
result.append(util.NamedTuple(newrow, row._labels))
|
load=load, _recursive={})
|
||||||
|
result.append(util.NamedTuple(newrow, keys))
|
||||||
|
|
||||||
return iter(result)
|
return iter(result)
|
||||||
finally:
|
finally:
|
||||||
@@ -2468,6 +2521,7 @@ class Query(object):
|
|||||||
'limit':self._limit,
|
'limit':self._limit,
|
||||||
'offset':self._offset,
|
'offset':self._offset,
|
||||||
'distinct':self._distinct,
|
'distinct':self._distinct,
|
||||||
|
'prefixes':self._prefixes,
|
||||||
'group_by':self._group_by or None,
|
'group_by':self._group_by or None,
|
||||||
'having':self._having
|
'having':self._having
|
||||||
}
|
}
|
||||||
@@ -2488,8 +2542,8 @@ class Query(object):
|
|||||||
SELECT <rest of query follows...>
|
SELECT <rest of query follows...>
|
||||||
) AS anon_1
|
) AS anon_1
|
||||||
|
|
||||||
Note the above scheme is newly refined in 0.7
|
.. versionchanged:: 0.7
|
||||||
(as of 0.7b3).
|
The above scheme is newly refined as of 0.7b3.
|
||||||
|
|
||||||
For fine grained control over specific columns
|
For fine grained control over specific columns
|
||||||
to count, to skip the usage of a subquery or
|
to count, to skip the usage of a subquery or
|
||||||
@@ -2807,6 +2861,7 @@ class Query(object):
|
|||||||
if self._lockmode:
|
if self._lockmode:
|
||||||
try:
|
try:
|
||||||
for_update = {'read': 'read',
|
for_update = {'read': 'read',
|
||||||
|
'read_nowait': 'read_nowait',
|
||||||
'update': True,
|
'update': True,
|
||||||
'update_nowait': 'nowait',
|
'update_nowait': 'nowait',
|
||||||
None: False}[self._lockmode]
|
None: False}[self._lockmode]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# orm/scoping.py
|
# orm/scoping.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
+272
-87
@@ -1,5 +1,5 @@
|
|||||||
# orm/session.py
|
# orm/session.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
@@ -103,22 +103,61 @@ def sessionmaker(bind=None, class_=None, autoflush=True, autocommit=False,
|
|||||||
|
|
||||||
|
|
||||||
class SessionTransaction(object):
|
class SessionTransaction(object):
|
||||||
"""A Session-level transaction.
|
"""A :class:`.Session`-level transaction.
|
||||||
|
|
||||||
This corresponds to one or more Core :class:`~.engine.base.Transaction`
|
:class:`.SessionTransaction` is a mostly behind-the-scenes object
|
||||||
instances behind the scenes, with one :class:`~.engine.base.Transaction`
|
not normally referenced directly by application code. It coordinates
|
||||||
per :class:`~.engine.base.Engine` in use.
|
among multiple :class:`.Connection` objects, maintaining a database
|
||||||
|
transaction for each one individually, committing or rolling them
|
||||||
|
back all at once. It also provides optional two-phase commit behavior
|
||||||
|
which can augment this coordination operation.
|
||||||
|
|
||||||
Direct usage of :class:`.SessionTransaction` is not typically
|
The :attr:`.Session.transaction` attribute of :class:`.Session` refers to the
|
||||||
necessary as of SQLAlchemy 0.4; use the :meth:`.Session.rollback` and
|
current :class:`.SessionTransaction` object in use, if any.
|
||||||
:meth:`.Session.commit` methods on :class:`.Session` itself to
|
|
||||||
control the transaction.
|
|
||||||
|
|
||||||
The current instance of :class:`.SessionTransaction` for a given
|
|
||||||
:class:`.Session` is available via the :attr:`.Session.transaction`
|
|
||||||
attribute.
|
|
||||||
|
|
||||||
The :class:`.SessionTransaction` object is **not** thread-safe.
|
A :class:`.SessionTransaction` is associated with a :class:`.Session`
|
||||||
|
in its default mode of ``autocommit=False`` immediately, associated
|
||||||
|
with no database connections. As the :class:`.Session` is called upon
|
||||||
|
to emit SQL on behalf of various :class:`.Engine` or :class:`.Connection`
|
||||||
|
objects, a corresponding :class:`.Connection` and associated :class:`.Transaction`
|
||||||
|
is added to a collection within the :class:`.SessionTransaction` object,
|
||||||
|
becoming one of the connection/transaction pairs maintained by the
|
||||||
|
:class:`.SessionTransaction`.
|
||||||
|
|
||||||
|
The lifespan of the :class:`.SessionTransaction` ends when the
|
||||||
|
:meth:`.Session.commit`, :meth:`.Session.rollback` or :meth:`.Session.close`
|
||||||
|
methods are called. At this point, the :class:`.SessionTransaction` removes
|
||||||
|
its association with its parent :class:`.Session`. A :class:`.Session`
|
||||||
|
that is in ``autocommit=False`` mode will create a new
|
||||||
|
:class:`.SessionTransaction` to replace it immediately, whereas a
|
||||||
|
:class:`.Session` that's in ``autocommit=True``
|
||||||
|
mode will remain without a :class:`.SessionTransaction` until the
|
||||||
|
:meth:`.Session.begin` method is called.
|
||||||
|
|
||||||
|
Another detail of :class:`.SessionTransaction` behavior is that it is
|
||||||
|
capable of "nesting". This means that the :meth:`.begin` method can
|
||||||
|
be called while an existing :class:`.SessionTransaction` is already present,
|
||||||
|
producing a new :class:`.SessionTransaction` that temporarily replaces
|
||||||
|
the parent :class:`.SessionTransaction`. When a :class:`.SessionTransaction`
|
||||||
|
is produced as nested, it assigns itself to the :attr:`.Session.transaction`
|
||||||
|
attribute. When it is ended via :meth:`.Session.commit` or :meth:`.Session.rollback`,
|
||||||
|
it restores its parent :class:`.SessionTransaction` back onto the
|
||||||
|
:attr:`.Session.transaction` attribute. The
|
||||||
|
behavior is effectively a stack, where :attr:`.Session.transaction` refers
|
||||||
|
to the current head of the stack.
|
||||||
|
|
||||||
|
The purpose of this stack is to allow nesting of :meth:`.rollback` or
|
||||||
|
:meth:`.commit` calls in context with various flavors of :meth:`.begin`.
|
||||||
|
This nesting behavior applies to when :meth:`.Session.begin_nested`
|
||||||
|
is used to emit a SAVEPOINT transaction, and is also used to produce
|
||||||
|
a so-called "subtransaction" which allows a block of code to use a
|
||||||
|
begin/rollback/commit sequence regardless of whether or not its enclosing
|
||||||
|
code block has begun a transaction. The :meth:`.flush` method, whether called
|
||||||
|
explicitly or via autoflush, is the primary consumer of the "subtransaction"
|
||||||
|
feature, in that it wishes to guarantee that it works within in a transaction block
|
||||||
|
regardless of whether or not the :class:`.Session` is in transactional mode
|
||||||
|
when the method is called.
|
||||||
|
|
||||||
See also:
|
See also:
|
||||||
|
|
||||||
@@ -126,6 +165,10 @@ class SessionTransaction(object):
|
|||||||
|
|
||||||
:meth:`.Session.commit`
|
:meth:`.Session.commit`
|
||||||
|
|
||||||
|
:meth:`.Session.begin`
|
||||||
|
|
||||||
|
:meth:`.Session.begin_nested`
|
||||||
|
|
||||||
:attr:`.Session.is_active`
|
:attr:`.Session.is_active`
|
||||||
|
|
||||||
:meth:`.SessionEvents.after_commit`
|
:meth:`.SessionEvents.after_commit`
|
||||||
@@ -134,9 +177,6 @@ class SessionTransaction(object):
|
|||||||
|
|
||||||
:meth:`.SessionEvents.after_soft_rollback`
|
:meth:`.SessionEvents.after_soft_rollback`
|
||||||
|
|
||||||
.. index::
|
|
||||||
single: thread safety; SessionTransaction
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_rollback_exception = None
|
_rollback_exception = None
|
||||||
@@ -211,6 +251,7 @@ class SessionTransaction(object):
|
|||||||
if not self._is_transaction_boundary:
|
if not self._is_transaction_boundary:
|
||||||
self._new = self._parent._new
|
self._new = self._parent._new
|
||||||
self._deleted = self._parent._deleted
|
self._deleted = self._parent._deleted
|
||||||
|
self._key_switches = self._parent._key_switches
|
||||||
return
|
return
|
||||||
|
|
||||||
if not self.session._flushing:
|
if not self.session._flushing:
|
||||||
@@ -218,6 +259,7 @@ class SessionTransaction(object):
|
|||||||
|
|
||||||
self._new = weakref.WeakKeyDictionary()
|
self._new = weakref.WeakKeyDictionary()
|
||||||
self._deleted = weakref.WeakKeyDictionary()
|
self._deleted = weakref.WeakKeyDictionary()
|
||||||
|
self._key_switches = weakref.WeakKeyDictionary()
|
||||||
|
|
||||||
def _restore_snapshot(self):
|
def _restore_snapshot(self):
|
||||||
assert self._is_transaction_boundary
|
assert self._is_transaction_boundary
|
||||||
@@ -227,11 +269,16 @@ class SessionTransaction(object):
|
|||||||
if s.key:
|
if s.key:
|
||||||
del s.key
|
del s.key
|
||||||
|
|
||||||
|
for s, (oldkey, newkey) in self._key_switches.items():
|
||||||
|
self.session.identity_map.discard(s)
|
||||||
|
s.key = oldkey
|
||||||
|
self.session.identity_map.replace(s)
|
||||||
|
|
||||||
for s in set(self._deleted).union(self.session._deleted):
|
for s in set(self._deleted).union(self.session._deleted):
|
||||||
if s.deleted:
|
if s.deleted:
|
||||||
#assert s in self._deleted
|
#assert s in self._deleted
|
||||||
del s.deleted
|
del s.deleted
|
||||||
self.session._update_impl(s)
|
self.session._update_impl(s, discard_existing=True)
|
||||||
|
|
||||||
assert not self.session._deleted
|
assert not self.session._deleted
|
||||||
|
|
||||||
@@ -294,7 +341,15 @@ class SessionTransaction(object):
|
|||||||
subtransaction.commit()
|
subtransaction.commit()
|
||||||
|
|
||||||
if not self.session._flushing:
|
if not self.session._flushing:
|
||||||
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:
|
if self._parent is None and self.session.twophase:
|
||||||
try:
|
try:
|
||||||
@@ -735,49 +790,99 @@ class Session(object):
|
|||||||
return engine.contextual_connect(**kwargs)
|
return engine.contextual_connect(**kwargs)
|
||||||
|
|
||||||
def execute(self, clause, params=None, mapper=None, bind=None, **kw):
|
def execute(self, clause, params=None, mapper=None, bind=None, **kw):
|
||||||
"""Execute a clause within the current transaction.
|
"""Execute a SQL expression construct or string statement within
|
||||||
|
the current transaction.
|
||||||
|
|
||||||
Returns a :class:`.ResultProxy` representing
|
Returns a :class:`.ResultProxy` representing
|
||||||
results of the statement execution, in the same manner as that of an
|
results of the statement execution, in the same manner as that of an
|
||||||
:class:`.Engine` or
|
:class:`.Engine` or
|
||||||
:class:`.Connection`.
|
:class:`.Connection`.
|
||||||
|
|
||||||
|
E.g.::
|
||||||
|
|
||||||
|
result = session.execute(
|
||||||
|
user_table.select().where(user_table.c.id == 5)
|
||||||
|
)
|
||||||
|
|
||||||
:meth:`~.Session.execute` accepts any executable clause construct, such
|
:meth:`~.Session.execute` accepts any executable clause construct, such
|
||||||
as :func:`~.sql.expression.select`,
|
as :func:`~.sql.expression.select`,
|
||||||
:func:`~.sql.expression.insert`,
|
:func:`~.sql.expression.insert`,
|
||||||
:func:`~.sql.expression.update`,
|
:func:`~.sql.expression.update`,
|
||||||
:func:`~.sql.expression.delete`, and
|
:func:`~.sql.expression.delete`, and
|
||||||
:func:`~.sql.expression.text`, and additionally accepts
|
:func:`~.sql.expression.text`. Plain SQL strings can be passed
|
||||||
plain strings that represent SQL statements. If a plain string is
|
as well, which in the case of :meth:`.Session.execute` only
|
||||||
passed, it is first converted to a
|
will be interpreted the same as if it were passed via a :func:`~.expression.text`
|
||||||
:func:`~.sql.expression.text` construct, which here means
|
construct. That is, the following usage::
|
||||||
that bind parameters should be specified using the format ``:param``.
|
|
||||||
If raw DBAPI statement execution is desired, use :meth:`.Session.connection`
|
result = session.execute(
|
||||||
to acquire a :class:`.Connection`, then call its :meth:`~.Connection.execute`
|
"SELECT * FROM user WHERE id=:param",
|
||||||
method.
|
{"param":5}
|
||||||
|
)
|
||||||
|
|
||||||
|
is equivalent to::
|
||||||
|
|
||||||
|
from sqlalchemy import text
|
||||||
|
result = session.execute(
|
||||||
|
text("SELECT * FROM user WHERE id=:param"),
|
||||||
|
{"param":5}
|
||||||
|
)
|
||||||
|
|
||||||
|
The second positional argument to :meth:`.Session.execute` is an
|
||||||
|
optional parameter set. Similar to that of :meth:`.Connection.execute`, whether this
|
||||||
|
is passed as a single dictionary, or a list of dictionaries, determines
|
||||||
|
whether the DBAPI cursor's ``execute()`` or ``executemany()`` is used to execute the
|
||||||
|
statement. An INSERT construct may be invoked for a single row::
|
||||||
|
|
||||||
|
result = session.execute(users.insert(), {"id": 7, "name": "somename"})
|
||||||
|
|
||||||
|
or for multiple rows::
|
||||||
|
|
||||||
|
result = session.execute(users.insert(), [
|
||||||
|
{"id": 7, "name": "somename7"},
|
||||||
|
{"id": 8, "name": "somename8"},
|
||||||
|
{"id": 9, "name": "somename9"}
|
||||||
|
])
|
||||||
|
|
||||||
The statement is executed within the current transactional context of
|
The statement is executed within the current transactional context of
|
||||||
this :class:`.Session`, using the same behavior as that of
|
this :class:`.Session`. The :class:`.Connection` which is used
|
||||||
the :meth:`.Session.connection` method to determine the active
|
to execute the statement can also be acquired directly by
|
||||||
:class:`.Connection`. The ``close_with_result`` flag is
|
calling the :meth:`.Session.connection` method. Both methods use
|
||||||
set to ``True`` so that an ``autocommit=True`` :class:`.Session`
|
a rule-based resolution scheme in order to determine the
|
||||||
with no active transaction will produce a result that auto-closes
|
:class:`.Connection`, which in the average case is derived directly
|
||||||
the underlying :class:`.Connection`.
|
from the "bind" of the :class:`.Session` itself, and in other cases
|
||||||
|
can be based on the :func:`.mapper`
|
||||||
|
and :class:`.Table` objects passed to the method; see the documentation
|
||||||
|
for :meth:`.Session.get_bind` for a full description of this scheme.
|
||||||
|
|
||||||
|
The :meth:`.Session.execute` method does *not* invoke autoflush.
|
||||||
|
|
||||||
|
The :class:`.ResultProxy` returned by the :meth:`.Session.execute`
|
||||||
|
method is returned with the "close_with_result" flag set to true;
|
||||||
|
the significance of this flag is that if this :class:`.Session` is
|
||||||
|
autocommitting and does not have a transaction-dedicated :class:`.Connection`
|
||||||
|
available, a temporary :class:`.Connection` is established for the
|
||||||
|
statement execution, which is closed (meaning, returned to the connection
|
||||||
|
pool) when the :class:`.ResultProxy` has consumed all available data.
|
||||||
|
This applies *only* when the :class:`.Session` is configured with
|
||||||
|
autocommit=True and no transaction has been started.
|
||||||
|
|
||||||
:param clause:
|
:param clause:
|
||||||
A :class:`.ClauseElement` (i.e. :func:`~.sql.expression.select`,
|
An executable statement (i.e. an :class:`.Executable` expression
|
||||||
:func:`~.sql.expression.text`, etc.) or string SQL statement to be executed. The clause
|
such as :func:`.expression.select`) or string SQL statement
|
||||||
will also be used to locate a bind, if this :class:`.Session`
|
to be executed.
|
||||||
is not bound to a single engine already, and the ``mapper``
|
|
||||||
and ``bind`` arguments are not passed.
|
|
||||||
|
|
||||||
:param params:
|
:param params:
|
||||||
Optional dictionary of bind names mapped to values.
|
Optional dictionary, or list of dictionaries, containing
|
||||||
|
bound parameter values. If a single dictionary, single-row
|
||||||
|
execution occurs; if a list of dictionaries, an
|
||||||
|
"executemany" will be invoked. The keys in each dictionary
|
||||||
|
must correspond to parameter names present in the statement.
|
||||||
|
|
||||||
:param mapper:
|
:param mapper:
|
||||||
Optional :func:`.mapper` or mapped class, used to identify
|
Optional :func:`.mapper` or mapped class, used to identify
|
||||||
the appropriate bind. This argument takes precedence over
|
the appropriate bind. This argument takes precedence over
|
||||||
``clause`` when locating a bind.
|
``clause`` when locating a bind. See :meth:`.Session.get_bind`
|
||||||
|
for more details.
|
||||||
|
|
||||||
:param bind:
|
:param bind:
|
||||||
Optional :class:`.Engine` to be used as the bind. If
|
Optional :class:`.Engine` to be used as the bind. If
|
||||||
@@ -787,9 +892,20 @@ class Session(object):
|
|||||||
a bind.
|
a bind.
|
||||||
|
|
||||||
:param \**kw:
|
:param \**kw:
|
||||||
Additional keyword arguments are sent to :meth:`get_bind()`,
|
Additional keyword arguments are sent to :meth:`.Session.get_bind()`
|
||||||
allowing additional arguments to be passed to custom
|
to allow extensibility of "bind" schemes.
|
||||||
implementations of :meth:`get_bind`.
|
|
||||||
|
.. seealso::
|
||||||
|
|
||||||
|
:ref:`sqlexpression_toplevel` - Tutorial on using Core SQL
|
||||||
|
constructs.
|
||||||
|
|
||||||
|
:ref:`connections_toplevel` - Further information on direct
|
||||||
|
statement execution.
|
||||||
|
|
||||||
|
:meth:`.Connection.execute` - core level statement execution
|
||||||
|
method, which is :meth:`.Session.execute` ultimately uses
|
||||||
|
in order to execute the statement.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
clause = expression._literal_as_text(clause)
|
clause = expression._literal_as_text(clause)
|
||||||
@@ -998,7 +1114,7 @@ class Session(object):
|
|||||||
of objects which involve existing database queries,
|
of objects which involve existing database queries,
|
||||||
where the uncompleted object should not yet be flushed.
|
where the uncompleted object should not yet be flushed.
|
||||||
|
|
||||||
New in 0.7.6.
|
.. versionadded:: 0.7.6
|
||||||
|
|
||||||
"""
|
"""
|
||||||
autoflush = self.autoflush
|
autoflush = self.autoflush
|
||||||
@@ -1214,6 +1330,11 @@ class Session(object):
|
|||||||
# state has already replaced this one in the identity
|
# state has already replaced this one in the identity
|
||||||
# map (see test/orm/test_naturalpks.py ReversePKsTest)
|
# map (see test/orm/test_naturalpks.py ReversePKsTest)
|
||||||
self.identity_map.discard(state)
|
self.identity_map.discard(state)
|
||||||
|
if state in self.transaction._key_switches:
|
||||||
|
orig_key = self.transaction._key_switches[state][0]
|
||||||
|
else:
|
||||||
|
orig_key = state.key
|
||||||
|
self.transaction._key_switches[state] = (orig_key, instance_key)
|
||||||
state.key = instance_key
|
state.key = instance_key
|
||||||
|
|
||||||
self.identity_map.replace(state)
|
self.identity_map.replace(state)
|
||||||
@@ -1303,20 +1424,47 @@ class Session(object):
|
|||||||
self._delete_impl(st_)
|
self._delete_impl(st_)
|
||||||
|
|
||||||
def merge(self, instance, load=True, **kw):
|
def merge(self, instance, load=True, **kw):
|
||||||
"""Copy the state an instance onto the persistent instance with the
|
"""Copy the state of a given instance into a corresponding instance
|
||||||
same identifier.
|
within this :class:`.Session`.
|
||||||
|
|
||||||
If there is no persistent instance currently associated with the
|
:meth:`.Session.merge` examines the primary key attributes of the
|
||||||
session, it will be loaded. Return the persistent instance. If the
|
source instance, and attempts to reconcile it with an instance of the
|
||||||
given instance is unsaved, save a copy of and return it as a newly
|
same primary key in the session. If not found locally, it attempts
|
||||||
persistent instance. The given instance does not become associated
|
to load the object from the database based on primary key, and if
|
||||||
with the session.
|
none can be located, creates a new instance. The state of each attribute
|
||||||
|
on the source instance is then copied to the target instance.
|
||||||
|
The resulting target instance is then returned by the method; the
|
||||||
|
original source instance is left unmodified, and un-associated with the
|
||||||
|
:class:`.Session` if not already.
|
||||||
|
|
||||||
This operation cascades to associated instances if the association is
|
This operation cascades to associated instances if the association is
|
||||||
mapped with ``cascade="merge"``.
|
mapped with ``cascade="merge"``.
|
||||||
|
|
||||||
See :ref:`unitofwork_merging` for a detailed discussion of merging.
|
See :ref:`unitofwork_merging` for a detailed discussion of merging.
|
||||||
|
|
||||||
|
:param instance: Instance to be merged.
|
||||||
|
:param load: Boolean, when False, :meth:`.merge` switches into
|
||||||
|
a "high performance" mode which causes it to forego emitting history
|
||||||
|
events as well as all database access. This flag is used for
|
||||||
|
cases such as transferring graphs of objects into a :class:`.Session`
|
||||||
|
from a second level cache, or to transfer just-loaded objects
|
||||||
|
into the :class:`.Session` owned by a worker thread or process
|
||||||
|
without re-querying the database.
|
||||||
|
|
||||||
|
The ``load=False`` use case adds the caveat that the given
|
||||||
|
object has to be in a "clean" state, that is, has no pending changes
|
||||||
|
to be flushed - even if the incoming object is detached from any
|
||||||
|
:class:`.Session`. This is so that when
|
||||||
|
the merge operation populates local attributes and
|
||||||
|
cascades to related objects and
|
||||||
|
collections, the values can be "stamped" onto the
|
||||||
|
target object as is, without generating any history or attribute
|
||||||
|
events, and without the need to reconcile the incoming data with
|
||||||
|
any existing related objects or collections that might not
|
||||||
|
be loaded. The resulting objects from ``load=False`` are always
|
||||||
|
produced as "clean", so it is only appropriate that the given objects
|
||||||
|
should be "clean" as well, else this suggests a mis-use of the method.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if 'dont_load' in kw:
|
if 'dont_load' in kw:
|
||||||
load = not kw['dont_load']
|
load = not kw['dont_load']
|
||||||
@@ -1465,7 +1613,7 @@ class Session(object):
|
|||||||
self._new[state] = state.obj()
|
self._new[state] = state.obj()
|
||||||
state.insert_order = len(self._new)
|
state.insert_order = len(self._new)
|
||||||
|
|
||||||
def _update_impl(self, state):
|
def _update_impl(self, state, discard_existing=False):
|
||||||
if (self.identity_map.contains_state(state) and
|
if (self.identity_map.contains_state(state) and
|
||||||
state not in self._deleted):
|
state not in self._deleted):
|
||||||
return
|
return
|
||||||
@@ -1481,6 +1629,10 @@ class Session(object):
|
|||||||
"function to send this object back to the transient state." %
|
"function to send this object back to the transient state." %
|
||||||
mapperutil.state_str(state)
|
mapperutil.state_str(state)
|
||||||
)
|
)
|
||||||
|
if discard_existing:
|
||||||
|
existing = self.identity_map.get(state.key)
|
||||||
|
if existing is not None:
|
||||||
|
self.identity_map.discard(attributes.instance_state(existing))
|
||||||
self._attach(state)
|
self._attach(state)
|
||||||
self._deleted.pop(state, None)
|
self._deleted.pop(state, None)
|
||||||
self.identity_map.add(state)
|
self.identity_map.add(state)
|
||||||
@@ -1563,20 +1715,15 @@ class Session(object):
|
|||||||
will create a transaction on the fly that surrounds the entire set of
|
will create a transaction on the fly that surrounds the entire set of
|
||||||
operations int the flush.
|
operations int the flush.
|
||||||
|
|
||||||
objects
|
:param objects: Optional; restricts the flush operation to operate
|
||||||
Optional; a list or tuple collection. Restricts the flush operation
|
only on elements that are in the given collection.
|
||||||
to only these objects, rather than all pending changes.
|
|
||||||
Deprecated - this flag prevents the session from properly maintaining
|
This feature is for an extremely narrow set of use cases where
|
||||||
accounting among inter-object relations and can cause invalid results.
|
particular objects may need to be operated upon before the
|
||||||
|
full flush() occurs. It is not intended for general use.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if objects:
|
|
||||||
util.warn_deprecated(
|
|
||||||
"The 'objects' argument to session.flush() is deprecated; "
|
|
||||||
"Please do not add objects to the session which should not "
|
|
||||||
"yet be persisted.")
|
|
||||||
|
|
||||||
if self._flushing:
|
if self._flushing:
|
||||||
raise sa_exc.InvalidRequestError("Session is already flushing")
|
raise sa_exc.InvalidRequestError("Session is already flushing")
|
||||||
|
|
||||||
@@ -1695,17 +1842,16 @@ class Session(object):
|
|||||||
|
|
||||||
return session.is_modified(someobject, passive=True)
|
return session.is_modified(someobject, passive=True)
|
||||||
|
|
||||||
.. note::
|
.. versionchanged:: 0.8
|
||||||
|
In SQLAlchemy 0.7 and earlier, the ``passive``
|
||||||
In SQLAlchemy 0.7 and earlier, the ``passive``
|
flag should **always** be explicitly set to ``True``.
|
||||||
flag should **always** be explicitly set to ``True``.
|
The current default value of :data:`.attributes.PASSIVE_OFF`
|
||||||
The current default value of :data:`.attributes.PASSIVE_OFF`
|
for this flag is incorrect, in that it loads unloaded
|
||||||
for this flag is incorrect, in that it loads unloaded
|
collections and attributes which by definition
|
||||||
collections and attributes which by definition
|
have no modified state, and furthermore trips off
|
||||||
have no modified state, and furthermore trips off
|
autoflush which then causes all subsequent, possibly
|
||||||
autoflush which then causes all subsequent, possibly
|
modified attributes to lose their modified state.
|
||||||
modified attributes to lose their modified state.
|
The default value of the flag will be changed in 0.8.
|
||||||
The default value of the flag will be changed in 0.8.
|
|
||||||
|
|
||||||
A few caveats to this method apply:
|
A few caveats to this method apply:
|
||||||
|
|
||||||
@@ -1750,10 +1896,11 @@ class Session(object):
|
|||||||
is a bug, as unloaded attributes by definition have
|
is a bug, as unloaded attributes by definition have
|
||||||
no changes, and the load operation also triggers an
|
no changes, and the load operation also triggers an
|
||||||
autoflush which then cancels out subsequent changes.
|
autoflush which then cancels out subsequent changes.
|
||||||
This flag should **always be set to
|
This flag should **always be set to True**.
|
||||||
True**. In 0.8 the flag will be deprecated and the default
|
|
||||||
set to ``True``.
|
|
||||||
|
|
||||||
|
.. versionchanged:: 0.8
|
||||||
|
The flag will be deprecated and the default
|
||||||
|
set to ``True``.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
@@ -1784,20 +1931,58 @@ class Session(object):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def is_active(self):
|
def is_active(self):
|
||||||
"""True if this :class:`.Session` has an active transaction.
|
"""True if this :class:`.Session` is in "transaction mode" and
|
||||||
|
is not in "partial rollback" state.
|
||||||
|
|
||||||
This indicates if the :class:`.Session` is capable of emitting
|
The :class:`.Session` in its default mode of ``autocommit=False``
|
||||||
SQL, as from the :meth:`.Session.execute`, :meth:`.Session.query`,
|
is essentially always in "transaction mode", in that a
|
||||||
or :meth:`.Session.flush` methods. If False, it indicates
|
:class:`.SessionTransaction` is associated with it as soon as
|
||||||
that the innermost transaction has been rolled back, but enclosing
|
it is instantiated. This :class:`.SessionTransaction` is immediately
|
||||||
:class:`.SessionTransaction` objects remain in the transactional
|
replaced with a new one as soon as it is ended, due to a rollback,
|
||||||
stack, which also must be rolled back.
|
commit, or close operation.
|
||||||
|
|
||||||
This flag is generally only useful with a :class:`.Session`
|
"Transaction mode" does *not* indicate whether
|
||||||
configured in its default mode of ``autocommit=False``.
|
or not actual database connection resources are in use; the
|
||||||
|
:class:`.SessionTransaction` object coordinates among zero or more
|
||||||
|
actual database transactions, and starts out with none, accumulating
|
||||||
|
individual DBAPI connections as different data sources are used
|
||||||
|
within its scope. The best way to track when a particular
|
||||||
|
:class:`.Session` has actually begun to use DBAPI resources is to
|
||||||
|
implement a listener using the :meth:`.SessionEvents.after_begin`
|
||||||
|
method, which will deliver both the :class:`.Session` as well as the
|
||||||
|
target :class:`.Connection` to a user-defined event listener.
|
||||||
|
|
||||||
|
The "partial rollback" state refers to when an "inner" transaction,
|
||||||
|
typically used during a flush, encounters an error and emits
|
||||||
|
a rollback of the DBAPI connection. At this point, the :class:`.Session`
|
||||||
|
is in "partial rollback" and awaits for the user to call :meth:`.rollback`,
|
||||||
|
in order to close out the transaction stack. It is in this "partial
|
||||||
|
rollback" period that the :attr:`.is_active` flag returns False. After
|
||||||
|
the call to :meth:`.rollback`, the :class:`.SessionTransaction` is replaced
|
||||||
|
with a new one and :attr:`.is_active` returns ``True`` again.
|
||||||
|
|
||||||
|
When a :class:`.Session` is used in ``autocommit=True`` mode, the
|
||||||
|
:class:`.SessionTransaction` is only instantiated within the scope
|
||||||
|
of a flush call, or when :meth:`.Session.begin` is called. So
|
||||||
|
:attr:`.is_active` will always be ``False`` outside of a flush or
|
||||||
|
:meth:`.begin` block in this mode, and will be ``True`` within the
|
||||||
|
:meth:`.begin` block as long as it doesn't enter "partial rollback"
|
||||||
|
state.
|
||||||
|
|
||||||
|
From all the above, it follows that the only purpose to this flag is
|
||||||
|
for application frameworks that wish to detect is a "rollback" is
|
||||||
|
necessary within a generic error handling routine, for :class:`.Session`
|
||||||
|
objects that would otherwise be in "partial rollback" mode. In
|
||||||
|
a typical integration case, this is also not necessary as it is standard
|
||||||
|
practice to emit :meth:`.Session.rollback` unconditionally within the
|
||||||
|
outermost exception catch.
|
||||||
|
|
||||||
|
To track the transactional state of a :class:`.Session` fully,
|
||||||
|
use event listeners, primarily the :meth:`.SessionEvents.after_begin`,
|
||||||
|
:meth:`.SessionEvents.after_commit`, :meth:`.SessionEvents.after_rollback`
|
||||||
|
and related events.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return self.transaction and self.transaction.is_active
|
return self.transaction and self.transaction.is_active
|
||||||
|
|
||||||
identity_map = None
|
identity_map = None
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# orm/shard.py
|
# orm/shard.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# orm/state.py
|
# orm/state.py
|
||||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||||
#
|
#
|
||||||
# This module is part of SQLAlchemy and is released under
|
# This module is part of SQLAlchemy and is released under
|
||||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|||||||
Loaded 100 of 121 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user