[Python-modules-commits] [sqlalchemy] 01/06: Import sqlalchemy_1.1.4+ds1.orig.tar.gz
Piotr Ożarowski
piotr at moszumanska.debian.org
Thu Nov 17 19:49:39 UTC 2016
This is an automated email from the git hooks/post-receive script.
piotr pushed a commit to branch master
in repository sqlalchemy.
commit e8013818e291a6062dc274ccbcee67638e468280
Author: Piotr Ożarowski <piotr at debian.org>
Date: Thu Nov 17 20:20:04 2016 +0100
Import sqlalchemy_1.1.4+ds1.orig.tar.gz
---
PKG-INFO | 2 +-
doc/build/changelog/changelog_10.rst | 40 +++++++
doc/build/changelog/changelog_11.rst | 85 ++++++++++++++
doc/build/conf.py | 4 +-
doc/build/orm/extensions/associationproxy.rst | 2 +-
doc/build/requirements.txt | 2 +-
lib/sqlalchemy/__init__.py | 2 +-
lib/sqlalchemy/connectors/pyodbc.py | 11 ++
lib/sqlalchemy/dialects/mssql/adodbapi.py | 9 +-
lib/sqlalchemy/dialects/mysql/base.py | 22 ++++
lib/sqlalchemy/dialects/mysql/enumerated.py | 10 ++
lib/sqlalchemy/dialects/mysql/mysqldb.py | 18 +++
lib/sqlalchemy/dialects/mysql/pymysql.py | 15 ++-
lib/sqlalchemy/dialects/postgresql/base.py | 1 +
lib/sqlalchemy/dialects/postgresql/psycopg2.py | 48 ++------
lib/sqlalchemy/dialects/sqlite/pysqlcipher.py | 20 +++-
lib/sqlalchemy/engine/base.py | 5 +-
lib/sqlalchemy/engine/default.py | 46 +++++++-
lib/sqlalchemy/orm/collections.py | 8 +-
lib/sqlalchemy/orm/dynamic.py | 4 +-
lib/sqlalchemy/orm/mapper.py | 14 ++-
lib/sqlalchemy/orm/persistence.py | 9 +-
lib/sqlalchemy/orm/query.py | 4 +-
lib/sqlalchemy/orm/strategies.py | 35 +++---
lib/sqlalchemy/sql/crud.py | 2 +-
lib/sqlalchemy/testing/requirements.py | 8 ++
lib/sqlalchemy/testing/suite/test_results.py | 149 +++++++++++++++++++++++-
test/dialect/mssql/test_engine.py | 45 +++++++-
test/dialect/mysql/test_types.py | 24 ++++
test/dialect/postgresql/test_compiler.py | 12 +-
test/dialect/postgresql/test_on_conflict.py | 44 +++++++
test/dialect/postgresql/test_query.py | 133 ---------------------
test/orm/test_bulk.py | 154 +++++++++++++++++++++++++
test/orm/test_inspect.py | 28 +++++
test/orm/test_pickled.py | 31 +++++
test/orm/test_relationships.py | 50 +++++++-
test/sql/test_insert.py | 17 +++
37 files changed, 888 insertions(+), 225 deletions(-)
diff --git a/PKG-INFO b/PKG-INFO
index 612c0e7..5e328a1 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: SQLAlchemy
-Version: 1.1.3
+Version: 1.1.4
Summary: Database Abstraction Library
Home-page: http://www.sqlalchemy.org
Author: Mike Bayer
diff --git a/doc/build/changelog/changelog_10.rst b/doc/build/changelog/changelog_10.rst
index 8a061cd..4ae8a8f 100644
--- a/doc/build/changelog/changelog_10.rst
+++ b/doc/build/changelog/changelog_10.rst
@@ -17,6 +17,15 @@
.. changelog::
:version: 1.0.16
+ :released: November 15, 2016
+
+ .. change::
+ :tags: bug, orm
+ :tickets: 3849
+ :versions: 1.1.4
+
+ Fixed bug in :meth:`.Session.bulk_update_mappings` where an alternate-named
+ primary key attribute would not track properly into the UPDATE statement.
.. change::
:tags: bug, mssql
@@ -77,6 +86,37 @@
collection of the mapped table, thereby interfering with the
initialization of relationships.
+ .. change::
+ :tags: bug, orm
+ :tickets: 3781
+ :versions: 1.1.4
+
+ Fixed bug in :meth:`.Session.bulk_save` where an UPDATE would
+ not function correctly in conjunction with a mapping that
+ implements a version id counter.
+
+ .. 3778
+
+ .. change::
+ :tags: bug, orm
+ :tickets: 3778
+ :versions: 1.1.4
+
+ Fixed bug where the :attr:`.Mapper.attrs`,
+ :attr:`.Mapper.all_orm_descriptors` and other derived attributes would
+ fail to refresh when mapper properties or other ORM constructs were
+ added to the mapper/class after these accessors were first called.
+
+ .. change:: 3762
+ :tags: bug, mssql
+ :tickets: 3762
+ :versions: 1.1.4
+
+ Fixed bug in pyodbc dialect (as well as in the mostly non-working
+ adodbapi dialect) whereby a semicolon present in the password
+ or username fields could be interpreted as a separator for another
+ token; the values are now quoted when semicolons are present.
+
.. changelog::
:version: 1.0.15
:released: September 1, 2016
diff --git a/doc/build/changelog/changelog_11.rst b/doc/build/changelog/changelog_11.rst
index 9c43374..701f30e 100644
--- a/doc/build/changelog/changelog_11.rst
+++ b/doc/build/changelog/changelog_11.rst
@@ -19,6 +19,91 @@
:start-line: 5
.. changelog::
+ :version: 1.1.4
+ :released: November 15, 2016
+
+ .. change:: 3842
+ :tags: bug, sql
+ :tickets: 3842
+
+ Fixed bug where newly added warning for primary key on insert w/o
+ autoincrement setting (see :ref:`change_3216`) would fail to emit
+ correctly when invoked upon a lower-case :func:`.table` construct.
+
+ .. change:: 3852
+ :tags: bug, orm
+ :tickets: 3852
+
+ Fixed regression in collections due to :ticket:`3457` whereby
+ deserialize during pickle or deepcopy would fail to establish all
+ attributes of an ORM collection, causing further mutation operations to
+ fail.
+
+ .. change:: default_schema
+ :tags: bug, engine
+
+ Removed long-broken "default_schema_name()" method from
+ :class:`.Connection`. This method was left over from a very old
+ version and was non-working (e.g. would raise). Pull request
+ courtesy Benjamin Dopplinger.
+
+ .. change:: pragma
+ :tags: bug, sqlite
+
+ Added quotes to the PRAGMA directives in the pysqlcipher dialect
+ to support additional cipher arguments appropriately. Pull request
+ courtesy Kevin Jurczyk.
+
+ .. change:: 3846
+ :tags: bug, postgresql
+ :tickets: 3846, 3807
+
+ Fixed regression caused by the fix in :ticket:`3807` (version 1.1.0)
+ where we ensured that the tablename was qualified in the WHERE clause
+ of the DO UPDATE portion of PostgreSQL's ON CONFLICT, however you
+ *cannot* put the table name in the WHERE clause in the actual ON
+ CONFLICT itself. This was an incorrect assumption, so that portion
+ of the change in :ticket:`3807` is rolled back.
+
+ .. change:: 3845
+ :tags: bug, orm
+ :tickets: 3845
+
+ Fixed long-standing bug where the "noload" relationship loading
+ strategy would cause backrefs and/or back_populates options to be
+ ignored.
+
+ .. change:: sscursor_mysql
+ :tags: feature, mysql
+
+ Added support for server side cursors to the mysqlclient and
+ pymysql dialects. This feature is available via the
+ :paramref:`.Connection.execution_options.stream_results` flag as well
+ as the ``server_side_cursors=True`` dialect argument in the
+ same way that it has been for psycopg2 on Postgresql. Pull request
+ courtesy Roman Podoliaka.
+
+ .. change::
+ :tags: bug, mysql
+ :tickets: 3841
+
+ MySQL's native ENUM type supports any non-valid value being sent, and
+ in response will return a blank string. A hardcoded rule to check for
+ "is returning the blank string" has been added to the MySQL
+ implementation for ENUM so that this blank string is returned to the
+ application rather than being rejected as a non-valid value. Note that
+ if your MySQL enum is linking values to objects, you still get the
+ blank string back.
+
+ .. change::
+ :tags: bug, sqlite, py3k
+
+ Added an optional import for the pysqlcipher3 DBAPI when using the
+ pysqlcipher dialect. This package will attempt to be imported
+ if the Python-2 only pysqlcipher DBAPI is non-present.
+ Pull request courtesy Kevin Jurczyk.
+
+.. changelog::
:version: 1.1.3
:released: October 27, 2016
diff --git a/doc/build/conf.py b/doc/build/conf.py
index f5785a7..992657b 100644
--- a/doc/build/conf.py
+++ b/doc/build/conf.py
@@ -107,9 +107,9 @@ copyright = u'2007-2016, the SQLAlchemy authors and contributors'
# The short X.Y version.
version = "1.1"
# The full version, including alpha/beta/rc tags.
-release = "1.1.3"
+release = "1.1.4"
-release_date = "October 27, 2016"
+release_date = "November 15, 2016"
site_base = os.environ.get("RTD_SITE_BASE", "http://www.sqlalchemy.org")
site_adapter_template = "docs_adapter.mako"
diff --git a/doc/build/orm/extensions/associationproxy.rst b/doc/build/orm/extensions/associationproxy.rst
index 7e7b1f9..7628bc1 100644
--- a/doc/build/orm/extensions/associationproxy.rst
+++ b/doc/build/orm/extensions/associationproxy.rst
@@ -151,7 +151,7 @@ Simplifying Association Objects
The "association object" pattern is an extended form of a many-to-many
relationship, and is described at :ref:`association_pattern`. Association
-proxies are useful for keeping "association objects" out the way during
+proxies are useful for keeping "association objects" out of the way during
regular use.
Suppose our ``userkeywords`` table above had additional columns
diff --git a/doc/build/requirements.txt b/doc/build/requirements.txt
index d1eb23d..9465700 100644
--- a/doc/build/requirements.txt
+++ b/doc/build/requirements.txt
@@ -1,3 +1,3 @@
-changelog>=0.3.4
+changelog>=0.3.5
sphinx-paramlinks>=0.2.2
git+https://bitbucket.org/zzzeek/zzzeeksphinx.git@HEAD#egg=zzzeeksphinx
diff --git a/lib/sqlalchemy/__init__.py b/lib/sqlalchemy/__init__.py
index ca681a0..ab787d5 100644
--- a/lib/sqlalchemy/__init__.py
+++ b/lib/sqlalchemy/__init__.py
@@ -128,7 +128,7 @@ from .schema import (
from .inspection import inspect
from .engine import create_engine, engine_from_config
-__version__ = '1.1.3'
+__version__ = '1.1.4'
def __go(lcls):
diff --git a/lib/sqlalchemy/connectors/pyodbc.py b/lib/sqlalchemy/connectors/pyodbc.py
index 1811229..6478de5 100644
--- a/lib/sqlalchemy/connectors/pyodbc.py
+++ b/lib/sqlalchemy/connectors/pyodbc.py
@@ -55,6 +55,7 @@ class PyODBCConnector(Connector):
opts.update(url.query)
keys = opts
+
query = url.query
connect_args = {}
@@ -65,6 +66,15 @@ class PyODBCConnector(Connector):
if 'odbc_connect' in keys:
connectors = [util.unquote_plus(keys.pop('odbc_connect'))]
else:
+ def check_quote(token):
+ if ";" in str(token):
+ token = "'%s'" % token
+ return token
+
+ keys = dict(
+ (k, check_quote(v)) for k, v in keys.items()
+ )
+
dsn_connection = 'dsn' in keys or \
('host' in keys and 'database' not in keys)
if dsn_connection:
@@ -107,6 +117,7 @@ class PyODBCConnector(Connector):
keys.pop("odbc_autotranslate"))
connectors.extend(['%s=%s' % (k, v) for k, v in keys.items()])
+
return [[";".join(connectors)], connect_args]
def is_disconnect(self, e, connection, cursor):
diff --git a/lib/sqlalchemy/dialects/mssql/adodbapi.py b/lib/sqlalchemy/dialects/mssql/adodbapi.py
index 60fa25d..a85ce5a 100644
--- a/lib/sqlalchemy/dialects/mssql/adodbapi.py
+++ b/lib/sqlalchemy/dialects/mssql/adodbapi.py
@@ -56,7 +56,14 @@ class MSDialect_adodbapi(MSDialect):
)
def create_connect_args(self, url):
- keys = url.query
+ def check_quote(token):
+ if ";" in str(token):
+ token = "'%s'" % token
+ return token
+
+ keys = dict(
+ (k, check_quote(v)) for k, v in url.query.items()
+ )
connectors = ["Provider=SQLOLEDB"]
if 'port' in keys:
diff --git a/lib/sqlalchemy/dialects/mysql/base.py b/lib/sqlalchemy/dialects/mysql/base.py
index e7e5338..449fffa 100644
--- a/lib/sqlalchemy/dialects/mysql/base.py
+++ b/lib/sqlalchemy/dialects/mysql/base.py
@@ -177,6 +177,22 @@ multi-column key for some storage engines::
Column('id', Integer, primary_key=True)
)
+.. _mysql_ss_cursors:
+
+Server Side Cursors
+-------------------
+
+Server-side cursor support is available for the MySQLdb and PyMySQL dialects.
+From a MySQL point of view this means that the ``MySQLdb.cursors.SSCursor`` or
+``pymysql.cursors.SSCursor`` class is used when building up the cursor which
+will receive results. The most typical way of invoking this feature is via the
+:paramref:`.Connection.execution_options.stream_results` connection execution
+option. Server side cursors can also be enabled for all SELECT statements
+unconditionally by passing ``server_side_cursors=True`` to
+:func:`.create_engine`.
+
+.. versionadded:: 1.1.4 - added server-side cursor support.
+
.. _mysql_unicode:
Unicode
@@ -743,6 +759,12 @@ class MySQLExecutionContext(default.DefaultExecutionContext):
def should_autocommit_text(self, statement):
return AUTOCOMMIT_RE.match(statement)
+ def create_server_side_cursor(self):
+ if self.dialect.supports_server_side_cursors:
+ return self._dbapi_connection.cursor(self.dialect._sscursor)
+ else:
+ raise NotImplementedError()
+
class MySQLCompiler(compiler.SQLCompiler):
diff --git a/lib/sqlalchemy/dialects/mysql/enumerated.py b/lib/sqlalchemy/dialects/mysql/enumerated.py
index a47d94c..a7cd891 100644
--- a/lib/sqlalchemy/dialects/mysql/enumerated.py
+++ b/lib/sqlalchemy/dialects/mysql/enumerated.py
@@ -130,6 +130,16 @@ class ENUM(sqltypes.Enum, _EnumeratedValues):
values, length = self._init_values(values, kw)
return sqltypes.Enum._setup_for_values(self, values, objects, kw)
+ def _object_value_for_elem(self, elem):
+ # mysql sends back a blank string for any value that
+ # was persisted that was not in the enums; that is, it does no
+ # validation on the incoming data, it "truncates" it to be
+ # the blank string. Return it straight.
+ if elem == "":
+ return elem
+ else:
+ return super(ENUM, self)._object_value_for_elem(elem)
+
def __repr__(self):
return util.generic_repr(
self, to_inspect=[ENUM, _StringType, sqltypes.Enum])
diff --git a/lib/sqlalchemy/dialects/mysql/mysqldb.py b/lib/sqlalchemy/dialects/mysql/mysqldb.py
index aa8377b..568c05f 100644
--- a/lib/sqlalchemy/dialects/mysql/mysqldb.py
+++ b/lib/sqlalchemy/dialects/mysql/mysqldb.py
@@ -38,6 +38,11 @@ using a URL like the following::
mysql+mysqldb://root@/<dbname>?unix_socket=/cloudsql/<projectid>:<instancename>
+Server Side Cursors
+-------------------
+
+The mysqldb dialect supports server-side cursors. See :ref:`mysql_ss_cursors`.
+
"""
from .base import (MySQLDialect, MySQLExecutionContext,
@@ -87,6 +92,19 @@ class MySQLDialect_mysqldb(MySQLDialect):
statement_compiler = MySQLCompiler_mysqldb
preparer = MySQLIdentifierPreparer_mysqldb
+ def __init__(self, server_side_cursors=False, **kwargs):
+ super(MySQLDialect_mysqldb, self).__init__(**kwargs)
+ self.server_side_cursors = server_side_cursors
+
+ @util.langhelpers.memoized_property
+ def supports_server_side_cursors(self):
+ try:
+ cursors = __import__('MySQLdb.cursors').cursors
+ self._sscursor = cursors.SSCursor
+ return True
+ except (ImportError, AttributeError):
+ return False
+
@classmethod
def dbapi(cls):
return __import__('MySQLdb')
diff --git a/lib/sqlalchemy/dialects/mysql/pymysql.py b/lib/sqlalchemy/dialects/mysql/pymysql.py
index 3c493fb..e29c17d 100644
--- a/lib/sqlalchemy/dialects/mysql/pymysql.py
+++ b/lib/sqlalchemy/dialects/mysql/pymysql.py
@@ -30,7 +30,7 @@ to the pymysql driver as well.
"""
from .mysqldb import MySQLDialect_mysqldb
-from ...util import py3k
+from ...util import langhelpers, py3k
class MySQLDialect_pymysql(MySQLDialect_mysqldb):
@@ -44,6 +44,19 @@ class MySQLDialect_pymysql(MySQLDialect_mysqldb):
supports_unicode_statements = True
supports_unicode_binds = True
+ def __init__(self, server_side_cursors=False, **kwargs):
+ super(MySQLDialect_pymysql, self).__init__(**kwargs)
+ self.server_side_cursors = server_side_cursors
+
+ @langhelpers.memoized_property
+ def supports_server_side_cursors(self):
+ try:
+ cursors = __import__('pymysql.cursors').cursors
+ self._sscursor = cursors.SSCursor
+ return True
+ except (ImportError, AttributeError):
+ return False
+
@classmethod
def dbapi(cls):
return __import__('pymysql')
diff --git a/lib/sqlalchemy/dialects/postgresql/base.py b/lib/sqlalchemy/dialects/postgresql/base.py
index 9898e4b..4c82325 100644
--- a/lib/sqlalchemy/dialects/postgresql/base.py
+++ b/lib/sqlalchemy/dialects/postgresql/base.py
@@ -1433,6 +1433,7 @@ class PGCompiler(compiler.SQLCompiler):
target_text += ' WHERE %s' % \
self.process(
clause.inferred_target_whereclause,
+ include_table=False,
use_schema=False
)
else:
diff --git a/lib/sqlalchemy/dialects/postgresql/psycopg2.py b/lib/sqlalchemy/dialects/postgresql/psycopg2.py
index 8488da8..27a1ec0 100644
--- a/lib/sqlalchemy/dialects/postgresql/psycopg2.py
+++ b/lib/sqlalchemy/dialects/postgresql/psycopg2.py
@@ -28,7 +28,8 @@ psycopg2-specific keyword arguments which are accepted by
:class:`~sqlalchemy.engine.ResultProxy` uses special row-buffering
behavior when this feature is enabled, such that groups of 100 rows at a
time are fetched over the wire to reduce conversational overhead.
- Note that the ``stream_results=True`` execution option is a more targeted
+ Note that the :paramref:`.Connection.execution_options.stream_results`
+ execution option is a more targeted
way of enabling this mode on a per-execution basis.
* ``use_native_unicode``: Enable the usage of Psycopg2 "native unicode" mode
per connection. True by default.
@@ -422,53 +423,24 @@ class _PGUUID(UUID):
return value
return process
-# When we're handed literal SQL, ensure it's a SELECT query. Since
-# 8.3, combining cursors and "FOR UPDATE" has been fine.
-SERVER_SIDE_CURSOR_RE = re.compile(
- r'\s*SELECT',
- re.I | re.UNICODE)
_server_side_id = util.counter()
class PGExecutionContext_psycopg2(PGExecutionContext):
- def create_cursor(self):
- # TODO: coverage for server side cursors + select.for_update()
-
- if self.dialect.server_side_cursors:
- is_server_side = \
- self.execution_options.get('stream_results', True) and (
- (self.compiled and isinstance(self.compiled.statement,
- expression.Selectable)
- or
- (
- (not self.compiled or
- isinstance(self.compiled.statement,
- expression.TextClause))
- and self.statement and SERVER_SIDE_CURSOR_RE.match(
- self.statement))
- )
- )
- else:
- is_server_side = \
- self.execution_options.get('stream_results', False)
-
- self.__is_server_side = is_server_side
- if is_server_side:
- # use server-side cursors:
- # http://lists.initd.org/pipermail/psycopg/2007-January/005251.html
- ident = "c_%s_%s" % (hex(id(self))[2:],
- hex(_server_side_id())[2:])
- return self._dbapi_connection.cursor(ident)
- else:
- return self._dbapi_connection.cursor()
+ def create_server_side_cursor(self):
+ # use server-side cursors:
+ # http://lists.initd.org/pipermail/psycopg/2007-January/005251.html
+ ident = "c_%s_%s" % (hex(id(self))[2:],
+ hex(_server_side_id())[2:])
+ return self._dbapi_connection.cursor(ident)
def get_result_proxy(self):
# TODO: ouch
if logger.isEnabledFor(logging.INFO):
self._log_notices(self.cursor)
- if self.__is_server_side:
+ if self._is_server_side:
return _result.BufferedRowResultProxy(self)
else:
return _result.ResultProxy(self)
@@ -502,6 +474,8 @@ class PGDialect_psycopg2(PGDialect):
if util.py2k:
supports_unicode_statements = False
+ supports_server_side_cursors = True
+
default_paramstyle = 'pyformat'
# set to true based on psycopg2 version
supports_sane_multi_rowcount = False
diff --git a/lib/sqlalchemy/dialects/sqlite/pysqlcipher.py b/lib/sqlalchemy/dialects/sqlite/pysqlcipher.py
index bbafc8d..bea3bd8 100644
--- a/lib/sqlalchemy/dialects/sqlite/pysqlcipher.py
+++ b/lib/sqlalchemy/dialects/sqlite/pysqlcipher.py
@@ -15,7 +15,12 @@
``pysqlcipher`` is a fork of the standard ``pysqlite`` driver to make
use of the `SQLCipher <https://www.zetetic.net/sqlcipher>`_ backend.
- .. versionadded:: 0.9.9
+ ``pysqlcipher3`` is a fork of ``pysqlcipher`` for Python 3. This dialect
+ will attempt to import it if ``pysqlcipher`` is non-present.
+
+ .. versionadded:: 1.1.4 - added fallback import for pysqlcipher3
+
+ .. versionadded:: 0.9.9 - added pysqlcipher dialect
Driver
------
@@ -26,6 +31,9 @@ introduces new PRAGMA commands to SQLite which allows the setting of a
passphrase and other encryption parameters, allowing the database
file to be encrypted.
+`pysqlcipher3` is a fork of `pysqlcipher` with support for Python 3,
+the driver is the same.
+
Connect Strings
---------------
@@ -80,7 +88,13 @@ class SQLiteDialect_pysqlcipher(SQLiteDialect_pysqlite):
@classmethod
def dbapi(cls):
- from pysqlcipher import dbapi2 as sqlcipher
+ try:
+ from pysqlcipher import dbapi2 as sqlcipher
+ except ImportError as e:
+ try:
+ from pysqlcipher3 import dbapi2 as sqlcipher
+ except ImportError:
+ raise e
return sqlcipher
@classmethod
@@ -100,7 +114,7 @@ class SQLiteDialect_pysqlcipher(SQLiteDialect_pysqlite):
conn.execute('pragma key="%s"' % passphrase)
for prag, value in pragmas.items():
if value is not None:
- conn.execute('pragma %s=%s' % (prag, value))
+ conn.execute('pragma %s="%s"' % (prag, value))
return conn
diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py
index 1d23c66..e187fa6 100644
--- a/lib/sqlalchemy/engine/base.py
+++ b/lib/sqlalchemy/engine/base.py
@@ -295,7 +295,7 @@ class Connection(Connectable):
Indicate to the dialect that results should be
"streamed" and not pre-buffered, if possible. This is a limitation
of many DBAPIs. The flag is currently understood only by the
- psycopg2 dialect.
+ psycopg2, mysqldb and pymysql dialects.
:param schema_translate_map: Available on: Connection, Engine.
A dictionary mapping schema names to schema names, that will be
@@ -1458,9 +1458,6 @@ class Connection(Connectable):
else:
util.reraise(*exc_info)
- def default_schema_name(self):
- return self.engine.dialect.get_default_schema_name(self)
-
def transaction(self, callable_, *args, **kwargs):
"""Execute the given function within a transaction boundary.
diff --git a/lib/sqlalchemy/engine/default.py b/lib/sqlalchemy/engine/default.py
index 3ee2403..719178f 100644
--- a/lib/sqlalchemy/engine/default.py
+++ b/lib/sqlalchemy/engine/default.py
@@ -27,6 +27,11 @@ AUTOCOMMIT_REGEXP = re.compile(
r'\s*(?:UPDATE|INSERT|CREATE|DELETE|DROP|ALTER)',
re.I | re.UNICODE)
+# When we're handed literal SQL, ensure it's a SELECT query
+SERVER_SIDE_CURSOR_RE = re.compile(
+ r'\s*SELECT',
+ re.I | re.UNICODE)
+
class DefaultDialect(interfaces.Dialect):
"""Default implementation of Dialect"""
@@ -108,6 +113,8 @@ class DefaultDialect(interfaces.Dialect):
supports_empty_insert = True
supports_multivalues_insert = False
+ supports_server_side_cursors = False
+
server_version_info = None
construct_arguments = None
@@ -780,8 +787,40 @@ class DefaultExecutionContext(interfaces.ExecutionContext):
def should_autocommit_text(self, statement):
return AUTOCOMMIT_REGEXP.match(statement)
+ def _use_server_side_cursor(self):
+ if not self.dialect.supports_server_side_cursors:
+ return False
+
+ if self.dialect.server_side_cursors:
+ use_server_side = \
+ self.execution_options.get('stream_results', True) and (
+ (self.compiled and isinstance(self.compiled.statement,
+ expression.Selectable)
+ or
+ (
+ (not self.compiled or
+ isinstance(self.compiled.statement,
+ expression.TextClause))
+ and self.statement and SERVER_SIDE_CURSOR_RE.match(
+ self.statement))
+ )
+ )
+ else:
+ use_server_side = \
+ self.execution_options.get('stream_results', False)
+
+ return use_server_side
+
def create_cursor(self):
- return self._dbapi_connection.cursor()
+ if self._use_server_side_cursor():
+ self._is_server_side = True
+ return self.create_server_side_cursor()
+ else:
+ self._is_server_side = False
+ return self._dbapi_connection.cursor()
+
+ def create_server_side_cursor(self):
+ raise NotImplementedError()
def pre_exec(self):
pass
@@ -831,7 +870,10 @@ class DefaultExecutionContext(interfaces.ExecutionContext):
pass
def get_result_proxy(self):
- return result.ResultProxy(self)
+ if self._is_server_side:
+ return result.BufferedRowResultProxy(self)
+ else:
+ return result.ResultProxy(self)
@property
def rowcount(self):
diff --git a/lib/sqlalchemy/orm/collections.py b/lib/sqlalchemy/orm/collections.py
index 1e022e1..47cd774 100644
--- a/lib/sqlalchemy/orm/collections.py
+++ b/lib/sqlalchemy/orm/collections.py
@@ -714,12 +714,18 @@ class CollectionAdapter(object):
def __getstate__(self):
return {'key': self._key,
'owner_state': self.owner_state,
- 'data': self.data}
+ 'owner_cls': self.owner_state.class_,
+ 'data': self.data,
+ 'invalidated': self.invalidated}
def __setstate__(self, d):
self._key = d['key']
self.owner_state = d['owner_state']
self._data = weakref.ref(d['data'])
+ self._converter = d['data']._sa_converter
+ d['data']._sa_adapter = self
+ self.invalidated = d['invalidated']
+ self.attr = getattr(d['owner_cls'], self._key).impl
def bulk_replace(values, existing_adapter, new_adapter):
diff --git a/lib/sqlalchemy/orm/dynamic.py b/lib/sqlalchemy/orm/dynamic.py
index 026ebc3..867c6ee 100644
--- a/lib/sqlalchemy/orm/dynamic.py
+++ b/lib/sqlalchemy/orm/dynamic.py
@@ -32,15 +32,13 @@ class DynaLoader(strategies.AbstractRelationshipLoader):
"many-to-one/one-to-one relationships and/or "
"uselist=False." % self.parent_property)
strategies._register_attribute(
- self,
+ self.parent_property,
mapper,
useobject=True,
- uselist=True,
impl_class=DynamicAttributeImpl,
target_mapper=self.parent_property.mapper,
order_by=self.parent_property.order_by,
query_class=self.parent_property.query_class,
- backref=self.parent_property.back_populates,
)
diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py
index 6084809..5156e43 100644
--- a/lib/sqlalchemy/orm/mapper.py
+++ b/lib/sqlalchemy/orm/mapper.py
@@ -2020,6 +2020,16 @@ class Mapper(InspectionAttr):
)
@_memoized_configured_property
+ def _pk_attr_keys_by_table(self):
+ return dict(
+ (
+ table,
+ frozenset([self._columntoproperty[col].key for col in pks])
+ )
+ for table, pks in self._pks_by_table.items()
+ )
+
+ @_memoized_configured_property
def _server_default_cols(self):
return dict(
(
@@ -2101,7 +2111,7 @@ class Mapper(InspectionAttr):
continue
yield c
- @util.memoized_property
+ @_memoized_configured_property
def attrs(self):
"""A namespace of all :class:`.MapperProperty` objects
associated this mapper.
@@ -2139,7 +2149,7 @@ class Mapper(InspectionAttr):
configure_mappers()
return util.ImmutableProperties(self._props)
- @util.memoized_property
+ @_memoized_configured_property
def all_orm_descriptors(self):
"""A namespace of all :class:`.InspectionAttr` attributes associated
with the mapped class.
diff --git a/lib/sqlalchemy/orm/persistence.py b/lib/sqlalchemy/orm/persistence.py
index 2bc189c..2f7acba 100644
--- a/lib/sqlalchemy/orm/persistence.py
+++ b/lib/sqlalchemy/orm/persistence.py
@@ -84,11 +84,16 @@ def _bulk_update(mapper, mappings, session_transaction,
cached_connections = _cached_connection_dict(base_mapper)
+ search_keys = mapper._primary_key_propkeys
+ if mapper._version_id_prop:
+ search_keys = set([mapper._version_id_prop.key]).union(search_keys)
+
def _changed_dict(mapper, state):
return dict(
(k, v)
for k, v in state.dict.items() if k in state.committed_state or k
- in mapper._primary_key_propkeys
+ in search_keys
+
)
if isstates:
@@ -522,7 +527,7 @@ def _collect_update_commands(
(propkey_to_col[propkey]._label, state_dict.get(propkey))
for propkey in
set(propkey_to_col).
- intersection(mapper._pk_keys_by_table[table])
+ intersection(mapper._pk_attr_keys_by_table[table])
)
else:
pk_params = {}
diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py
index 23d33b0..139b61a 100644
--- a/lib/sqlalchemy/orm/query.py
+++ b/lib/sqlalchemy/orm/query.py
@@ -751,7 +751,9 @@ class Query(object):
:meth:`~sqlalchemy.orm.query.Query.yield_per` will set the
``stream_results`` execution option to True, currently
this is only understood by
- :mod:`~sqlalchemy.dialects.postgresql.psycopg2` dialect
+ :mod:`~sqlalchemy.dialects.postgresql.psycopg2`,
+ :mod:`~sqlalchemy.dialects.mysql.mysqldb` and
+ :mod:`~sqlalchemy.dialects.mysql.pymysql` dialects
which will stream results using server side cursors
instead of pre-buffer all rows for this query. Other
DBAPIs **pre-buffer all rows** before making them
diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py
index 5f5ab10..33feab0 100644
--- a/lib/sqlalchemy/orm/strategies.py
+++ b/lib/sqlalchemy/orm/strategies.py
@@ -28,10 +28,9 @@ import itertools
def _register_attribute(
- strategy, mapper, useobject,
+ prop, mapper, useobject,
compare_function=None,
typecallable=None,
- uselist=False,
callable_=None,
proxy_property=None,
active_history=False,
@@ -39,12 +38,12 @@ def _register_attribute(
**kw
):
- prop = strategy.parent_property
-
attribute_ext = list(util.to_list(prop.extension, default=[]))
listen_hooks = []
+ uselist = useobject and prop.uselist
+
if useobject and prop.single_parent:
listen_hooks.append(single_parent_validator)
@@ -61,15 +60,16 @@ def _register_attribute(
# need to assemble backref listeners
# after the singleparentvalidator, mapper validator
- backref = kw.pop('backref', None)
- if backref:
- listen_hooks.append(
- lambda desc, prop: attributes.backref_listeners(
- desc,
- backref,
- uselist
+ if useobject:
+ backref = prop.back_populates
+ if backref:
+ listen_hooks.append(
+ lambda desc, prop: attributes.backref_listeners(
+ desc,
+ backref,
+ uselist
+ )
)
- )
# a single MapperProperty is shared down a class inheritance
# hierarchy, so we set up attribute instrumentation and backref event
@@ -173,7 +173,7 @@ class ColumnLoader(LoaderStrategy):
mapper.version_id_col in set(self.columns)
_register_attribute(
- self, mapper, useobject=False,
+ self.parent_property, mapper, useobject=False,
compare_function=coltype.compare_values,
active_history=active_history
)
@@ -228,7 +228,7 @@ class DeferredColumnLoader(LoaderStrategy):
self.is_class_level = True
_register_attribute(
- self, mapper, useobject=False,
+ self.parent_property, mapper, useobject=False,
compare_function=self.columns[0].type.compare_values,
callable_=self._load_for_state,
expire_missing=False
@@ -350,9 +350,8 @@ class NoLoader(AbstractRelationshipLoader):
self.is_class_level = True
_register_attribute(
- self, mapper,
+ self.parent_property, mapper,
useobject=True,
- uselist=self.parent_property.uselist,
typecallable=self.parent_property.collection_class,
)
@@ -434,12 +433,10 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
# in that case. otherwise we don't need the
# "old" value during backref operations.
_register_attribute(
- self,
+ self.parent_property,
mapper,
useobject=True,
callable_=self._load_for_state,
- uselist=self.parent_property.uselist,
- backref=self.parent_property.back_populates,
typecallable=self.parent_property.collection_class,
active_history=active_history
)
diff --git a/lib/sqlalchemy/sql/crud.py b/lib/sqlalchemy/sql/crud.py
index 452fe5d..9d10fbe 100644
--- a/lib/sqlalchemy/sql/crud.py
+++ b/lib/sqlalchemy/sql/crud.py
@@ -681,7 +681,7 @@ def _warn_pk_with_no_anticipated_value(c):
"Primary key columns typically may not store NULL."
%
(c.table.fullname, c.name, c.table.fullname))
- if len(c.table.primary_key.columns) > 1:
+ if len(c.table.primary_key) > 1:
msg += (
" Note that as of SQLAlchemy 1.1, 'autoincrement=True' must be "
"indicated explicitly for composite (e.g. multicolumn) primary "
diff --git a/lib/sqlalchemy/testing/requirements.py b/lib/sqlalchemy/testing/requirements.py
index af148a3..b001aaf 100644
--- a/lib/sqlalchemy/testing/requirements.py
+++ b/lib/sqlalchemy/testing/requirements.py
@@ -288,6 +288,14 @@ class SuiteRequirements(Requirements):
return exclusions.closed()
@property
+ def server_side_cursors(self):
+ """Target dialect must support server side cursors."""
+
+ return exclusions.only_if([
+ lambda config: config.db.dialect.supports_server_side_cursors
+ ], "no server side cursors support")
+
+ @property
def sequences(self):
"""Target database must support SEQUENCEs."""
diff --git a/lib/sqlalchemy/testing/suite/test_results.py b/lib/sqlalchemy/testing/suite/test_results.py
index f40d9a0..98ddc7e 100644
--- a/lib/sqlalchemy/testing/suite/test_results.py
+++ b/lib/sqlalchemy/testing/suite/test_results.py
@@ -3,8 +3,9 @@ from ..config import requirements
from .. import exclusions
from ..assertions import eq_
from .. import engines
+from ... import testing
-from sqlalchemy import Integer, String, select, util, sql, DateTime
+from sqlalchemy import Integer, String, select, util, sql, DateTime, text, func
import datetime
from ..schema import Table, Column
@@ -218,3 +219,149 @@ class PercentSchemaNamesTest(fixtures.TablesTest):
),
[(5, 15), (7, 15), (9, 15), (11, 15)]
)
+
+
+class ServerSideCursorsTest(fixtures.TestBase, testing.AssertsExecutionResults):
+
+ __requires__ = ('server_side_cursors', )
+
+ __backend__ = True
+
+ def _is_server_side(self, cursor):
+ if self.engine.url.drivername == 'postgresql':
+ return cursor.name
+ elif self.engine.url.drivername == 'mysql':
+ sscursor = __import__('MySQLdb.cursors').cursors.SSCursor
+ return isinstance(cursor, sscursor)
+ elif self.engine.url.drivername == 'mysql+pymysql':
+ sscursor = __import__('pymysql.cursors').cursors.SSCursor
+ return isinstance(cursor, sscursor)
+ else:
+ return False
+
+ def _fixture(self, server_side_cursors):
+ self.engine = engines.testing_engine(
... 842 lines suppressed ...
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/sqlalchemy.git
More information about the Python-modules-commits
mailing list