[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