diff -Nru sqlalchemy-1.0.12+ds1/debian/changelog sqlalchemy-1.0.14+ds1/debian/changelog --- sqlalchemy-1.0.12+ds1/debian/changelog 2016-05-25 13:50:10.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/debian/changelog 2016-08-29 13:18:53.000000000 +0000 @@ -1,3 +1,30 @@ +sqlalchemy (1.0.14+ds1-1ubuntu1) yakkety; urgency=medium + + * Merge with Debian; remaining changes: + - d/p/pymysql-replacement.patch: Just switch the default mysql dialect + to pymysql, allowing users who really want to to use mysqldb still. + - debian/control, debian/README.Debian: Drop python-mysqldb in favor of + python-pymysql. + + -- Matthias Klose Mon, 29 Aug 2016 15:18:53 +0200 + +sqlalchemy (1.0.14+ds1-1) unstable; urgency=medium + + * New upstream version + + -- Piotr Ożarowski Sun, 31 Jul 2016 11:00:45 +0200 + +sqlalchemy (1.0.13+ds1-1) unstable; urgency=medium + + [ Ondřej Nový ] + * Fixed VCS URL (https) + + [ Piotr Ożarowski ] + * New upstream release + * Standards-Version bumped to 3.9.8 (no changes needed) + + -- Piotr Ożarowski Sun, 05 Jun 2016 22:53:44 +0200 + sqlalchemy (1.0.12+ds1-1ubuntu1) yakkety; urgency=medium * Merge with Debian; remaining changes: diff -Nru sqlalchemy-1.0.12+ds1/debian/control sqlalchemy-1.0.14+ds1/debian/control --- sqlalchemy-1.0.12+ds1/debian/control 2016-05-25 13:50:10.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/debian/control 2016-08-29 13:18:53.000000000 +0000 @@ -9,9 +9,9 @@ python-setuptools (>= 0.6b3-1~), python3-setuptools, Build-Depends-Indep: python-sphinx (>= 1.1.2), python-mako (>= 0.4.1), python-changelog, python-sphinx-paramlinks, python-zzzeeksphinx -Standards-Version: 3.9.7 +Standards-Version: 3.9.8 Homepage: http://www.sqlalchemy.org/ -Vcs-Git: git://anonscm.debian.org/python-modules/packages/sqlalchemy.git +Vcs-Git: https://anonscm.debian.org/git/python-modules/packages/sqlalchemy.git Vcs-Browser: https://anonscm.debian.org/cgit/python-modules/packages/sqlalchemy.git X-Python-Version: >= 2.5 diff -Nru sqlalchemy-1.0.12+ds1/debian/.git-dpm sqlalchemy-1.0.14+ds1/debian/.git-dpm --- sqlalchemy-1.0.12+ds1/debian/.git-dpm 2016-02-28 21:19:06.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/debian/.git-dpm 2016-07-31 09:00:32.000000000 +0000 @@ -1,11 +1,11 @@ # see git-dpm(1) from git-dpm package -c25b7c61fa04d6ba2592c2681d9f37d5652bc29b -c25b7c61fa04d6ba2592c2681d9f37d5652bc29b -c25b7c61fa04d6ba2592c2681d9f37d5652bc29b -c25b7c61fa04d6ba2592c2681d9f37d5652bc29b -sqlalchemy_1.0.12+ds1.orig.tar.xz -9698e26ab84a67280c091856b3801841d0bca09d -1788544 +796dc5659d53dd9ffcd94d6b0b5089a52c97b516 +796dc5659d53dd9ffcd94d6b0b5089a52c97b516 +796dc5659d53dd9ffcd94d6b0b5089a52c97b516 +796dc5659d53dd9ffcd94d6b0b5089a52c97b516 +sqlalchemy_1.0.14+ds1.orig.tar.gz +b1c59a0b539ca04aa87ddba0eda5e4d2e3e54bb5 +2571702 debianTag="debian/%e%v" patchedTag="patched/%e%v" upstreamTag="upstream/%e%u" diff -Nru sqlalchemy-1.0.12+ds1/debian/rules sqlalchemy-1.0.14+ds1/debian/rules --- sqlalchemy-1.0.12+ds1/debian/rules 2016-02-28 21:19:06.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/debian/rules 2016-07-31 18:37:24.000000000 +0000 @@ -38,7 +38,7 @@ build-docs: dh_testdir dh_installdirs -i - cd doc/build && sphinx-build -N -q -E -b html . \ + cd doc/build && sphinx-build -N -E -b html . \ $(call pkgdir,2.X)-doc/usr/share/doc/${p_name}-doc/html/ rm -rf $(call pkgdir,2.X)-doc/usr/share/doc/${p_name}-doc/html/.doctrees touch $@ diff -Nru sqlalchemy-1.0.12+ds1/doc/build/changelog/changelog_05.rst sqlalchemy-1.0.14+ds1/doc/build/changelog/changelog_05.rst --- sqlalchemy-1.0.12+ds1/doc/build/changelog/changelog_05.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/changelog/changelog_05.rst 2016-07-06 21:09:34.000000000 +0000 @@ -3461,7 +3461,7 @@ :tickets: Fixed query.join() when used in conjunction with a - columns-only clause and an SQL-expression ON clause in the + columns-only clause and a SQL-expression ON clause in the join. .. change:: diff -Nru sqlalchemy-1.0.12+ds1/doc/build/changelog/changelog_06.rst sqlalchemy-1.0.14+ds1/doc/build/changelog/changelog_06.rst --- sqlalchemy-1.0.12+ds1/doc/build/changelog/changelog_06.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/changelog/changelog_06.rst 2016-07-06 21:09:34.000000000 +0000 @@ -3845,7 +3845,7 @@ :tickets: For the full set of feature descriptions, see - http://www.sqlalchemy.org/trac/wiki/06Migration . + http://docs.sqlalchemy.org/en/latest/changelog/migration_06.html . This document is a work in progress. .. change:: diff -Nru sqlalchemy-1.0.12+ds1/doc/build/changelog/changelog_07.rst sqlalchemy-1.0.14+ds1/doc/build/changelog/changelog_07.rst --- sqlalchemy-1.0.12+ds1/doc/build/changelog/changelog_07.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/changelog/changelog_07.rst 2016-07-06 21:09:34.000000000 +0000 @@ -3220,7 +3220,7 @@ This section documents those changes from 0.7b4 to 0.7.0. For an overview of what's new in SQLAlchemy 0.7, see - http://www.sqlalchemy.org/trac/wiki/07Migration + http://docs.sqlalchemy.org/en/latest/changelog/migration_07.html .. change:: :tags: orm @@ -4125,7 +4125,7 @@ Detailed descriptions of each change below are described at: - http://www.sqlalchemy.org/trac/wiki/07Migration + http://docs.sqlalchemy.org/en/latest/changelog/migration_07.html .. change:: :tags: general diff -Nru sqlalchemy-1.0.12+ds1/doc/build/changelog/changelog_08.rst sqlalchemy-1.0.14+ds1/doc/build/changelog/changelog_08.rst --- sqlalchemy-1.0.12+ds1/doc/build/changelog/changelog_08.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/changelog/changelog_08.rst 2016-07-06 21:09:34.000000000 +0000 @@ -2440,7 +2440,7 @@ :tickets: 2589 The Beaker caching example has been converted - to use `dogpile.cache `_. + to use `dogpile.cache `_. This is a new caching library written by the same creator of Beaker's caching internals, and represents a vastly improved, simplified, and modernized system of caching. diff -Nru sqlalchemy-1.0.12+ds1/doc/build/changelog/changelog_10.rst sqlalchemy-1.0.14+ds1/doc/build/changelog/changelog_10.rst --- sqlalchemy-1.0.12+ds1/doc/build/changelog/changelog_10.rst 2016-02-15 17:28:41.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/changelog/changelog_10.rst 2016-07-06 21:09:51.000000000 +0000 @@ -16,6 +16,247 @@ :start-line: 5 .. changelog:: + :version: 1.0.14 + :released: July 6, 2016 + + .. change:: + :tags: bug, postgresql + :tickets: 3739 + :versions: 1.1.0b3 + + Fixed bug whereby :class:`.TypeDecorator` and :class:`.Variant` + types were not deeply inspected enough by the Postgresql dialect + to determine if SMALLSERIAL or BIGSERIAL needed to be rendered + rather than SERIAL. + + .. change:: + :tags: bug, oracle + :tickets: 3741 + :versions: 1.1.0b3 + + Fixed bug in :paramref:`.Select.with_for_update.of`, where the Oracle + "rownum" approach to LIMIT/OFFSET would fail to accomodate for the + expressions inside the "OF" clause, which must be stated at the topmost + level referring to expression within the subquery. The expressions are + now added to the subquery if needed. + + .. change:: + :tags: bug, sql + :tickets: 3735 + :versions: 1.1.0b2 + + Fixed issue in SQL math negation operator where the type of the + expression would no longer be the numeric type of the original. + This would cause issues where the type determined result set + behaviors. + + .. change:: + :tags: bug, sql + :tickets: 3728 + :versions: 1.1.0b2 + + Fixed bug whereby the ``__getstate__`` / ``__setstate__`` + methods for sqlalchemy.util.Properties were + non-working due to the transition in the 1.0 series to ``__slots__``. + The issue potentially impacted some third-party applications. + Pull request courtesy Pieter Mulder. + + .. change:: + :tags: bug, sql + :tickets: 3724 + + :meth:`.FromClause.count` is pending deprecation for 1.1. This function + makes use of an arbitrary column in the table and is not reliable; + for Core use, ``func.count()`` should be preferred. + + .. change:: + :tags: bug, sql + :tickets: 3722 + + Fixed bug in :class:`.CTE` structure which would cause it to not + clone properly when a union was used, as is common in a recursive + CTE. The improper cloning would cause errors when the CTE is used + in various ORM contexts such as that of a :func:`.column_property`. + + .. change:: + :tags: bug, sql + :tickets: 3721 + + Fixed bug whereby :meth:`.Table.tometadata` would make a duplicate + :class:`.UniqueConstraint` for each :class:`.Column` object that + featured the ``unique=True`` parameter. + + .. change:: + :tags: bug, engine, postgresql + :tickets: 3716 + + Fixed bug in cross-schema foreign key reflection in conjunction + with the :paramref:`.MetaData.schema` argument, where a referenced + table that is present in the "default" schema would fail since there + would be no way to indicate a :class:`.Table` that has "blank" for + a schema. The special symbol :attr:`.schema.BLANK_SCHEMA` has been + added as an available value for :paramref:`.Table.schema` and + :paramref:`.Sequence.schema`, indicating that the schema name + should be forced to be ``None`` even if :paramref:`.MetaData.schema` + is specified. + + .. change:: + :tags: bug, examples + :tickets: 3704 + + Fixed a regression that occurred in the + examples/vertical/dictlike-polymorphic.py example which prevented it + from running. + +.. changelog:: + :version: 1.0.13 + :released: May 16, 2016 + + .. change:: + :tags: bug, orm + :tickets: 3700 + + Fixed bug in "evaluate" strategy of :meth:`.Query.update` and + :meth:`.Query.delete` which would fail to accommodate a bound + parameter with a "callable" value, as which occurs when filtering + by a many-to-one equality expression along a relationship. + + .. change:: + :tags: bug, postgresql + :tickets: 3715 + + Added disconnect detection support for the error string + "SSL error: decryption failed or bad record mac". Pull + request courtesy Iuri de Silvio. + + .. change:: + :tags: bug, mssql + :tickets: 3711 + + Fixed bug where by ROW_NUMBER OVER clause applied for OFFSET + selects in SQL Server would inappropriately substitute a plain column + from the local statement that overlaps with a label name used by + the ORDER BY criteria of the statement. + + .. change:: + :tags: bug, orm + :tickets: 3710 + + Fixed bug whereby the event listeners used for backrefs could + be inadvertently applied multiple times, when using a deep class + inheritance hierarchy in conjunction with mutiple mapper configuration + steps. + + .. change:: + :tags: bug, orm + :tickets: 3706 + + Fixed bug whereby passing a :func:`.text` construct to the + :meth:`.Query.group_by` method would raise an error, instead + of intepreting the object as a SQL fragment. + + .. change:: + :tags: bug, oracle + :tickets: 3705 + + Fixed a bug in the cx_Oracle connect process that caused a TypeError + when the either the user, password or dsn was empty. This prevented + external authentication to Oracle databases, and prevented connecting + to the default dsn. The connect string oracle:// now logs into the + default dsn using the Operating System username, equivalent to + connecting using '/' with sqlplus. + + .. change:: + :tags: bug, oracle + :tickets: 3699 + + Fixed a bug in the result proxy used mainly by Oracle when binary and + other LOB types are in play, such that when query / statement caching + were used, the type-level result processors, notably that required by + the binary type itself but also any other processor, would become lost + after the first run of the statement due to it being removed from the + cached result metadata. + + .. change:: + :tags: bug, examples + :tickets: 3698 + + Changed the "directed graph" example to no longer consider + integer identifiers of nodes as significant; the "higher" / "lower" + references now allow mutual edges in both directions. + + .. change:: + :tags: bug, sql + :tickets: 3690 + + Fixed bug where when using ``case_sensitive=False`` with an + :class:`.Engine`, the result set would fail to correctly accomodate + for duplicate column names in the result set, causing an error + when the statement is executed in 1.0, and preventing the + "ambiguous column" exception from functioning in 1.1. + + .. change:: + :tags: bug, sql + :tickets: 3682 + + Fixed bug where the negation of an EXISTS expression would not + be properly typed as boolean in the result, and also would fail to be + anonymously aliased in a SELECT list as is the case with a + non-negated EXISTS construct. + + .. change:: + :tags: bug, sql + :tickets: 3666 + + Fixed bug where "unconsumed column names" exception would fail to + be raised in the case where :meth:`.Insert.values` were called + with a list of parameter mappings, instead of a single mapping + of parameters. Pull request courtesy Athena Yao. + + .. change:: + :tags: bug, orm + :tickets: 3663 + + Anonymous labeling is applied to a :attr:`.func` construct that is + passed to :func:`.column_property`, so that if the same attribute + is referred to as a column expression twice the names are de-duped, + thus avoiding "ambiguous column" errors. Previously, the + ``.label(None)`` would need to be applied in order for the name + to be de-anonymized. + + .. change:: + :tags: bug, py3k + :tickets: 3660 + + Fixed bug in "to_list" conversion where a single bytes object + would be turned into a list of individual characters. This would + impact among other things using the :meth:`.Query.get` method + on a primary key that's a bytes object. + + .. change:: + :tags: bug, orm + :tickets: 3658 + + Fixed regression appearing in the 1.0 series in ORM loading where the + exception raised for an expected column missing would incorrectly + be a ``NoneType`` error, rather than the expected + :class:`.NoSuchColumnError`. + + .. change:: + :tags: bug, mssql, oracle + :tickets: 3657 + + Fixed regression appearing in the 1.0 series which would cause the Oracle + and SQL Server dialects to incorrectly account for result set columns + when these dialects would wrap a SELECT in a subquery in order to + provide LIMIT/OFFSET behavior, and the original SELECT statement + referred to the same column multiple times, such as a column and + a label of that same column. This issue is related + to :ticket:`3658` in that when the error occurred, it would also + cause a ``NoneType`` error, rather than reporting that it couldn't + locate a column. + +.. changelog:: :version: 1.0.12 :released: February 15, 2016 diff -Nru sqlalchemy-1.0.12+ds1/doc/build/changelog/migration_04.rst sqlalchemy-1.0.14+ds1/doc/build/changelog/migration_04.rst --- sqlalchemy-1.0.12+ds1/doc/build/changelog/migration_04.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/changelog/migration_04.rst 2016-07-06 21:09:34.000000000 +0000 @@ -412,7 +412,7 @@ foo.bars.append(Bar(name='lala')) for bar in foo.bars.filter(Bar.name=='lala'): - print bar + print(bar) session.commit() diff -Nru sqlalchemy-1.0.12+ds1/doc/build/changelog/migration_05.rst sqlalchemy-1.0.14+ds1/doc/build/changelog/migration_05.rst --- sqlalchemy-1.0.12+ds1/doc/build/changelog/migration_05.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/changelog/migration_05.rst 2016-07-06 21:09:34.000000000 +0000 @@ -72,7 +72,7 @@ :: for row in session.query(User.name, func.count(Address.id).label('numaddresses')).join(Address).group_by(User.name): - print "name", row.name, "number", row.numaddresses + print("name", row.name, "number", row.numaddresses) ``Query`` has a ``statement`` accessor, as well as a ``subquery()`` method which allow ``Query`` to be used to @@ -144,7 +144,7 @@ :: for col in table.c: - print col + print(col) Work with a specific column: @@ -606,7 +606,7 @@ from sqlalchemy.orm import aliased address_alias = aliased(Address) - print session.query(User, address_alias).join((address_alias, User.addresses)).all() + print(session.query(User, address_alias).join((address_alias, User.addresses)).all()) * ``sqlalchemy.orm.Mapper`` diff -Nru sqlalchemy-1.0.12+ds1/doc/build/changelog/migration_06.rst sqlalchemy-1.0.14+ds1/doc/build/changelog/migration_06.rst --- sqlalchemy-1.0.12+ds1/doc/build/changelog/migration_06.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/changelog/migration_06.rst 2016-07-06 21:09:34.000000000 +0000 @@ -180,7 +180,7 @@ :: >>> if column('foo') == 5: - ... print "yes" + ... print("yes") ... In previous versions of SQLAlchemy, the returned @@ -205,7 +205,7 @@ :: if expression: - print "the expression is:", expression + print("the expression is:", expression) Would not evaluate if ``expression`` was a binary clause. Since the above pattern should never be used, the base @@ -227,7 +227,7 @@ :: if expression is not None: - print "the expression is:", expression + print("the expression is:", expression) Keep in mind, **this applies to Table and Column objects too**. @@ -415,7 +415,7 @@ create = CreateTable(mytable) # dumps the CREATE TABLE as a string - print create + print(create) # executes the CREATE TABLE statement engine.execute(create) @@ -568,7 +568,7 @@ from sqlalchemy.engine.reflection import Inspector insp = Inspector.from_engine(my_engine) - print insp.get_schema_names() + print(insp.get_schema_names()) the ``from_engine()`` method will in some cases provide a backend-specific inspector with additional capabilities, @@ -581,7 +581,7 @@ my_engine = create_engine('postgresql://...') pg_insp = Inspector.from_engine(my_engine) - print pg_insp.get_table_oid('my_table') + print(pg_insp.get_table_oid('my_table')) RETURNING Support ================= @@ -603,7 +603,7 @@ table.insert().values(data='some data').returning(table.c.id, table.c.timestamp) ) row = result.first() - print "ID:", row['id'], "Timestamp:", row['timestamp'] + print("ID:", row['id'], "Timestamp:", row['timestamp']) The implementation of RETURNING across the four supported backends varies wildly, in the case of Oracle requiring an diff -Nru sqlalchemy-1.0.12+ds1/doc/build/changelog/migration_07.rst sqlalchemy-1.0.14+ds1/doc/build/changelog/migration_07.rst --- sqlalchemy-1.0.12+ds1/doc/build/changelog/migration_07.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/changelog/migration_07.rst 2016-07-06 21:09:34.000000000 +0000 @@ -398,7 +398,7 @@ label('avg') ]) - print s + print(s) SQL: @@ -997,7 +997,7 @@ :: - print s.query(Parent).with_polymorphic([Child]).filter(Child.id > 7) + print(s.query(Parent).with_polymorphic([Child]).filter(Child.id > 7)) Which on both 0.6 and 0.7 renders: diff -Nru sqlalchemy-1.0.12+ds1/doc/build/changelog/migration_08.rst sqlalchemy-1.0.14+ds1/doc/build/changelog/migration_08.rst --- sqlalchemy-1.0.12+ds1/doc/build/changelog/migration_08.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/changelog/migration_08.rst 2016-07-06 21:09:34.000000000 +0000 @@ -282,7 +282,7 @@ >>> # an expression - >>> print b.expression + >>> print(b.expression) "user".id = address.user_id >>> # inspect works on instances @@ -432,7 +432,7 @@ @event.listens_for("load", Base, propagate=True) def on_load(target, context): - print "New instance loaded:", target + print("New instance loaded:", target) # on_load() will be applied to SomeClass class SomeClass(Base): @@ -578,7 +578,7 @@ Caching Example now uses dogpile.cache --------------------------------------- -The caching example now uses `dogpile.cache `_. +The caching example now uses `dogpile.cache `_. Dogpile.cache is a rewrite of the caching portion of Beaker, featuring vastly simpler and faster operation, as well as support for distributed locking. @@ -665,7 +665,7 @@ ) stmt = select([data.c.x.log(data.c.y)]).where(data.c.x.log(2) < value) - print conn.execute(stmt).fetchall() + print(conn.execute(stmt).fetchall()) New features which have come from this immediately include @@ -738,7 +738,7 @@ whenever the ``test_table.c.data`` column is rendered in the columns clause of a SELECT statement:: - >>> print select([test_table]).where(test_table.c.data == 'HI') + >>> print(select([test_table]).where(test_table.c.data == 'HI')) SELECT lower(test_table.data) AS data FROM test_table WHERE test_table.data = lower(:data_1) @@ -764,7 +764,7 @@ engine = create_engine("postgresql://scott:tiger@localhost/test") insp = inspect(engine) - print insp.get_table_names() + print(insp.get_table_names()) It can also be applied to any :class:`.ClauseElement`, which returns the :class:`.ClauseElement` itself, such as :class:`.Table`, :class:`.Column`, @@ -947,7 +947,7 @@ when features such as :meth:`.MetaData.create_all` and :func:`.cast` is used:: >>> stmt = select([cast(sometable.c.somechar, String(20, collation='utf8'))]) - >>> print stmt + >>> print(stmt) SELECT CAST(sometable.somechar AS VARCHAR(20) COLLATE "utf8") AS anon_1 FROM sometable @@ -1208,7 +1208,7 @@ s2 = select([t1, t2]).where(t1.c.x == t2.c.y).where(t1.c.x == s) - print (s2) + print(s2) SELECT t1.x, t2.y FROM t1, t2 WHERE t1.x = t2.y AND t1.x = diff -Nru sqlalchemy-1.0.12+ds1/doc/build/changelog/migration_09.rst sqlalchemy-1.0.14+ds1/doc/build/changelog/migration_09.rst --- sqlalchemy-1.0.12+ds1/doc/build/changelog/migration_09.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/changelog/migration_09.rst 2016-07-06 21:09:34.000000000 +0000 @@ -550,7 +550,7 @@ Previously, an expression like the following:: - print (column('x') == 'somevalue').collate("en_EN") + print((column('x') == 'somevalue').collate("en_EN")) would produce an expression like this:: @@ -567,7 +567,7 @@ The potentially backwards incompatible change arises if the :meth:`.collate` operator is being applied to the right-hand column, as follows:: - print column('x') == literal('somevalue').collate("en_EN") + print(column('x') == literal('somevalue').collate("en_EN")) In 0.8, this produces:: @@ -584,11 +584,11 @@ generated:: >>> # 0.8 - >>> print column('x').collate('en_EN').desc() + >>> print(column('x').collate('en_EN').desc()) (x COLLATE en_EN) DESC >>> # 0.9 - >>> print column('x').collate('en_EN').desc() + >>> print(column('x').collate('en_EN').desc()) x COLLATE en_EN DESC :ticket:`2879` @@ -606,7 +606,7 @@ >>> from sqlalchemy.dialects import postgresql >>> type = postgresql.ENUM('one', 'two', "three's", name="myenum") >>> from sqlalchemy.dialects.postgresql import base - >>> print base.CreateEnumType(type).compile(dialect=postgresql.dialect()) + >>> print(base.CreateEnumType(type).compile(dialect=postgresql.dialect())) CREATE TYPE myenum AS ENUM ('one','two','three''s') Existing workarounds which already escape single quote signs will need to be @@ -1085,7 +1085,7 @@ session.commit() # collection-based relationships are by default named "_collection" - print (u1.address_collection) + print(u1.address_collection) Beyond that, the :class:`.AutomapBase` class is a declarative base, and supports all the features that declarative does. The "automapping" feature can be used @@ -1095,7 +1095,7 @@ It is hoped that the :class:`.AutomapBase` system provides a quick and modernized solution to the problem that the very famous -`SQLSoup `_ +`SQLSoup `_ also tries to solve, that of generating a quick and rudimentary object model from an existing database on the fly. By addressing the issue strictly at the mapper configuration level, and integrating fully with existing @@ -1528,41 +1528,41 @@ >>> from sqlalchemy import select, and_, false, true >>> from sqlalchemy.dialects import mysql, postgresql - >>> print select([t1]).where(t1.c.x).compile(dialect=mysql.dialect()) + >>> print(select([t1]).where(t1.c.x).compile(dialect=mysql.dialect())) SELECT t.x, t.y FROM t WHERE t.x = 1 The :func:`.and_` and :func:`.or_` constructs will now exhibit quasi "short circuit" behavior, that is truncating a rendered expression, when a :func:`.true` or :func:`.false` constant is present:: - >>> print select([t1]).where(and_(t1.c.y > 5, false())).compile( - ... dialect=postgresql.dialect()) + >>> print(select([t1]).where(and_(t1.c.y > 5, false())).compile( + ... dialect=postgresql.dialect())) SELECT t.x, t.y FROM t WHERE false :func:`.true` can be used as the base to build up an expression:: >>> expr = true() >>> expr = expr & (t1.c.y > 5) - >>> print select([t1]).where(expr) + >>> print(select([t1]).where(expr)) SELECT t.x, t.y FROM t WHERE t.y > :y_1 The boolean constants :func:`.true` and :func:`.false` themselves render as ``0 = 1`` and ``1 = 1`` for a backend with no boolean constants:: - >>> print select([t1]).where(and_(t1.c.y > 5, false())).compile( - ... dialect=mysql.dialect()) + >>> print(select([t1]).where(and_(t1.c.y > 5, false())).compile( + ... dialect=mysql.dialect())) SELECT t.x, t.y FROM t WHERE 0 = 1 Interpretation of ``None``, while not particularly valid SQL, is at least now consistent:: - >>> print select([t1.c.x]).where(None) + >>> print(select([t1.c.x]).where(None)) SELECT t.x FROM t WHERE NULL - >>> print select([t1.c.x]).where(None).where(None) + >>> print(select([t1.c.x]).where(None).where(None)) SELECT t.x FROM t WHERE NULL AND NULL - >>> print select([t1.c.x]).where(and_(None, None)) + >>> print(select([t1.c.x]).where(and_(None, None))) SELECT t.x FROM t WHERE NULL AND NULL :ticket:`2804` @@ -1586,7 +1586,7 @@ stmt = select([expr]).order_by(expr) - print stmt + print(stmt) Prior to 0.9 would render as:: diff -Nru sqlalchemy-1.0.12+ds1/doc/build/changelog/migration_10.rst sqlalchemy-1.0.14+ds1/doc/build/changelog/migration_10.rst --- sqlalchemy-1.0.12+ds1/doc/build/changelog/migration_10.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/changelog/migration_10.rst 2016-07-06 21:09:34.000000000 +0000 @@ -530,7 +530,7 @@ A simple scenario that included "A.b" twice would fail to render correctly:: - print sess.query(A, a1).order_by(a1.b) + print(sess.query(A, a1).order_by(a1.b)) This would order by the wrong column:: @@ -845,7 +845,7 @@ Column('y', Integer, default=func.somefunction())) stmt = select([t.c.x]) - print t.insert().from_select(['x'], stmt) + print(t.insert().from_select(['x'], stmt)) Will render:: @@ -1384,7 +1384,7 @@ A query that joins to ``A.bs`` twice:: - print s.query(A).join(A.bs).join(A.bs) + print(s.query(A).join(A.bs).join(A.bs)) Will render:: @@ -1407,7 +1407,7 @@ The bigger change involves when joining to an entity without using a relationship-bound path. If we join to ``B`` twice:: - print s.query(A).join(B, B.a_id == A.id).join(B, B.a_id == A.id) + print(s.query(A).join(B, B.a_id == A.id).join(B, B.a_id == A.id)) In 0.9, this would render as follows:: @@ -1467,9 +1467,9 @@ s = Session() - print s.query(ASub1).join(B, ASub1.b).join(ASub2, B.a) + print(s.query(ASub1).join(B, ASub1.b).join(ASub2, B.a)) - print s.query(ASub1).join(B, ASub1.b).join(ASub2, ASub2.id == B.a_id) + print(s.query(ASub1).join(B, ASub1.b).join(ASub2, ASub2.id == B.a_id)) The two queries at the bottom are equivalent, and should both render the identical SQL:: @@ -1499,7 +1499,7 @@ asub2_alias = aliased(ASub2) - print s.query(ASub1).join(B, ASub1.b).join(asub2_alias, B.a.of_type(asub2_alias)) + print(s.query(ASub1).join(B, ASub1.b).join(asub2_alias, B.a.of_type(asub2_alias))) :ticket:`3233` :ticket:`3367` diff -Nru sqlalchemy-1.0.12+ds1/doc/build/conf.py sqlalchemy-1.0.14+ds1/doc/build/conf.py --- sqlalchemy-1.0.12+ds1/doc/build/conf.py 2016-02-15 18:43:31.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/conf.py 2016-07-06 21:10:07.000000000 +0000 @@ -13,25 +13,6 @@ import sys import os -import traceback - -def force_install_reqs(): - import logging - - log = logging.getLogger("pip") - handler = logging.StreamHandler(sys.stderr) - handler.setFormatter(logging.Formatter("[pip] %(message)s")) - log.addHandler(handler) - log.setLevel(logging.INFO) - - log.info("READTHEDOCS is set, force-installing requirements.txt") - - from pip.commands import install - req = os.path.join(os.path.dirname(__file__), "requirements.txt") - cmd = install.InstallCommand() - options, args = cmd.parse_args(["-v", "-U", "-r", req]) - cmd.run(options, args) - # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -42,18 +23,6 @@ import sqlalchemy -# attempt to force pip to definitely get the latest -# versions of libraries, see -# https://github.com/rtfd/readthedocs.org/issues/1293 -rtd = os.environ.get('READTHEDOCS', None) == 'True' -if rtd: - try: - force_install_reqs() - except: - traceback.print_exc() - - - # -- General configuration ----------------------------------------------------- @@ -138,9 +107,9 @@ # The short X.Y version. version = "1.0" # The full version, including alpha/beta/rc tags. -release = "1.0.12" +release = "1.0.14" -release_date = "February 15, 2016" +release_date = "July 6, 2016" site_base = os.environ.get("RTD_SITE_BASE", "http://www.sqlalchemy.org") site_adapter_template = "docs_adapter.mako" @@ -373,7 +342,7 @@ #epub_tocdup = True intersphinx_mapping = { - 'alembic': ('http://alembic.readthedocs.org/en/latest/', None), + 'alembic': ('http://alembic.zzzcomputing.com/en/latest/', None), 'psycopg2': ('http://pythonhosted.org/psycopg2', None), } diff -Nru sqlalchemy-1.0.12+ds1/doc/build/core/connections.rst sqlalchemy-1.0.14+ds1/doc/build/core/connections.rst --- sqlalchemy-1.0.12+ds1/doc/build/core/connections.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/core/connections.rst 2016-07-06 21:09:34.000000000 +0000 @@ -48,7 +48,7 @@ connection = engine.connect() result = connection.execute("select username from users") for row in result: - print "username:", row['username'] + print("username:", row['username']) connection.close() The connection is an instance of :class:`.Connection`, @@ -76,7 +76,7 @@ result = engine.execute("select username from users") for row in result: - print "username:", row['username'] + print("username:", row['username']) Where above, the :meth:`~.Engine.execute` method acquires a new :class:`.Connection` on its own, executes the statement with that object, @@ -251,7 +251,7 @@ result = engine.execute("select username from users") for row in result: - print "username:", row['username'] + print("username:", row['username']) In addition to "connectionless" execution, it is also possible to use the :meth:`~.Executable.execute` method of diff -Nru sqlalchemy-1.0.12+ds1/doc/build/core/constraints.rst sqlalchemy-1.0.14+ds1/doc/build/core/constraints.rst --- sqlalchemy-1.0.12+ds1/doc/build/core/constraints.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/core/constraints.rst 2016-07-06 21:09:34.000000000 +0000 @@ -426,7 +426,7 @@ without an explicit name being specified. The use case of alteration of existing tables and constraints can be handled -by schema migration tools such as `Alembic `_. +by schema migration tools such as `Alembic `_. However, neither Alembic nor SQLAlchemy currently create names for constraint objects where the name is otherwise unspecified, leading to the case where being able to alter existing constraints means that one must reverse-engineer diff -Nru sqlalchemy-1.0.12+ds1/doc/build/core/custom_types.rst sqlalchemy-1.0.14+ds1/doc/build/core/custom_types.rst --- sqlalchemy-1.0.12+ds1/doc/build/core/custom_types.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/core/custom_types.rst 2016-07-06 21:09:34.000000000 +0000 @@ -297,8 +297,8 @@ Column('geom_data', Geometry) ) - print select([geometry]).where( - geometry.c.geom_data == 'LINESTRING(189412 252431,189631 259122)') + print(select([geometry]).where( + geometry.c.geom_data == 'LINESTRING(189412 252431,189631 259122)')) The resulting SQL embeds both functions as appropriate. ``ST_AsText`` is applied to the columns clause so that the return value is run through @@ -315,7 +315,7 @@ a :func:`.select` against a :func:`.label` of our expression, the string label is moved to the outside of the wrapped expression:: - print select([geometry.c.geom_data.label('my_data')]) + print(select([geometry.c.geom_data.label('my_data')])) Output:: @@ -361,10 +361,10 @@ conn.execute(message.insert(), username="some user", message="this is my message") - print conn.scalar( + print(conn.scalar( select([message.c.message]).\ where(message.c.username == "some user") - ) + )) The ``pgp_sym_encrypt`` and ``pgp_sym_decrypt`` functions are applied to the INSERT and SELECT statements:: @@ -425,7 +425,7 @@ Usage:: >>> sometable = Table("sometable", metadata, Column("data", MyInt)) - >>> print sometable.c.data + 5 + >>> print(sometable.c.data + 5) sometable.data goofy :data_1 The implementation for :meth:`.ColumnOperators.__add__` is consulted @@ -452,7 +452,7 @@ Using the above type:: - >>> print sometable.c.data.log(5) + >>> print(sometable.c.data.log(5)) log(:log_1, :log_2) @@ -475,7 +475,7 @@ Using the above type:: >>> from sqlalchemy.sql import column - >>> print column('x', MyInteger).factorial() + >>> print(column('x', MyInteger).factorial()) x ! See also: diff -Nru sqlalchemy-1.0.12+ds1/doc/build/core/event.rst sqlalchemy-1.0.14+ds1/doc/build/core/event.rst --- sqlalchemy-1.0.12+ds1/doc/build/core/event.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/core/event.rst 2016-07-06 21:09:34.000000000 +0000 @@ -30,7 +30,7 @@ from sqlalchemy.pool import Pool def my_on_connect(dbapi_con, connection_record): - print "New DBAPI connection:", dbapi_con + print("New DBAPI connection:", dbapi_con) listen(Pool, 'connect', my_on_connect) @@ -41,7 +41,7 @@ @listens_for(Pool, "connect") def my_on_connect(dbapi_con, connection_record): - print "New DBAPI connection:", dbapi_con + print("New DBAPI connection:", dbapi_con) Named Argument Styles --------------------- diff -Nru sqlalchemy-1.0.12+ds1/doc/build/core/metadata.rst sqlalchemy-1.0.14+ds1/doc/build/core/metadata.rst --- sqlalchemy-1.0.12+ds1/doc/build/core/metadata.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/core/metadata.rst 2016-07-06 21:09:34.000000000 +0000 @@ -58,7 +58,7 @@ references):: >>> for t in metadata.sorted_tables: - ... print t.name + ... print(t.name) user user_preference invoice @@ -93,15 +93,15 @@ # iterate through all columns for c in employees.c: - print c + print(c) # get the table's primary key columns for primary_key in employees.primary_key: - print primary_key + print(primary_key) # get the table's foreign key objects: for fkey in employees.foreign_keys: - print fkey + print(fkey) # access the table's MetaData: employees.metadata @@ -243,7 +243,7 @@ There are two major migration tools available for SQLAlchemy: -* `Alembic `_ - Written by the author of SQLAlchemy, +* `Alembic `_ - Written by the author of SQLAlchemy, Alembic features a highly customizable environment and a minimalistic usage pattern, supporting such features as transactional DDL, automatic generation of "candidate" migrations, an "offline" mode which generates SQL scripts, and support for branch @@ -303,6 +303,23 @@ Column, Table, MetaData API --------------------------- +.. attribute:: sqlalchemy.schema.BLANK_SCHEMA + + Symbol indicating that a :class:`.Table` or :class:`.Sequence` + should have 'None' for its schema, even if the parent + :class:`.MetaData` has specified a schema. + + .. seealso:: + + :paramref:`.MetaData.schema` + + :paramref:`.Table.schema` + + :paramref:`.Sequence.schema` + + .. versionadded:: 1.0.14 + + .. autoclass:: Column :members: :inherited-members: diff -Nru sqlalchemy-1.0.12+ds1/doc/build/core/pooling.rst sqlalchemy-1.0.14+ds1/doc/build/core/pooling.rst --- sqlalchemy-1.0.12+ds1/doc/build/core/pooling.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/core/pooling.rst 2016-07-06 21:09:34.000000000 +0000 @@ -195,7 +195,7 @@ except exc.DBAPIError, e: # an exception is raised, Connection is invalidated. if e.connection_invalidated: - print "Connection was invalidated!" + print("Connection was invalidated!") # after the invalidate event, a new connection # starts with a new Pool @@ -253,6 +253,11 @@ # we don't want to bother pinging on these. return + # turn off "close with result". This flag is only used with + # "connectionless" execution, otherwise will be False in any case + save_should_close_with_result = connection.should_close_with_result + connection.should_close_with_result = False + try: # run a SELECT 1. use a core select() so that # the SELECT of a scalar value without a table is @@ -272,6 +277,9 @@ connection.scalar(select([1])) else: raise + finally: + # restore "close with result" + connection.should_close_with_result = save_should_close_with_result The above recipe has the advantage that we are making use of SQLAlchemy's facilities for detecting those DBAPI exceptions that are known to indicate diff -Nru sqlalchemy-1.0.12+ds1/doc/build/core/reflection.rst sqlalchemy-1.0.14+ds1/doc/build/core/reflection.rst --- sqlalchemy-1.0.12+ds1/doc/build/core/reflection.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/core/reflection.rst 2016-07-06 21:09:34.000000000 +0000 @@ -125,7 +125,7 @@ from sqlalchemy.engine import reflection engine = create_engine('...') insp = reflection.Inspector.from_engine(engine) - print insp.get_table_names() + print(insp.get_table_names()) .. autoclass:: sqlalchemy.engine.reflection.Inspector :members: diff -Nru sqlalchemy-1.0.12+ds1/doc/build/faq/metadata_schema.rst sqlalchemy-1.0.14+ds1/doc/build/faq/metadata_schema.rst --- sqlalchemy-1.0.12+ds1/doc/build/faq/metadata_schema.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/faq/metadata_schema.rst 2016-07-06 21:09:34.000000000 +0000 @@ -64,7 +64,7 @@ # ... add Table objects to metadata ti = metadata.sorted_tables: for t in ti: - print t + print(t) How can I get the CREATE TABLE/ DROP TABLE output as a string? =========================================================================== @@ -74,17 +74,17 @@ from sqlalchemy.schema import CreateTable - print CreateTable(mytable) + print(CreateTable(mytable)) To get the string specific to a certain engine:: - print CreateTable(mytable).compile(engine) + print(CreateTable(mytable).compile(engine)) There's also a special form of :class:`.Engine` that can let you dump an entire metadata creation sequence, using this recipe:: def dump(sql, *multiparams, **params): - print sql.compile(dialect=engine.dialect) + print(sql.compile(dialect=engine.dialect)) engine = create_engine('postgresql://', strategy='mock', executor=dump) metadata.create_all(engine, checkfirst=False) diff -Nru sqlalchemy-1.0.12+ds1/doc/build/faq/ormconfiguration.rst sqlalchemy-1.0.14+ds1/doc/build/faq/ormconfiguration.rst --- sqlalchemy-1.0.12+ds1/doc/build/faq/ormconfiguration.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/faq/ormconfiguration.rst 2016-07-06 21:09:34.000000000 +0000 @@ -89,7 +89,8 @@ mapper = inspect(MyClass) -From there, all information about the class can be acquired using such methods as: +From there, all information about the class can be accessed through properties +such as: * :attr:`.Mapper.attrs` - a namespace of all mapped attributes. The attributes themselves are instances of :class:`.MapperProperty`, which contain additional @@ -293,7 +294,7 @@ ORDER BY anon_1.users_id Depending on database specifics, there is -a chance we may get the a result like the following for the two queries:: +a chance we may get a result like the following for the two queries:: -- query #1 +--------+ diff -Nru sqlalchemy-1.0.12+ds1/doc/build/faq/performance.rst sqlalchemy-1.0.14+ds1/doc/build/faq/performance.rst --- sqlalchemy-1.0.12+ds1/doc/build/faq/performance.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/faq/performance.rst 2016-07-06 21:09:34.000000000 +0000 @@ -104,7 +104,7 @@ ps.print_stats() # uncomment this to see who's calling what # ps.print_callers() - print s.getvalue() + print(s.getvalue()) To profile a section of code:: diff -Nru sqlalchemy-1.0.12+ds1/doc/build/faq/sessions.rst sqlalchemy-1.0.14+ds1/doc/build/faq/sessions.rst --- sqlalchemy-1.0.12+ds1/doc/build/faq/sessions.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/faq/sessions.rst 2016-07-06 21:09:34.000000000 +0000 @@ -282,11 +282,11 @@ class Iterates(object): def __len__(self): - print "LEN!" + print("LEN!") return 5 def __iter__(self): - print "ITER!" + print("ITER!") return iter([1, 2, 3, 4, 5]) list(Iterates()) @@ -477,7 +477,7 @@ for obj in walk(a1): - print obj + print(obj) Output:: diff -Nru sqlalchemy-1.0.12+ds1/doc/build/glossary.rst sqlalchemy-1.0.14+ds1/doc/build/glossary.rst --- sqlalchemy-1.0.12+ds1/doc/build/glossary.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/glossary.rst 2016-07-06 21:09:34.000000000 +0000 @@ -60,7 +60,7 @@ ``__delete__()`` methods. The :class:`.InstrumentedAttribute` will generate a SQL expression when used at the class level:: - >>> print MyClass.data == 5 + >>> print(MyClass.data == 5) data = :data_1 and at the instance level, keeps track of changes to values, @@ -103,7 +103,7 @@ Instrumentation refers to the process of augmenting the functionality and attribute set of a particular class. Ideally, the behavior of the class should remain close to a regular - class, except that additional behviors and features are + class, except that additional behaviors and features are made available. The SQLAlchemy :term:`mapping` process, among other things, adds database-enabled :term:`descriptors` to a mapped @@ -246,7 +246,7 @@ transactional resources", to indicate more explicitly that what we are actually "releasing" is any transactional state which as accumulated upon the connection. In most - situations, the proces of selecting from tables, emitting + situations, the process of selecting from tables, emitting updates, etc. acquires :term:`isolated` state upon that connection as well as potential row or table locks. This state is all local to a particular transaction @@ -360,7 +360,7 @@ comprises the WHERE clause of the ``SELECT``. FROM clause - The portion of the ``SELECT`` statement which incicates the initial + The portion of the ``SELECT`` statement which indicates the initial source of rows. A simple ``SELECT`` will feature one or more table names in its diff -Nru sqlalchemy-1.0.12+ds1/doc/build/orm/backref.rst sqlalchemy-1.0.14+ds1/doc/build/orm/backref.rst --- sqlalchemy-1.0.12+ds1/doc/build/orm/backref.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/orm/backref.rst 2016-07-06 21:09:34.000000000 +0000 @@ -71,7 +71,7 @@ >>> a1 = Address() >>> u1.addresses [] - >>> print a1.user + >>> print(a1.user) None However, once the ``Address`` is appended to the ``u1.addresses`` collection, @@ -144,10 +144,10 @@ We can observe, by inspecting the resulting property, that both sides of the relationship have this join condition applied:: - >>> print User.addresses.property.primaryjoin + >>> print(User.addresses.property.primaryjoin) "user".id = address.user_id AND address.email LIKE :email_1 || '%%' >>> - >>> print Address.user.property.primaryjoin + >>> print(Address.user.property.primaryjoin) "user".id = address.user_id AND address.email LIKE :email_1 || '%%' >>> diff -Nru sqlalchemy-1.0.12+ds1/doc/build/orm/basic_relationships.rst sqlalchemy-1.0.14+ds1/doc/build/orm/basic_relationships.rst --- sqlalchemy-1.0.12+ds1/doc/build/orm/basic_relationships.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/orm/basic_relationships.rst 2016-07-06 21:09:34.000000000 +0000 @@ -359,8 +359,8 @@ # iterate through child objects via association, including association # attributes for assoc in p.children: - print assoc.extra_data - print assoc.child + print(assoc.extra_data) + print(assoc.child) To enhance the association object pattern such that direct access to the ``Association`` object is optional, SQLAlchemy diff -Nru sqlalchemy-1.0.12+ds1/doc/build/orm/cascades.rst sqlalchemy-1.0.14+ds1/doc/build/orm/cascades.rst --- sqlalchemy-1.0.12+ds1/doc/build/orm/cascades.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/orm/cascades.rst 2016-07-06 21:09:34.000000000 +0000 @@ -17,7 +17,7 @@ these settings are appropriate for related objects which only exist as long as they are attached to their parent, and are otherwise deleted. -Cascade behavior is configured using the by changing the +Cascade behavior is configured using the :paramref:`~.relationship.cascade` option on :func:`~sqlalchemy.orm.relationship`:: @@ -341,7 +341,7 @@ }) If an ``Order`` is already in the session, and is assigned to the ``order`` -attribute of an ``Item``, the backref appends the ``Order`` to the ``items`` +attribute of an ``Item``, the backref appends the ``Item`` to the ``items`` collection of that ``Order``, resulting in the ``save-update`` cascade taking place:: diff -Nru sqlalchemy-1.0.12+ds1/doc/build/orm/collections.rst sqlalchemy-1.0.14+ds1/doc/build/orm/collections.rst --- sqlalchemy-1.0.12+ds1/doc/build/orm/collections.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/orm/collections.rst 2016-07-06 21:09:34.000000000 +0000 @@ -168,7 +168,7 @@ parent = Parent() parent.children.append(Child()) - print parent.children[0] + print(parent.children[0]) Collections are not limited to lists. Sets, mutable sequences and almost any other Python object that can act as a container can be used in place of the diff -Nru sqlalchemy-1.0.12+ds1/doc/build/orm/composites.rst sqlalchemy-1.0.14+ds1/doc/build/orm/composites.rst --- sqlalchemy-1.0.12+ds1/doc/build/orm/composites.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/orm/composites.rst 2016-07-06 21:09:34.000000000 +0000 @@ -48,7 +48,7 @@ also should supply adequate ``__eq__()`` and ``__ne__()`` methods which test the equality of two instances. -We will create a mapping to a table ``vertice``, which represents two points +We will create a mapping to a table ``vertices``, which represents two points as ``x1/y1`` and ``x2/y2``. These are created normally as :class:`.Column` objects. Then, the :func:`.composite` function is used to assign new attributes that will represent sets of columns via the ``Point`` class:: @@ -60,7 +60,7 @@ Base = declarative_base() class Vertex(Base): - __tablename__ = 'vertice' + __tablename__ = 'vertices' id = Column(Integer, primary_key=True) x1 = Column(Integer) @@ -74,9 +74,9 @@ A classical mapping above would define each :func:`.composite` against the existing table:: - mapper(Vertex, vertice_table, properties={ - 'start':composite(Point, vertice_table.c.x1, vertice_table.c.y1), - 'end':composite(Point, vertice_table.c.x2, vertice_table.c.y2), + mapper(Vertex, vertices_table, properties={ + 'start':composite(Point, vertices_table.c.x1, vertices_table.c.y1), + 'end':composite(Point, vertices_table.c.x2, vertices_table.c.y2), }) We can now persist and use ``Vertex`` instances, as well as query for them, @@ -87,17 +87,17 @@ >>> v = Vertex(start=Point(3, 4), end=Point(5, 6)) >>> session.add(v) >>> q = session.query(Vertex).filter(Vertex.start == Point(3, 4)) - {sql}>>> print q.first().start + {sql}>>> print(q.first().start) BEGIN (implicit) - INSERT INTO vertice (x1, y1, x2, y2) VALUES (?, ?, ?, ?) + INSERT INTO vertices (x1, y1, x2, y2) VALUES (?, ?, ?, ?) (3, 4, 5, 6) - SELECT vertice.id AS vertice_id, - vertice.x1 AS vertice_x1, - vertice.y1 AS vertice_y1, - vertice.x2 AS vertice_x2, - vertice.y2 AS vertice_y2 - FROM vertice - WHERE vertice.x1 = ? AND vertice.y1 = ? + SELECT vertices.id AS vertices_id, + vertices.x1 AS vertices_x1, + vertices.y1 AS vertices_y1, + vertices.x2 AS vertices_x2, + vertices.y2 AS vertices_y2 + FROM vertices + WHERE vertices.x1 = ? AND vertices.y1 = ? LIMIT ? OFFSET ? (3, 4, 1, 0) {stop}Point(x=3, y=4) @@ -145,7 +145,7 @@ other.__composite_values__())]) class Vertex(Base): - ___tablename__ = 'vertice' + ___tablename__ = 'vertices' id = Column(Integer, primary_key=True) x1 = Column(Integer) diff -Nru sqlalchemy-1.0.12+ds1/doc/build/orm/contextual.rst sqlalchemy-1.0.14+ds1/doc/build/orm/contextual.rst --- sqlalchemy-1.0.12+ds1/doc/build/orm/contextual.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/orm/contextual.rst 2016-07-06 21:09:34.000000000 +0000 @@ -96,9 +96,9 @@ # equivalent to: # # session = Session() - # print session.query(MyClass).all() + # print(session.query(MyClass).all()) # - print Session.query(MyClass).all() + print(Session.query(MyClass).all()) The above code accomplishes the same task as that of acquiring the current :class:`.Session` by calling upon the registry, then using that :class:`.Session`. diff -Nru sqlalchemy-1.0.12+ds1/doc/build/orm/extensions/declarative/mixins.rst sqlalchemy-1.0.14+ds1/doc/build/orm/extensions/declarative/mixins.rst --- sqlalchemy-1.0.12+ds1/doc/build/orm/extensions/declarative/mixins.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/orm/extensions/declarative/mixins.rst 2016-07-06 21:09:34.000000000 +0000 @@ -357,7 +357,7 @@ of ``StringAttribute`` objects, which are persisted into a table that's local to either the ``type_a_strings`` or ``type_b_strings`` table:: - >>> print ta._strings + >>> print(ta._strings) [<__main__.StringAttribute object at 0x10151cd90>, <__main__.StringAttribute object at 0x10151ce10>] diff -Nru sqlalchemy-1.0.12+ds1/doc/build/orm/loading_columns.rst sqlalchemy-1.0.14+ds1/doc/build/orm/loading_columns.rst --- sqlalchemy-1.0.12+ds1/doc/build/orm/loading_columns.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/orm/loading_columns.rst 2016-07-06 21:09:34.000000000 +0000 @@ -156,7 +156,7 @@ bn = Bundle('mybundle', MyClass.data1, MyClass.data2) for row in session.query(bn).filter(bn.c.data1 == 'd1'): - print row.mybundle.data1, row.mybundle.data2 + print(row.mybundle.data1, row.mybundle.data2) The bundle can be subclassed to provide custom behaviors when results are fetched. The method :meth:`.Bundle.create_row_processor` is given @@ -187,7 +187,7 @@ bn = DictBundle('mybundle', MyClass.data1, MyClass.data2) for row in session.query(bn).filter(bn.c.data1 == 'd1'): - print row.mybundle['data1'], row.mybundle['data2'] + print(row.mybundle['data1'], row.mybundle['data2']) The :class:`.Bundle` construct is also integrated into the behavior of :func:`.composite`, where it is used to return composite attributes as objects diff -Nru sqlalchemy-1.0.12+ds1/doc/build/orm/mapped_attributes.rst sqlalchemy-1.0.14+ds1/doc/build/orm/mapped_attributes.rst --- sqlalchemy-1.0.12+ds1/doc/build/orm/mapped_attributes.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/orm/mapped_attributes.rst 2016-07-06 21:09:34.000000000 +0000 @@ -254,10 +254,10 @@ ``.status`` that will behave as one attribute, both at the expression level:: - >>> print MyClass.job_status == 'some_status' + >>> print(MyClass.job_status == 'some_status') my_table.job_status = :job_status_1 - >>> print MyClass.status == 'some_status' + >>> print(MyClass.status == 'some_status') my_table.job_status = :job_status_1 and at the instance level:: diff -Nru sqlalchemy-1.0.12+ds1/doc/build/orm/mapped_sql_expr.rst sqlalchemy-1.0.14+ds1/doc/build/orm/mapped_sql_expr.rst --- sqlalchemy-1.0.12+ds1/doc/build/orm/mapped_sql_expr.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/orm/mapped_sql_expr.rst 2016-07-06 21:09:34.000000000 +0000 @@ -35,7 +35,7 @@ class level, so that it is available from an instance:: some_user = session.query(User).first() - print some_user.fullname + print(some_user.fullname) as well as usable within queries:: diff -Nru sqlalchemy-1.0.12+ds1/doc/build/orm/session_basics.rst sqlalchemy-1.0.14+ds1/doc/build/orm/session_basics.rst --- sqlalchemy-1.0.12+ds1/doc/build/orm/session_basics.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/orm/session_basics.rst 2016-07-06 21:09:34.000000000 +0000 @@ -410,7 +410,7 @@ The :class:`.Session` is not designed to be a global object from which everyone consults as a "registry" of objects. That's more the job of a **second level cache**. SQLAlchemy provides -a pattern for implementing second level caching using `dogpile.cache `_, +a pattern for implementing second level caching using `dogpile.cache `_, via the :ref:`examples_caching` example. How can I get the :class:`~sqlalchemy.orm.session.Session` for a certain object? diff -Nru sqlalchemy-1.0.12+ds1/doc/build/orm/session_state_management.rst sqlalchemy-1.0.14+ds1/doc/build/orm/session_state_management.rst --- sqlalchemy-1.0.12+ds1/doc/build/orm/session_state_management.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/orm/session_state_management.rst 2016-07-06 21:09:34.000000000 +0000 @@ -79,12 +79,12 @@ interface:: for obj in session: - print obj + print(obj) And presence may be tested for using regular "contains" semantics:: if obj in session: - print "Object is present" + print("Object is present") The session is also keeping track of all newly created (i.e. pending) objects, all objects which have had changes since they were last loaded or saved (i.e. diff -Nru sqlalchemy-1.0.12+ds1/doc/build/orm/session_transaction.rst sqlalchemy-1.0.14+ds1/doc/build/orm/session_transaction.rst --- sqlalchemy-1.0.12+ds1/doc/build/orm/session_transaction.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/orm/session_transaction.rst 2016-07-06 21:09:34.000000000 +0000 @@ -124,7 +124,7 @@ with session.begin_nested(): session.merge(record) except: - print "Skipped record %s" % record + print("Skipped record %s" % record) session.commit() .. _session_autocommit: diff -Nru sqlalchemy-1.0.12+ds1/doc/build/orm/tutorial.rst sqlalchemy-1.0.14+ds1/doc/build/orm/tutorial.rst --- sqlalchemy-1.0.12+ds1/doc/build/orm/tutorial.rst 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/doc/build/orm/tutorial.rst 2016-07-06 21:09:34.000000000 +0000 @@ -950,7 +950,7 @@ (224, 'fred') {stop} -To use an entirely string-based statement, using +To use an entirely string-based statement, use :meth:`~sqlalchemy.orm.query.Query.from_statement()`; just ensure that the columns clause of the statement contains the column names normally used by the mapper (below illustrated using an asterisk): @@ -2002,7 +2002,7 @@ ``User.posts``, we'd like to be able to filter results further so as not to load the entire collection. For this we use a setting accepted by :func:`~sqlalchemy.orm.relationship` called ``lazy='dynamic'``, which -configures an alternate **loader strategy** on the attribute:: +configures an alternate **loader strategy** on the attribute: .. sourcecode:: python+sql diff -Nru sqlalchemy-1.0.12+ds1/examples/dogpile_caching/__init__.py sqlalchemy-1.0.14+ds1/examples/dogpile_caching/__init__.py --- sqlalchemy-1.0.12+ds1/examples/dogpile_caching/__init__.py 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/examples/dogpile_caching/__init__.py 2016-07-06 21:09:34.000000000 +0000 @@ -1,5 +1,5 @@ """ -Illustrates how to embed `dogpile.cache `_ +Illustrates how to embed `dogpile.cache `_ functionality within the :class:`.Query` object, allowing full cache control as well as the ability to pull "lazy loaded" attributes from long term cache diff -Nru sqlalchemy-1.0.12+ds1/examples/graphs/directed_graph.py sqlalchemy-1.0.14+ds1/examples/graphs/directed_graph.py --- sqlalchemy-1.0.12+ds1/examples/graphs/directed_graph.py 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/examples/graphs/directed_graph.py 2016-07-06 21:09:34.000000000 +0000 @@ -1,57 +1,51 @@ """a directed graph example.""" -from sqlalchemy import MetaData, Table, Column, Integer, ForeignKey, \ +from sqlalchemy import Column, Integer, ForeignKey, \ create_engine -from sqlalchemy.orm import mapper, relationship, sessionmaker +from sqlalchemy.orm import relationship, sessionmaker from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() + class Node(Base): __tablename__ = 'node' node_id = Column(Integer, primary_key=True) - def __init__(self, id): - self.node_id = id - - def add_neighbors(self, *nodes): - for node in nodes: - Edge(self, node) - return self - def higher_neighbors(self): return [x.higher_node for x in self.lower_edges] def lower_neighbors(self): return [x.lower_node for x in self.higher_edges] + class Edge(Base): __tablename__ = 'edge' - lower_id = Column(Integer, - ForeignKey('node.node_id'), - primary_key=True) - - higher_id = Column(Integer, - ForeignKey('node.node_id'), - primary_key=True) - - lower_node = relationship(Node, - primaryjoin=lower_id==Node.node_id, - backref='lower_edges') - higher_node = relationship(Node, - primaryjoin=higher_id==Node.node_id, - backref='higher_edges') + lower_id = Column( + Integer, + ForeignKey('node.node_id'), + primary_key=True) + + higher_id = Column( + Integer, + ForeignKey('node.node_id'), + primary_key=True) + + lower_node = relationship( + Node, + primaryjoin=lower_id == Node.node_id, + backref='lower_edges') + + higher_node = relationship( + Node, + primaryjoin=higher_id == Node.node_id, + backref='higher_edges') - # here we have lower.node_id <= higher.node_id def __init__(self, n1, n2): - if n1.node_id < n2.node_id: - self.lower_node = n1 - self.higher_node = n2 - else: - self.lower_node = n2 - self.higher_node = n1 + self.lower_node = n1 + self.higher_node = n2 engine = create_engine('sqlite://', echo=True) Base.metadata.create_all(engine) @@ -59,29 +53,31 @@ session = sessionmaker(engine)() # create a directed graph like this: -# n1 -> n2 -> n5 +# n1 -> n2 -> n1 +# -> n5 # -> n7 # -> n3 -> n6 -n1 = Node(1) -n2 = Node(2) -n3 = Node(3) -n4 = Node(4) -n5 = Node(5) -n6 = Node(6) -n7 = Node(7) - -n2.add_neighbors(n5, n1) -n3.add_neighbors(n6) -n7.add_neighbors(n2) -n1.add_neighbors(n3) +n1 = Node() +n2 = Node() +n3 = Node() +n4 = Node() +n5 = Node() +n6 = Node() +n7 = Node() + +Edge(n1, n2) +Edge(n1, n3) +Edge(n2, n1) +Edge(n2, n5) +Edge(n2, n7) +Edge(n3, n6) session.add_all([n1, n2, n3, n4, n5, n6, n7]) session.commit() -assert [x.node_id for x in n3.higher_neighbors()] == [6] -assert [x.node_id for x in n3.lower_neighbors()] == [1] -assert [x.node_id for x in n2.lower_neighbors()] == [1] -assert [x.node_id for x in n2.higher_neighbors()] == [5,7] - +assert [x for x in n3.higher_neighbors()] == [n6] +assert [x for x in n3.lower_neighbors()] == [n1] +assert [x for x in n2.lower_neighbors()] == [n1] +assert [x for x in n2.higher_neighbors()] == [n1, n5, n7] diff -Nru sqlalchemy-1.0.12+ds1/examples/performance/short_selects.py sqlalchemy-1.0.14+ds1/examples/performance/short_selects.py --- sqlalchemy-1.0.12+ds1/examples/performance/short_selects.py 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/examples/performance/short_selects.py 2016-07-06 21:09:34.000000000 +0000 @@ -1,5 +1,5 @@ -"""This series of tests illustrates different ways to INSERT a large number -of rows in bulk. +"""This series of tests illustrates different ways to SELECT a single +record by primary key """ diff -Nru sqlalchemy-1.0.12+ds1/examples/vertical/dictlike-polymorphic.py sqlalchemy-1.0.14+ds1/examples/vertical/dictlike-polymorphic.py --- sqlalchemy-1.0.12+ds1/examples/vertical/dictlike-polymorphic.py 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/examples/vertical/dictlike-polymorphic.py 2016-07-06 21:09:34.000000000 +0000 @@ -134,7 +134,7 @@ char_value = Column(UnicodeText, info={'type': (str, 'string')}) boolean_value = Column(Boolean, info={'type': (bool, 'boolean')}) - class Animal(ProxiedDictMixin._base_class(Base)): + class Animal(ProxiedDictMixin, Base): """an Animal""" __tablename__ = 'animal' diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/connectors/mxodbc.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/connectors/mxodbc.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/connectors/mxodbc.py 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/connectors/mxodbc.py 2016-07-06 21:09:34.000000000 +0000 @@ -6,7 +6,7 @@ # the MIT License: http://www.opensource.org/licenses/mit-license.php """ -Provide an SQLALchemy connector for the eGenix mxODBC commercial +Provide a SQLALchemy connector for the eGenix mxODBC commercial Python adapter for ODBC. This is not a free product, but eGenix provides SQLAlchemy with a license for use in continuous integration testing. diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/dialects/mssql/base.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/dialects/mssql/base.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/dialects/mssql/base.py 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/dialects/mssql/base.py 2016-07-06 21:09:34.000000000 +0000 @@ -264,7 +264,7 @@ name VARCHAR(20) If ``nullable`` is ``True`` or ``False`` then the column will be -``NULL` or ``NOT NULL`` respectively. +``NULL`` or ``NOT NULL`` respectively. Date / Time Handling -------------------- @@ -433,6 +433,40 @@ This option can also be specified engine-wide using the ``implicit_returning=False`` argument on :func:`.create_engine`. +.. _mssql_rowcount_versioning: + +Rowcount Support / ORM Versioning +--------------------------------- + +The SQL Server drivers have very limited ability to return the number +of rows updated from an UPDATE or DELETE statement. In particular, the +pymssql driver has no support, whereas the pyodbc driver can only return +this value under certain conditions. + +In particular, updated rowcount is not available when OUTPUT INSERTED +is used. This impacts the SQLAlchemy ORM's versioning feature when +server-side versioning schemes are used. When +using pyodbc, the "implicit_returning" flag needs to be set to false +for any ORM mapped class that uses a version_id column in conjunction with +a server-side version generator:: + + class MyTable(Base): + __tablename__ = 'mytable' + id = Column(Integer, primary_key=True) + stuff = Column(String(10)) + timestamp = Column(TIMESTAMP(), default=text('DEFAULT')) + __mapper_args__ = { + 'version_id_col': timestamp, + 'version_id_generator': False, + } + __table_args__ = { + 'implicit_returning': False + } + +Without the implicit_returning flag above, the UPDATE statement will +use ``OUTPUT inserted.timestamp`` and the rowcount will be returned as +-1, causing the versioning logic to fail. + Enabling Snapshot Isolation --------------------------- @@ -1095,7 +1129,11 @@ 'using an OFFSET or a non-simple ' 'LIMIT clause') - _order_by_clauses = select._order_by_clause.clauses + _order_by_clauses = [ + sql_util.unwrap_label_reference(elem) + for elem in select._order_by_clause.clauses + ] + limit_clause = select._limit_clause offset_clause = select._offset_clause kwargs['select_wraps_for'] = select diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/dialects/mssql/pymssql.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/dialects/mssql/pymssql.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/dialects/mssql/pymssql.py 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/dialects/mssql/pymssql.py 2016-07-06 21:09:34.000000000 +0000 @@ -9,7 +9,7 @@ .. dialect:: mssql+pymssql :name: pymssql :dbapi: pymssql - :connectstring: mssql+pymssql://:@?\ + :connectstring: mssql+pymssql://:@/?\ charset=utf8 :url: http://pymssql.org/ diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/dialects/mssql/pyodbc.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/dialects/mssql/pyodbc.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/dialects/mssql/pyodbc.py 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/dialects/mssql/pyodbc.py 2016-07-06 21:09:34.000000000 +0000 @@ -93,6 +93,13 @@ .. versionadded:: 0.7.7 ``supports_unicode_binds`` parameter to ``create_engine()``\ . +Rowcount Support +---------------- + +Pyodbc only has partial support for rowcount. See the notes at +:ref:`mssql_rowcount_versioning` for important notes when using ORM +versioning. + """ from .base import MSExecutionContext, MSDialect, VARBINARY diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/dialects/mysql/base.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/dialects/mysql/base.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/dialects/mysql/base.py 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/dialects/mysql/base.py 2016-07-06 21:09:34.000000000 +0000 @@ -111,19 +111,35 @@ Transaction Isolation Level --------------------------- -:func:`.create_engine` accepts an :paramref:`.create_engine.isolation_level` -parameter which results in the command ``SET SESSION -TRANSACTION ISOLATION LEVEL `` being invoked for -every new connection. Valid values for this parameter are -``READ COMMITTED``, ``READ UNCOMMITTED``, -``REPEATABLE READ``, and ``SERIALIZABLE``:: +All MySQL dialects support setting of transaction isolation level +both via a dialect-specific parameter :paramref:`.create_engine.isolation_level` +accepted by :func:`.create_engine`, +as well as the :paramref:`.Connection.execution_options.isolation_level` +argument as passed to :meth:`.Connection.execution_options`. +This feature works by issuing the command +``SET SESSION TRANSACTION ISOLATION LEVEL `` for +each new connection. + +To set isolation level using :func:`.create_engine`:: engine = create_engine( "mysql://scott:tiger@localhost/test", isolation_level="READ UNCOMMITTED" ) -.. versionadded:: 0.7.6 +To set using per-connection execution options:: + + connection = engine.connect() + connection = connection.execution_options( + isolation_level="READ COMMITTED" + ) + +Valid values for ``isolation_level`` include: + +* ``READ COMMITTED`` +* ``READ UNCOMMITTED`` +* ``REPEATABLE READ`` +* ``SERIALIZABLE`` AUTO_INCREMENT Behavior ----------------------- diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/dialects/oracle/base.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/dialects/oracle/base.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/dialects/oracle/base.py 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/dialects/oracle/base.py 2016-07-06 21:09:34.000000000 +0000 @@ -285,7 +285,7 @@ from sqlalchemy import util, sql from sqlalchemy.engine import default, reflection -from sqlalchemy.sql import compiler, visitors, expression +from sqlalchemy.sql import compiler, visitors, expression, util as sql_util from sqlalchemy.sql import operators as sql_operators from sqlalchemy.sql.elements import quoted_name from sqlalchemy import types as sqltypes, schema as sa_schema @@ -754,6 +754,20 @@ limitselect._oracle_visit = True limitselect._is_wrapper = True + # add expressions to accomodate FOR UPDATE OF + for_update = select._for_update_arg + if for_update is not None and for_update.of: + for_update = for_update._clone() + for_update._copy_internals() + + for elem in for_update.of: + select.append_column(elem) + + adapter = sql_util.ClauseAdapter(select) + for_update.of = [ + adapter.traverse(elem) + for elem in for_update.of] + # If needed, add the limiting clause if limit_clause is not None: if not self.dialect.use_binds_for_limits: @@ -773,7 +787,7 @@ # If needed, add the ora_rn, and wrap again with offset. if offset_clause is None: - limitselect._for_update_arg = select._for_update_arg + limitselect._for_update_arg = for_update select = limitselect else: limitselect = limitselect.column( @@ -786,13 +800,18 @@ offsetselect._oracle_visit = True offsetselect._is_wrapper = True + if for_update is not None and for_update.of: + for elem in for_update.of: + if limitselect.corresponding_column(elem) is None: + limitselect.append_column(elem) + if not self.dialect.use_binds_for_limits: offset_clause = sql.literal_column( "%d" % select._offset) offsetselect.append_whereclause( sql.literal_column("ora_rn") > offset_clause) - offsetselect._for_update_arg = select._for_update_arg + offsetselect._for_update_arg = for_update select = offsetselect return compiler.SQLCompiler.visit_select(self, select, **kwargs) diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/dialects/oracle/cx_oracle.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/dialects/oracle/cx_oracle.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/dialects/oracle/cx_oracle.py 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/dialects/oracle/cx_oracle.py 2016-07-06 21:09:34.000000000 +0000 @@ -898,13 +898,17 @@ dsn = url.host opts = dict( - user=url.username, - password=url.password, - dsn=dsn, threaded=self.threaded, twophase=self.allow_twophase, ) + if dsn is not None: + opts['dsn'] = dsn + if url.password is not None: + opts['password'] = url.password + if url.username is not None: + opts['user'] = url.username + if util.py2k: if self._cx_oracle_with_unicode: for k, v in opts.items(): diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/dialects/postgresql/base.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/dialects/postgresql/base.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/dialects/postgresql/base.py 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/dialects/postgresql/base.py 2016-07-06 21:09:34.000000000 +0000 @@ -50,11 +50,12 @@ All Postgresql dialects support setting of transaction isolation level both via a dialect-specific parameter :paramref:`.create_engine.isolation_level` accepted by :func:`.create_engine`, -as well as the ``isolation_level`` argument as passed to +as well as the :paramref:`.Connection.execution_options.isolation_level` argument as passed to :meth:`.Connection.execution_options`. When using a non-psycopg2 dialect, this feature works by issuing the command ``SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL `` for -each new connection. +each new connection. For the special AUTOCOMMIT isolation level, DBAPI-specific +techniques are used. To set isolation level using :func:`.create_engine`:: @@ -76,10 +77,7 @@ * ``READ UNCOMMITTED`` * ``REPEATABLE READ`` * ``SERIALIZABLE`` - -The :mod:`~sqlalchemy.dialects.postgresql.psycopg2` and -:mod:`~sqlalchemy.dialects.postgresql.pg8000` dialects also offer the -special level ``AUTOCOMMIT``. +* ``AUTOCOMMIT`` - on psycopg2 / pg8000 only .. seealso:: @@ -1580,6 +1578,9 @@ colspec = self.preparer.format_column(column) impl_type = column.type.dialect_impl(self.dialect) + if isinstance(impl_type, sqltypes.TypeDecorator): + impl_type = impl_type.impl + if column.primary_key and \ column is column.table._autoincrement_column and \ ( diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/dialects/postgresql/psycopg2.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/dialects/postgresql/psycopg2.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/dialects/postgresql/psycopg2.py 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/dialects/postgresql/psycopg2.py 2016-07-06 21:09:34.000000000 +0000 @@ -717,6 +717,7 @@ 'connection has been closed unexpectedly', 'SSL SYSCALL error: Bad file descriptor', 'SSL SYSCALL error: EOF detected', + 'SSL error: decryption failed or bad record mac', ]: idx = str_e.find(msg) if idx >= 0 and '"' not in str_e[:idx]: diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/engine/base.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/engine/base.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/engine/base.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/engine/base.py 2016-07-06 21:09:34.000000000 +0000 @@ -844,7 +844,7 @@ return self.execute(object, *multiparams, **params).scalar() def execute(self, object, *multiparams, **params): - """Executes the a SQL statement construct and returns a + """Executes a SQL statement construct and returns a :class:`.ResultProxy`. :param object: The statement to be executed. May be diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/engine/__init__.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/engine/__init__.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/engine/__init__.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/engine/__init__.py 2016-07-06 21:09:34.000000000 +0000 @@ -373,7 +373,7 @@ * the ``mock`` strategy, which dispatches all statement execution to a function passed as the argument ``executor``. See `example in the FAQ - `_. + `_. :param executor=None: a function taking arguments ``(sql, *multiparams, **params)``, to which the ``mock`` strategy will diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/engine/reflection.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/engine/reflection.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/engine/reflection.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/engine/reflection.py 2016-07-06 21:09:34.000000000 +0000 @@ -692,6 +692,7 @@ else: sa_schema.Table(referred_table, table.metadata, autoload=True, autoload_with=self.bind, + schema=sa_schema.BLANK_SCHEMA, **reflection_options ) for column in referred_columns: diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/engine/result.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/engine/result.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/engine/result.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/engine/result.py 2016-07-06 21:09:34.000000000 +0000 @@ -192,6 +192,7 @@ typemap = dialect.dbapi_type_map translate_colname = context._translate_colname self.case_sensitive = case_sensitive = dialect.case_sensitive + self._orig_processors = None if context.result_column_struct: result_columns, cols_are_ordered = context.result_column_struct @@ -304,6 +305,7 @@ for rec in raw: key = rec[1] if key in seen: + key = key.lower() if not self.case_sensitive else key by_key[key] = (None, by_key[key][1], None) seen.add(key) @@ -416,11 +418,11 @@ else: return self._key_fallback(key, False) is not None - def _getter(self, key): + def _getter(self, key, raiseerr=True): if key in self._keymap: processor, obj, index = self._keymap[key] else: - ret = self._key_fallback(key, False) + ret = self._key_fallback(key, raiseerr) if ret is None: return None processor, obj, index = ret @@ -494,13 +496,13 @@ context.engine._should_log_debug() self._init_metadata() - def _getter(self, key): + def _getter(self, key, raiseerr=True): try: getter = self._metadata._getter except AttributeError: return self._non_result(None) else: - return getter(key) + return getter(key, raiseerr) def _has_key(self, key): try: @@ -671,7 +673,7 @@ """Close this ResultProxy. This closes out the underlying DBAPI cursor corresonding - to the statement execution, if one is stil present. Note that the + to the statement execution, if one is still present. Note that the DBAPI cursor is automatically released when the :class:`.ResultProxy` exhausts all available rows. :meth:`.ResultProxy.close` is generally an optional method except in the case when discarding a @@ -1237,16 +1239,21 @@ def _init_metadata(self): super(BufferedColumnResultProxy, self)._init_metadata() + metadata = self._metadata - # orig_processors will be used to preprocess each row when they are - # constructed. - metadata._orig_processors = metadata._processors - # replace the all type processors by None processors. - metadata._processors = [None for _ in range(len(metadata.keys))] - keymap = {} - for k, (func, obj, index) in metadata._keymap.items(): - keymap[k] = (None, obj, index) - self._metadata._keymap = keymap + + # don't double-replace the processors, in the case + # of a cached ResultMetaData + if metadata._orig_processors is None: + # orig_processors will be used to preprocess each row when + # they are constructed. + metadata._orig_processors = metadata._processors + # replace the all type processors by None processors. + metadata._processors = [None for _ in range(len(metadata.keys))] + keymap = {} + for k, (func, obj, index) in metadata._keymap.items(): + keymap[k] = (None, obj, index) + metadata._keymap = keymap def fetchall(self): # can't call cursor.fetchall(), since rows must be diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/events.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/events.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/events.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/events.py 2016-07-06 21:09:34.000000000 +0000 @@ -424,7 +424,7 @@ def before_cursor_execute(conn, cursor, statement, parameters, context, executemany): - log.info("Received statement: %s" % statement) + log.info("Received statement: %s", statement) engine = create_engine('postgresql://scott:tiger@localhost/test') event.listen(engine, "before_cursor_execute", before_cursor_execute) @@ -435,7 +435,7 @@ @event.listens_for(conn, 'before_cursor_execute') def before_cursor_execute(conn, cursor, statement, parameters, context, executemany): - log.info("Received statement: %s" % statement) + log.info("Received statement: %s", statement) When the methods are called with a `statement` parameter, such as in :meth:`.after_cursor_execute`, :meth:`.before_cursor_execute` and diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/ext/automap.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/ext/automap.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/ext/automap.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/ext/automap.py 2016-07-06 21:09:34.000000000 +0000 @@ -13,7 +13,7 @@ It is hoped that the :class:`.AutomapBase` system provides a quick and modernized solution to the problem that the very famous -`SQLSoup `_ +`SQLSoup `_ also tries to solve, that of generating a quick and rudimentary object model from an existing database on the fly. By addressing the issue strictly at the mapper configuration level, and integrating fully with existing diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/ext/declarative/base.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/ext/declarative/base.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/ext/declarative/base.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/ext/declarative/base.py 2016-07-06 21:09:34.000000000 +0000 @@ -351,7 +351,8 @@ util.warn( "On class %r, Column object %r named " "directly multiple times, " - "only one will be used: %s" % + "only one will be used: %s. " + "Consider using orm.synonym instead" % (self.classname, name, (", ".join(sorted(keys)))) ) diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/ext/mutable.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/ext/mutable.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/ext/mutable.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/ext/mutable.py 2016-07-06 21:09:34.000000000 +0000 @@ -46,7 +46,7 @@ :class:`.PickleType`, :class:`.postgresql.ARRAY`, etc. When using the :mod:`sqlalchemy.ext.mutable` extension, the value itself -tracks all parents which reference it. Below, we illustrate the a simple +tracks all parents which reference it. Below, we illustrate a simple version of the :class:`.MutableDict` dictionary object, which applies the :class:`.Mutable` mixin to a plain Python dictionary:: diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/__init__.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/__init__.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/__init__.py 2016-02-15 17:27:53.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/__init__.py 2016-07-06 21:09:34.000000000 +0000 @@ -114,13 +114,14 @@ ThreadLocalMetaData, UniqueConstraint, DDL, + BLANK_SCHEMA ) from .inspection import inspect from .engine import create_engine, engine_from_config -__version__ = '1.0.12' +__version__ = '1.0.14' def __go(lcls): diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/orm/base.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/orm/base.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/orm/base.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/orm/base.py 2016-07-06 21:09:34.000000000 +0000 @@ -344,7 +344,7 @@ def _orm_columns(entity): insp = inspection.inspect(entity, False) - if hasattr(insp, 'selectable'): + if hasattr(insp, 'selectable') and hasattr(insp.selectable, 'c'): return [c for c in insp.selectable.c] else: return [entity] diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/orm/evaluator.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/orm/evaluator.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/orm/evaluator.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/orm/evaluator.py 2016-07-06 21:09:34.000000000 +0000 @@ -130,5 +130,8 @@ (type(clause).__name__, clause.operator)) def visit_bindparam(self, clause): - val = clause.value + if clause.callable: + val = clause.callable() + else: + val = clause.value return lambda obj: val diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/orm/loading.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/orm/loading.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/orm/loading.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/orm/loading.py 2016-07-06 21:09:34.000000000 +0000 @@ -312,7 +312,7 @@ else: if adapter: col = adapter.columns[col] - getter = result._getter(col) + getter = result._getter(col, False) if getter: populators["quick"].append((prop.key, getter)) else: diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/orm/mapper.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/orm/mapper.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/orm/mapper.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/orm/mapper.py 2016-07-06 21:09:34.000000000 +0000 @@ -2052,7 +2052,7 @@ .. warning:: - the :attr:`.Mapper.relationships` accessor namespace is an + The :attr:`.Mapper.attrs` accessor namespace is an instance of :class:`.OrderedProperties`. This is a dictionary-like object which includes a small number of named methods such as :meth:`.OrderedProperties.items` @@ -2098,14 +2098,15 @@ .. warning:: - the :attr:`.Mapper.relationships` accessor namespace is an + The :attr:`.Mapper.all_orm_descriptors` accessor namespace is an instance of :class:`.OrderedProperties`. This is a dictionary-like object which includes a small number of named methods such as :meth:`.OrderedProperties.items` and :meth:`.OrderedProperties.values`. When accessing attributes dynamically, favor using the dict-access - scheme, e.g. ``mapper.attrs[somename]`` over - ``getattr(mapper.attrs, somename)`` to avoid name collisions. + scheme, e.g. ``mapper.all_orm_descriptors[somename]`` over + ``getattr(mapper.all_orm_descriptors, somename)`` to avoid name + collisions. .. versionadded:: 0.8.0 @@ -2145,8 +2146,8 @@ @_memoized_configured_property def relationships(self): - """Return a namespace of all :class:`.RelationshipProperty` - properties maintained by this :class:`.Mapper`. + """A namespace of all :class:`.RelationshipProperty` properties + maintained by this :class:`.Mapper`. .. warning:: @@ -2156,8 +2157,9 @@ named methods such as :meth:`.OrderedProperties.items` and :meth:`.OrderedProperties.values`. When accessing attributes dynamically, favor using the dict-access - scheme, e.g. ``mapper.attrs[somename]`` over - ``getattr(mapper.attrs, somename)`` to avoid name collisions. + scheme, e.g. ``mapper.relationships[somename]`` over + ``getattr(mapper.relationships, somename)`` to avoid name + collisions. .. seealso:: diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/orm/query.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/orm/query.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/orm/query.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/orm/query.py 2016-07-06 21:09:34.000000000 +0000 @@ -2463,9 +2463,35 @@ @_generative(_no_statement_condition) def slice(self, start, stop): - """apply LIMIT/OFFSET to the ``Query`` based on a " - "range and return the newly resulting ``Query``.""" + """Computes the "slice" of the :class:`.Query` represented by + the given indices and returns the resulting :class:`.Query`. + The start and stop indices behave like the argument to Python's + built-in :func:`range` function. This method provides an + alternative to using ``LIMIT``/``OFFSET`` to get a slice of the + query. + + For example, :: + + session.query(User).order_by(User.id).slice(1, 3) + + renders as + + .. sourcecode:: sql + + SELECT users.id AS users_id, + users.name AS users_name + FROM users ORDER BY users.id + LIMIT ? OFFSET ? + (2, 1) + + .. seealso:: + + :meth:`.Query.limit` + + :meth:`.Query.offset` + + """ if start is not None and stop is not None: self._offset = (self._offset or 0) + start self._limit = stop - start @@ -2480,7 +2506,6 @@ @_generative(_no_statement_condition) def limit(self, limit): """Apply a ``LIMIT`` to the query and return the newly resulting - ``Query``. """ diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/orm/strategies.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/orm/strategies.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/orm/strategies.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/orm/strategies.py 2016-07-06 21:09:34.000000000 +0000 @@ -71,8 +71,20 @@ ) ) + # a single MapperProperty is shared down a class inheritance + # hierarchy, so we set up attribute instrumentation and backref event + # for each mapper down the hierarchy. + + # typically, "mapper" is the same as prop.parent, due to the way + # the configure_mappers() process runs, however this is not strongly + # enforced, and in the case of a second configure_mappers() run the + # mapper here might not be prop.parent; also, a subclass mapper may + # be called here before a superclass mapper. That is, can't depend + # on mappers not already being set up so we have to check each one. + for m in mapper.self_and_descendants: - if prop is m._props.get(prop.key): + if prop is m._props.get(prop.key) and \ + not m.class_manager._attr_has_impl(prop.key): desc = attributes.register_attribute_impl( m.class_, @@ -83,8 +95,8 @@ useobject=useobject, extension=attribute_ext, trackparent=useobject and ( - prop.single_parent - or prop.direction is interfaces.ONETOMANY), + prop.single_parent or + prop.direction is interfaces.ONETOMANY), typecallable=typecallable, callable_=callable_, active_history=active_history, @@ -100,7 +112,7 @@ @properties.ColumnProperty.strategy_for(instrument=False, deferred=False) class UninstrumentedColumnLoader(LoaderStrategy): - """Represent the a non-instrumented MapperProperty. + """Represent a non-instrumented MapperProperty. The polymorphic_on argument of mapper() often results in this, if the argument is against the with_polymorphic selectable. @@ -174,7 +186,7 @@ for col in self.columns: if adapter: col = adapter.columns[col] - getter = result._getter(col) + getter = result._getter(col, False) if getter: populators["quick"].append((self.key, getter)) break diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/orm/strategy_options.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/orm/strategy_options.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/orm/strategy_options.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/orm/strategy_options.py 2016-07-06 21:09:34.000000000 +0000 @@ -608,7 +608,7 @@ ``Address`` object load only the ``email_address`` attribute:: session.query(User).options( - subqueryload("addreses").load_only("email_address") + subqueryload("addresses").load_only("email_address") ) For a :class:`.Query` that has multiple entities, the lead entity can be diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/schema.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/schema.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/schema.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/schema.py 2016-07-06 21:09:34.000000000 +0000 @@ -15,6 +15,7 @@ from .sql.schema import ( + BLANK_SCHEMA, CheckConstraint, Column, ColumnDefault, diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/sql/compiler.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/sql/compiler.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/sql/compiler.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/sql/compiler.py 2016-07-06 21:09:34.000000000 +0000 @@ -1572,7 +1572,6 @@ select, select._prefixes, **kwargs) text += self.get_select_precolumns(select, **kwargs) - # the actual list of columns to print in the SELECT column list. inner_columns = [ c for c in [ @@ -1590,15 +1589,14 @@ if populate_result_map and select_wraps_for is not None: # if this select is a compiler-generated wrapper, # rewrite the targeted columns in the result map - wrapped_inner_columns = set(select_wraps_for.inner_columns) + translate = dict( - (outer, inner.pop()) for outer, inner in [ - ( - outer, - outer.proxy_set.intersection(wrapped_inner_columns)) - for outer in select.inner_columns - ] if inner + zip( + [name for (key, name) in select._columns_plus_names], + [name for (key, name) in + select_wraps_for._columns_plus_names]) ) + self._result_columns = [ (key, name, tuple(translate.get(o, o) for o in obj), type_) for key, name, obj, type_ in self._result_columns diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/sql/crud.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/sql/crud.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/sql/crud.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/sql/crud.py 2016-07-06 21:09:34.000000000 +0000 @@ -101,7 +101,7 @@ if parameters and stmt_parameters: check = set(parameters).intersection( - _column_as_key(k) for k in stmt.parameters + _column_as_key(k) for k in stmt_parameters ).difference(check_columns) if check: raise exc.CompileError( diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/sql/default_comparator.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/sql/default_comparator.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/sql/default_comparator.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/sql/default_comparator.py 2016-07-06 21:09:34.000000000 +0000 @@ -176,7 +176,7 @@ def _neg_impl(expr, op, **kw): """See :meth:`.ColumnOperators.__neg__`.""" - return UnaryExpression(expr, operator=operators.neg) + return UnaryExpression(expr, operator=operators.neg, type_=expr.type) def _match_impl(expr, op, other, **kw): diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/sql/elements.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/sql/elements.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/sql/elements.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/sql/elements.py 2016-07-06 21:09:34.000000000 +0000 @@ -705,6 +705,9 @@ def _negate(self): if self.type._type_affinity is type_api.BOOLEANTYPE._type_affinity: + # TODO: see the note in AsBoolean that it seems to assume + # the element is the True_() / False_() constant, so this + # is too broad return AsBoolean(self, operators.isfalse, operators.istrue) else: return super(ColumnElement, self)._negate() @@ -1263,6 +1266,8 @@ @property def selectable(self): + # allows text() to be considered by + # _interpret_as_from return self _hide_froms = [] @@ -2677,6 +2682,13 @@ modifier=self.modifier, type_=self.type, wraps_column_expression=self.wraps_column_expression) + elif self.type._type_affinity is type_api.BOOLEANTYPE._type_affinity: + return UnaryExpression( + self.self_group(against=operators.inv), + operator=operators.inv, + type_=type_api.BOOLEANTYPE, + wraps_column_expression=self.wraps_column_expression, + negate=None) else: return ClauseElement._negate(self) @@ -2701,6 +2713,9 @@ return self def _negate(self): + # TODO: this assumes the element is the True_() or False_() + # object, but this assumption isn't enforced and + # ColumnElement._negate() can send any number of expressions here return self.element._negate() @@ -3687,8 +3702,10 @@ if not all_overlap.intersection(elem._cloned_set)) -def _labeled(element): - if not hasattr(element, 'name'): +@util.dependencies("sqlalchemy.sql.functions") +def _labeled(functions, element): + if not hasattr(element, 'name') or \ + isinstance(element, functions.FunctionElement): return element.label(None) else: return element diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/sql/functions.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/sql/functions.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/sql/functions.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/sql/functions.py 2016-07-06 21:09:34.000000000 +0000 @@ -162,22 +162,24 @@ :class:`.FunctionElement`. This construct wraps the function in a named alias which - is suitable for the FROM clause. + is suitable for the FROM clause, in the style accepted for example + by Postgresql. e.g.:: from sqlalchemy.sql import column - stmt = select([column('data')]).select_from( - func.unnest(Table.data).alias('data_view') + stmt = select([column('data_view')]).\\ + select_from(SomeTable).\\ + select_from(func.unnest(SomeTable.data).alias('data_view') ) Would produce: .. sourcecode:: sql - SELECT data - FROM unnest(sometable.data) AS data_view + SELECT data_view + FROM sometable, unnest(sometable.data) AS data_view .. versionadded:: 0.9.8 The :meth:`.FunctionElement.alias` method is now supported. Previously, this method's behavior was @@ -283,13 +285,13 @@ :data:`.func` is a special object instance which generates SQL functions based on name-based attributes, e.g.:: - >>> print func.count(1) + >>> print(func.count(1)) count(:param_1) The element is a column-oriented SQL element like any other, and is used in that way:: - >>> print select([func.count(table.c.id)]) + >>> print(select([func.count(table.c.id)])) SELECT count(sometable.id) FROM sometable Any name can be given to :data:`.func`. If the function name is unknown to @@ -297,13 +299,13 @@ which SQLAlchemy is aware of, the name may be interpreted as a *generic function* which will be compiled appropriately to the target database:: - >>> print func.current_timestamp() + >>> print(func.current_timestamp()) CURRENT_TIMESTAMP To call functions which are present in dot-separated packages, specify them in the same manner:: - >>> print func.stats.yield_curve(5, 10) + >>> print(func.stats.yield_curve(5, 10)) stats.yield_curve(:yield_curve_1, :yield_curve_2) SQLAlchemy can be made aware of the return type of functions to enable @@ -312,8 +314,8 @@ treated as a string in expressions, specify :class:`~sqlalchemy.types.Unicode` as the type: - >>> print func.my_string(u'hi', type_=Unicode) + ' ' + \ - ... func.my_string(u'there', type_=Unicode) + >>> print(func.my_string(u'hi', type_=Unicode) + ' ' + + ... func.my_string(u'there', type_=Unicode)) my_string(:my_string_1) || :my_string_2 || my_string(:my_string_3) The object returned by a :data:`.func` call is usually an instance of @@ -323,7 +325,7 @@ method of a :class:`.Connection` or :class:`.Engine`, where it will be wrapped inside of a SELECT statement first:: - print connection.execute(func.current_timestamp()).scalar() + print(connection.execute(func.current_timestamp()).scalar()) In a few exception cases, the :data:`.func` accessor will redirect a name to a built-in expression such as :func:`.cast` diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/sql/schema.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/sql/schema.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/sql/schema.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/sql/schema.py 2016-07-06 21:09:34.000000000 +0000 @@ -47,6 +47,17 @@ RETAIN_SCHEMA = util.symbol('retain_schema') +BLANK_SCHEMA = util.symbol( + 'blank_schema', + """Symbol indicating that a :class:`.Table` or :class:`.Sequence` + should have 'None' for its schema, even if the parent + :class:`.MetaData` has specified a schema. + + .. versionadded:: 1.0.14 + + """ +) + def _get_table_key(name, schema): if schema is None: @@ -341,6 +352,17 @@ the table resides in a schema other than the default selected schema for the engine's database connection. Defaults to ``None``. + If the owning :class:`.MetaData` of this :class:`.Table` specifies + its own :paramref:`.MetaData.schema` parameter, then that schema + name will be applied to this :class:`.Table` if the schema parameter + here is set to ``None``. To set a blank schema name on a :class:`.Table` + that would otherwise use the schema set on the owning :class:`.MetaData`, + specify the special symbol :attr:`.BLANK_SCHEMA`. + + .. versionadded:: 1.0.14 Added the :attr:`.BLANK_SCHEMA` symbol to + allow a :class:`.Table` to have a blank schema name even when the + parent :class:`.MetaData` specifies :paramref:`.MetaData.schema`. + The quoting rules for the schema name are the same as those for the ``name`` parameter, in that quoting is applied for reserved words or case-sensitive names; to enable unconditional quoting for the @@ -372,6 +394,8 @@ schema = kw.get('schema', None) if schema is None: schema = metadata.schema + elif schema is BLANK_SCHEMA: + schema = None keep_existing = kw.pop('keep_existing', False) extend_existing = kw.pop('extend_existing', False) if 'useexisting' in kw: @@ -443,6 +467,8 @@ self.schema = kwargs.pop('schema', None) if self.schema is None: self.schema = metadata.schema + elif self.schema is BLANK_SCHEMA: + self.schema = None else: quote_schema = kwargs.pop('quote_schema', None) self.schema = quoted_name(self.schema, quote_schema) @@ -836,8 +862,14 @@ schema if referred_schema == self.schema else None) table.append_constraint( c.copy(schema=fk_constraint_schema, target_table=table)) - elif not c._type_bound: + # skip unique constraints that would be generated + # by the 'unique' flag on Column + if isinstance(c, UniqueConstraint) and \ + len(c.columns) == 1 and \ + list(c.columns)[0].unique: + continue + table.append_constraint( c.copy(schema=schema, target_table=table)) for index in self.indexes: @@ -2114,7 +2146,10 @@ .. versionadded:: 1.0.7 :param schema: Optional schema name for the sequence, if located - in a schema other than the default. + in a schema other than the default. The rules for selecting the + schema name when a :class:`.MetaData` is also present are the same + as that of :paramref:`.Table.schema`. + :param optional: boolean value, when ``True``, indicates that this :class:`.Sequence` object only needs to be explicitly generated on backends that don't provide another way to generate primary @@ -2163,7 +2198,9 @@ self.nomaxvalue = nomaxvalue self.cycle = cycle self.optional = optional - if metadata is not None and schema is None and metadata.schema: + if schema is BLANK_SCHEMA: + self.schema = schema = None + elif metadata is not None and schema is None and metadata.schema: self.schema = schema = metadata.schema else: self.schema = quoted_name(schema, quote_schema) @@ -3296,8 +3333,21 @@ :param schema: The default schema to use for the :class:`.Table`, - :class:`.Sequence`, and other objects associated with this - :class:`.MetaData`. Defaults to ``None``. + :class:`.Sequence`, and potentially other objects associated with + this :class:`.MetaData`. Defaults to ``None``. + + When this value is set, any :class:`.Table` or :class:`.Sequence` + which specifies ``None`` for the schema parameter will instead + have this schema name defined. To build a :class:`.Table` + or :class:`.Sequence` that still has ``None`` for the schema + even when this parameter is present, use the :attr:`.BLANK_SCHEMA` + symbol. + + .. seealso:: + + :paramref:`.Table.schema` + + :paramref:`.Sequence.schema` :param quote_schema: Sets the ``quote_schema`` flag for those :class:`.Table`, diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/sql/selectable.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/sql/selectable.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/sql/selectable.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/sql/selectable.py 2016-07-06 21:09:34.000000000 +0000 @@ -286,10 +286,35 @@ _memoized_property = util.group_expirable_memoized_property(["_columns"]) + @util.pending_deprecation( + '1.1', + message="``Table.count()`` is deprecated. Counting " + "rows requires that the correct column expression and " + "accommodations for joins, DISTINCT, etc. must be made, " + "otherwise results may not be what's expected. " + "Please use an appropriate ``func.count()`` expression " + "directly." + ) @util.dependencies("sqlalchemy.sql.functions") def count(self, functions, whereclause=None, **params): """return a SELECT COUNT generated against this - :class:`.FromClause`.""" + :class:`.FromClause`. + + The function generates COUNT against the + first column in the primary key of the table, or against + the first column in the table overall. Explicit use of + ``func.count()`` should be preferred:: + + row_count = conn.scalar( + select([func.count('*')]).select_from(table) + ) + + + .. seealso:: + + :data:`.func` + + """ if self.primary_key: col = list(self.primary_key)[0] @@ -1187,6 +1212,14 @@ self._suffixes = _suffixes super(CTE, self).__init__(selectable, name=name) + def _copy_internals(self, clone=_clone, **kw): + super(CTE, self)._copy_internals(clone, **kw) + if self._cte_alias is not None: + self._cte_alias = self + self._restates = frozenset([ + clone(elem, **kw) for elem in self._restates + ]) + def alias(self, name=None, flat=False): return CTE( self.original, @@ -1353,21 +1386,6 @@ else: return [] - @util.dependencies("sqlalchemy.sql.functions") - def count(self, functions, whereclause=None, **params): - """return a SELECT COUNT generated against this - :class:`.TableClause`.""" - - if self.primary_key: - col = list(self.primary_key)[0] - else: - col = list(self.columns)[0] - return Select( - [functions.func.count(col).label('tbl_row_count')], - whereclause, - from_obj=[self], - **params) - @util.dependencies("sqlalchemy.sql.dml") def insert(self, dml, values=None, inline=False, **kwargs): """Generate an :func:`.insert` construct against this diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/sql/type_api.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/sql/type_api.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/sql/type_api.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/sql/type_api.py 2016-07-06 21:09:34.000000000 +0000 @@ -1011,7 +1011,7 @@ the processing provided by ``self.impl`` is maintained. :param dialect: Dialect instance in use. - :param coltype: An SQLAlchemy data type + :param coltype: A SQLAlchemy data type This method is the reverse counterpart to the :meth:`bind_processor` method of this class. diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/sql/util.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/sql/util.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/sql/util.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/sql/util.py 2016-07-06 21:09:34.000000000 +0000 @@ -173,6 +173,16 @@ return cols +def unwrap_label_reference(element): + def replace(elem): + if isinstance(elem, (_label_reference, _textual_label_reference)): + return elem.element + + return visitors.replacement_traverse( + element, {}, replace + ) + + def clause_is_present(clause, search): """Given a target clause and a second to search within, return True if the target is plainly present in the search without any diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/testing/plugin/plugin_base.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/testing/plugin/plugin_base.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/testing/plugin/plugin_base.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/testing/plugin/plugin_base.py 2016-07-06 21:09:34.000000000 +0000 @@ -67,6 +67,9 @@ dest="low_connections", help="Use a low number of distinct connections - " "i.e. for Oracle TNS") + make_option("--write-idents", type="string", dest="write_idents", + help="write out generated follower idents to , " + "when -n is used") make_option("--reversetop", action="store_true", dest="reversetop", default=False, help="Use a random-ordering set implementation in the ORM " @@ -427,9 +430,11 @@ #from sqlalchemy import inspect #assert not inspect(testing.db).get_table_names() engines.testing_reaper._stop_test_ctx() - if not options.low_connections: - assertions.global_cleanup_assertions() - _restore_engine() + try: + if not options.low_connections: + assertions.global_cleanup_assertions() + finally: + _restore_engine() def _restore_engine(): diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/testing/plugin/pytestplugin.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/testing/plugin/pytestplugin.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/testing/plugin/pytestplugin.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/testing/plugin/pytestplugin.py 2016-07-06 21:09:34.000000000 +0000 @@ -9,7 +9,7 @@ import argparse import inspect import collections -import itertools +import os try: import xdist # noqa @@ -43,6 +43,14 @@ config.slaveinput["follower_ident"] ) + if config.option.write_idents: + with open(config.option.write_idents, "a") as file_: + file_.write(config.slaveinput["follower_ident"] + "\n") + else: + if config.option.write_idents and \ + os.path.exists(config.option.write_idents): + os.remove(config.option.write_idents) + plugin_base.pre_begin(config.option) plugin_base.set_coverage_flag(bool(getattr(config.option, diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/testing/provision.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/testing/provision.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/testing/provision.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/testing/provision.py 2016-07-06 21:09:34.000000000 +0000 @@ -40,7 +40,6 @@ def create_follower_db(follower_ident): - for cfg in _configs_for_db_operation(): _create_db(cfg, cfg.db, follower_ident) @@ -223,18 +222,9 @@ @_drop_db.for_db("mysql") def _mysql_drop_db(cfg, eng, ident): with eng.connect() as conn: - try: - conn.execute("DROP DATABASE %s_test_schema" % ident) - except Exception: - pass - try: - conn.execute("DROP DATABASE %s_test_schema_2" % ident) - except Exception: - pass - try: - conn.execute("DROP DATABASE %s" % ident) - except Exception: - pass + conn.execute("DROP DATABASE %s_test_schema" % ident) + conn.execute("DROP DATABASE %s_test_schema_2" % ident) + conn.execute("DROP DATABASE %s" % ident) @_create_db.for_db("oracle") @@ -260,10 +250,10 @@ def _ora_drop_ignore(conn, dbname): try: conn.execute("drop user %s cascade" % dbname) - log.info("Reaped db: %s" % dbname) + log.info("Reaped db: %s", dbname) return True except exc.DatabaseError as err: - log.warn("couldn't drop db: %s" % err) + log.warning("couldn't drop db: %s", err) return False @@ -280,9 +270,14 @@ _ora_drop_ignore(conn, "%s_ts2" % ident) -def reap_oracle_dbs(eng): +def reap_oracle_dbs(eng, idents_file): log.info("Reaping Oracle dbs...") with eng.connect() as conn: + with open(idents_file) as file_: + idents = set(line.strip() for line in file_) + + log.info("identifiers in file: %s", ", ".join(idents)) + to_reap = conn.execute( "select u.username from all_users u where username " "like 'TEST_%' and not exists (select username " @@ -292,7 +287,7 @@ for name in all_names: if name.endswith("_ts1") or name.endswith("_ts2"): continue - else: + elif name in idents: to_drop.add(name) if "%s_ts1" % name in all_names: to_drop.add("%s_ts1" % name) diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/util/_collections.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/util/_collections.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/util/_collections.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/util/_collections.py 2016-07-06 21:09:34.000000000 +0000 @@ -10,7 +10,8 @@ from __future__ import absolute_import import weakref import operator -from .compat import threading, itertools_filterfalse, string_types +from .compat import threading, itertools_filterfalse, string_types, \ + binary_types from . import py2k import types import collections @@ -199,10 +200,10 @@ self._data[key] = obj def __getstate__(self): - return {'_data': self.__dict__['_data']} + return {'_data': self._data} def __setstate__(self, state): - self.__dict__['_data'] = state['_data'] + object.__setattr__(self, '_data', state['_data']) def __getattr__(self, key): try: @@ -794,7 +795,8 @@ def to_list(x, default=None): if x is None: return default - if not isinstance(x, collections.Iterable) or isinstance(x, string_types): + if not isinstance(x, collections.Iterable) or \ + isinstance(x, string_types + binary_types): return [x] elif isinstance(x, list): return x diff -Nru sqlalchemy-1.0.12+ds1/lib/sqlalchemy/util/compat.py sqlalchemy-1.0.14+ds1/lib/sqlalchemy/util/compat.py --- sqlalchemy-1.0.12+ds1/lib/sqlalchemy/util/compat.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/lib/sqlalchemy/util/compat.py 2016-07-06 21:09:34.000000000 +0000 @@ -62,6 +62,7 @@ ) string_types = str, + binary_types = bytes, binary_type = bytes text_type = str int_types = int, @@ -115,6 +116,7 @@ from cStringIO import StringIO as byte_buffer string_types = basestring, + binary_types = bytes, binary_type = str text_type = unicode int_types = int, long diff -Nru sqlalchemy-1.0.12+ds1/PKG-INFO sqlalchemy-1.0.14+ds1/PKG-INFO --- sqlalchemy-1.0.12+ds1/PKG-INFO 2016-02-15 19:50:23.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/PKG-INFO 2016-07-06 21:28:49.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: SQLAlchemy -Version: 1.0.12 +Version: 1.0.14 Summary: Database Abstraction Library Home-page: http://www.sqlalchemy.org Author: Mike Bayer diff -Nru sqlalchemy-1.0.12+ds1/test/base/test_utils.py sqlalchemy-1.0.14+ds1/test/base/test_utils.py --- sqlalchemy-1.0.12+ds1/test/base/test_utils.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/test/base/test_utils.py 2016-07-06 21:09:34.000000000 +0000 @@ -7,7 +7,7 @@ from sqlalchemy.testing.util import picklers, gc_collect from sqlalchemy.util import classproperty, WeakSequence, get_callable_argspec from sqlalchemy.sql import column -from sqlalchemy.util import langhelpers +from sqlalchemy.util import langhelpers, compat import inspect @@ -343,6 +343,20 @@ [1, 2, 3] ) + def test_from_bytes(self): + + eq_( + util.to_list(compat.b('abc')), + [compat.b('abc')] + ) + + eq_( + util.to_list([ + compat.b('abc'), compat.b('def')]), + [compat.b('abc'), compat.b('def')] + ) + + class ColumnCollectionTest(fixtures.TestBase): def test_in(self): @@ -2173,3 +2187,38 @@ eq_(B.something, {'foo': 1, 'bazz': 2}) +class TestProperties(fixtures.TestBase): + + def test_pickle(self): + data = {'hello': 'bla'} + props = util.Properties(data) + + for loader, dumper in picklers(): + s = dumper(props) + p = loader(s) + + eq_(props._data, p._data) + eq_(props.keys(), p.keys()) + + def test_pickle_immuatbleprops(self): + data = {'hello': 'bla'} + props = util.Properties(data).as_immutable() + + for loader, dumper in picklers(): + s = dumper(props) + p = loader(s) + + eq_(props._data, p._data) + eq_(props.keys(), p.keys()) + + def test_pickle_orderedprops(self): + data = {'hello': 'bla'} + props = util.OrderedProperties() + props.update(data) + + for loader, dumper in picklers(): + s = dumper(props) + p = loader(s) + + eq_(props._data, p._data) + eq_(props.keys(), p.keys()) diff -Nru sqlalchemy-1.0.12+ds1/test/dialect/mssql/test_compiler.py sqlalchemy-1.0.14+ds1/test/dialect/mssql/test_compiler.py --- sqlalchemy-1.0.12+ds1/test/dialect/mssql/test_compiler.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/test/dialect/mssql/test_compiler.py 2016-07-06 21:09:34.000000000 +0000 @@ -1,5 +1,5 @@ # -*- encoding: utf-8 -from sqlalchemy.testing import eq_ +from sqlalchemy.testing import eq_, is_ from sqlalchemy import schema from sqlalchemy.sql import table, column from sqlalchemy.databases import mssql @@ -521,6 +521,30 @@ assert t.c.x in set(c._create_result_map()['x'][1]) assert t.c.y in set(c._create_result_map()['y'][1]) + def test_limit_offset_w_ambiguous_cols(self): + t = table('t', column('x', Integer), column('y', Integer)) + + cols = [t.c.x, t.c.x.label('q'), t.c.x.label('p'), t.c.y] + s = select(cols).where(t.c.x == 5).order_by(t.c.y).limit(10).offset(20) + + self.assert_compile( + s, + "SELECT anon_1.x, anon_1.q, anon_1.p, anon_1.y " + "FROM (SELECT t.x AS x, t.x AS q, t.x AS p, t.y AS y, " + "ROW_NUMBER() OVER (ORDER BY t.y) AS mssql_rn " + "FROM t " + "WHERE t.x = :x_1) AS anon_1 " + "WHERE mssql_rn > :param_1 AND mssql_rn <= :param_2 + :param_1", + checkparams={'param_1': 20, 'param_2': 10, 'x_1': 5} + ) + c = s.compile(dialect=mssql.MSDialect()) + eq_(len(c._result_columns), 4) + + result_map = c._create_result_map() + + for col in cols: + is_(result_map[col.key][1][0], col) + def test_limit_offset_with_correlated_order_by(self): t1 = table('t1', column('x', Integer), column('y', Integer)) t2 = table('t2', column('x', Integer), column('y', Integer)) @@ -547,6 +571,31 @@ assert t1.c.x in set(c._create_result_map()['x'][1]) assert t1.c.y in set(c._create_result_map()['y'][1]) + def test_offset_dont_misapply_labelreference(self): + m = MetaData() + + t = Table('t', m, Column('x', Integer)) + + expr1 = func.foo(t.c.x).label('x') + expr2 = func.foo(t.c.x).label('y') + + stmt1 = select([expr1]).order_by(expr1.desc()).offset(1) + stmt2 = select([expr2]).order_by(expr2.desc()).offset(1) + + self.assert_compile( + stmt1, + "SELECT anon_1.x FROM (SELECT foo(t.x) AS x, " + "ROW_NUMBER() OVER (ORDER BY foo(t.x) DESC) AS mssql_rn FROM t) " + "AS anon_1 WHERE mssql_rn > :param_1" + ) + + self.assert_compile( + stmt2, + "SELECT anon_1.y FROM (SELECT foo(t.x) AS y, " + "ROW_NUMBER() OVER (ORDER BY foo(t.x) DESC) AS mssql_rn FROM t) " + "AS anon_1 WHERE mssql_rn > :param_1" + ) + def test_limit_zero_offset_using_window(self): t = table('t', column('x', Integer), column('y', Integer)) diff -Nru sqlalchemy-1.0.12+ds1/test/dialect/postgresql/test_dialect.py sqlalchemy-1.0.14+ds1/test/dialect/postgresql/test_dialect.py --- sqlalchemy-1.0.12+ds1/test/dialect/postgresql/test_dialect.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/test/dialect/postgresql/test_dialect.py 2016-07-06 21:09:34.000000000 +0000 @@ -8,7 +8,7 @@ import datetime from sqlalchemy import ( Table, Column, select, MetaData, text, Integer, String, Sequence, Numeric, - DateTime, BigInteger, func, extract, SmallInteger) + DateTime, BigInteger, func, extract, SmallInteger, TypeDecorator) from sqlalchemy import exc, schema from sqlalchemy.dialects.postgresql import base as postgresql import logging @@ -242,6 +242,15 @@ "postgresql >= 8.2", "requires standard_conforming_strings") def test_serial_integer(self): + class BITD(TypeDecorator): + impl = Integer + + def load_dialect_impl(self, dialect): + if dialect.name == 'postgresql': + return BigInteger() + else: + return Integer() + for version, type_, expected in [ (None, Integer, 'SERIAL'), (None, BigInteger, 'BIGSERIAL'), @@ -249,6 +258,16 @@ ((9, 2), SmallInteger, 'SMALLSERIAL'), (None, postgresql.INTEGER, 'SERIAL'), (None, postgresql.BIGINT, 'BIGSERIAL'), + ( + None, Integer().with_variant(BigInteger(), 'postgresql'), + 'BIGSERIAL'), + ( + None, Integer().with_variant(postgresql.BIGINT, 'postgresql'), + 'BIGSERIAL'), + ( + (9, 2), Integer().with_variant(SmallInteger, 'postgresql'), + 'SMALLSERIAL'), + (None, BITD(), 'BIGSERIAL') ]: m = MetaData() diff -Nru sqlalchemy-1.0.12+ds1/test/dialect/postgresql/test_reflection.py sqlalchemy-1.0.14+ds1/test/dialect/postgresql/test_reflection.py --- sqlalchemy-1.0.12+ds1/test/dialect/postgresql/test_reflection.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/test/dialect/postgresql/test_reflection.py 2016-07-06 21:09:34.000000000 +0000 @@ -572,6 +572,29 @@ ['test_schema_2.some_other_table', 'test_schema.some_table'])) @testing.provide_metadata + def test_cross_schema_reflection_metadata_uses_schema(self): + # test [ticket:3716] + + metadata = self.metadata + + Table('some_table', metadata, + Column('id', Integer, primary_key=True), + Column('sid', Integer, ForeignKey('some_other_table.id')), + schema='test_schema' + ) + Table('some_other_table', metadata, + Column('id', Integer, primary_key=True), + schema=None + ) + metadata.create_all() + with testing.db.connect() as conn: + meta2 = MetaData(conn, schema="test_schema") + meta2.reflect() + + eq_(set(meta2.tables), set( + ['some_other_table', 'test_schema.some_table'])) + + @testing.provide_metadata def test_uppercase_lowercase_table(self): metadata = self.metadata diff -Nru sqlalchemy-1.0.12+ds1/test/dialect/test_oracle.py sqlalchemy-1.0.14+ds1/test/dialect/test_oracle.py --- sqlalchemy-1.0.12+ds1/test/dialect/test_oracle.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/test/dialect/test_oracle.py 2016-07-06 21:09:34.000000000 +0000 @@ -344,6 +344,79 @@ "mytable_1.myid, mytable_1.name" ) + def test_for_update_of_w_limit_adaption_col_present(self): + table1 = table('mytable', column('myid'), column('name')) + + self.assert_compile( + select([table1.c.myid, table1.c.name]). + where(table1.c.myid == 7). + with_for_update(nowait=True, of=table1.c.name). + limit(10), + "SELECT myid, name FROM " + "(SELECT mytable.myid AS myid, mytable.name AS name " + "FROM mytable WHERE mytable.myid = :myid_1) " + "WHERE ROWNUM <= :param_1 FOR UPDATE OF name NOWAIT", + ) + + def test_for_update_of_w_limit_adaption_col_unpresent(self): + table1 = table('mytable', column('myid'), column('name')) + + self.assert_compile( + select([table1.c.myid]). + where(table1.c.myid == 7). + with_for_update(nowait=True, of=table1.c.name). + limit(10), + "SELECT myid FROM " + "(SELECT mytable.myid AS myid, mytable.name AS name " + "FROM mytable WHERE mytable.myid = :myid_1) " + "WHERE ROWNUM <= :param_1 FOR UPDATE OF name NOWAIT", + ) + + def test_for_update_of_w_limit_offset_adaption_col_present(self): + table1 = table('mytable', column('myid'), column('name')) + + self.assert_compile( + select([table1.c.myid, table1.c.name]). + where(table1.c.myid == 7). + with_for_update(nowait=True, of=table1.c.name). + limit(10).offset(50), + "SELECT myid, name FROM (SELECT myid, name, ROWNUM AS ora_rn " + "FROM (SELECT mytable.myid AS myid, mytable.name AS name " + "FROM mytable WHERE mytable.myid = :myid_1) " + "WHERE ROWNUM <= :param_1 + :param_2) WHERE ora_rn > :param_2 " + "FOR UPDATE OF name NOWAIT", + ) + + def test_for_update_of_w_limit_offset_adaption_col_unpresent(self): + table1 = table('mytable', column('myid'), column('name')) + + self.assert_compile( + select([table1.c.myid]). + where(table1.c.myid == 7). + with_for_update(nowait=True, of=table1.c.name). + limit(10).offset(50), + "SELECT myid FROM (SELECT myid, ROWNUM AS ora_rn, name " + "FROM (SELECT mytable.myid AS myid, mytable.name AS name " + "FROM mytable WHERE mytable.myid = :myid_1) " + "WHERE ROWNUM <= :param_1 + :param_2) WHERE ora_rn > :param_2 " + "FOR UPDATE OF name NOWAIT", + ) + + def test_for_update_of_w_limit_offset_adaption_partial_col_unpresent(self): + table1 = table('mytable', column('myid'), column('foo'), column('bar')) + + self.assert_compile( + select([table1.c.myid, table1.c.bar]). + where(table1.c.myid == 7). + with_for_update(nowait=True, of=[table1.c.foo, table1.c.bar]). + limit(10).offset(50), + "SELECT myid, bar FROM (SELECT myid, bar, ROWNUM AS ora_rn, " + "foo FROM (SELECT mytable.myid AS myid, mytable.bar AS bar, " + "mytable.foo AS foo FROM mytable WHERE mytable.myid = :myid_1) " + "WHERE ROWNUM <= :param_1 + :param_2) WHERE ora_rn > :param_2 " + "FOR UPDATE OF foo, bar NOWAIT" + ) + def test_limit_preserves_typing_information(self): class MyType(TypeDecorator): impl = Integer diff -Nru sqlalchemy-1.0.12+ds1/test/engine/test_execute.py sqlalchemy-1.0.14+ds1/test/engine/test_execute.py --- sqlalchemy-1.0.12+ds1/test/engine/test_execute.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/test/engine/test_execute.py 2016-07-06 21:09:34.000000000 +0000 @@ -838,158 +838,6 @@ ) -class ResultProxyTest(fixtures.TestBase): - __backend__ = True - - def test_nontuple_row(self): - """ensure the C version of BaseRowProxy handles - duck-type-dependent rows.""" - - from sqlalchemy.engine import RowProxy - - class MyList(object): - - def __init__(self, l): - self.l = l - - def __len__(self): - return len(self.l) - - def __getitem__(self, i): - return list.__getitem__(self.l, i) - - proxy = RowProxy(object(), MyList(['value']), [None], { - 'key': (None, None, 0), 0: (None, None, 0)}) - eq_(list(proxy), ['value']) - eq_(proxy[0], 'value') - eq_(proxy['key'], 'value') - - @testing.provide_metadata - def test_no_rowcount_on_selects_inserts(self): - """assert that rowcount is only called on deletes and updates. - - This because cursor.rowcount may can be expensive on some dialects - such as Firebird, however many dialects require it be called - before the cursor is closed. - - """ - - metadata = self.metadata - - engine = engines.testing_engine() - - t = Table('t1', metadata, - Column('data', String(10)) - ) - metadata.create_all(engine) - - with patch.object( - engine.dialect.execution_ctx_cls, "rowcount") as mock_rowcount: - mock_rowcount.__get__ = Mock() - engine.execute(t.insert(), - {'data': 'd1'}, - {'data': 'd2'}, - {'data': 'd3'}) - - eq_(len(mock_rowcount.__get__.mock_calls), 0) - - eq_( - engine.execute(t.select()).fetchall(), - [('d1', ), ('d2', ), ('d3', )] - ) - eq_(len(mock_rowcount.__get__.mock_calls), 0) - - engine.execute(t.update(), {'data': 'd4'}) - - eq_(len(mock_rowcount.__get__.mock_calls), 1) - - engine.execute(t.delete()) - eq_(len(mock_rowcount.__get__.mock_calls), 2) - - def test_rowproxy_is_sequence(self): - import collections - from sqlalchemy.engine import RowProxy - - row = RowProxy( - object(), ['value'], [None], - {'key': (None, None, 0), 0: (None, None, 0)}) - assert isinstance(row, collections.Sequence) - - @testing.requires.cextensions - def test_row_c_sequence_check(self): - import csv - - metadata = MetaData() - metadata.bind = 'sqlite://' - users = Table('users', metadata, - Column('id', Integer, primary_key=True), - Column('name', String(40)), - ) - users.create() - - users.insert().execute(name='Test') - row = users.select().execute().fetchone() - - s = util.StringIO() - writer = csv.writer(s) - # csv performs PySequenceCheck call - writer.writerow(row) - assert s.getvalue().strip() == '1,Test' - - @testing.requires.selectone - def test_empty_accessors(self): - statements = [ - ( - "select 1", - [ - lambda r: r.last_inserted_params(), - lambda r: r.last_updated_params(), - lambda r: r.prefetch_cols(), - lambda r: r.postfetch_cols(), - lambda r: r.inserted_primary_key - ], - "Statement is not a compiled expression construct." - ), - ( - select([1]), - [ - lambda r: r.last_inserted_params(), - lambda r: r.inserted_primary_key - ], - r"Statement is not an insert\(\) expression construct." - ), - ( - select([1]), - [ - lambda r: r.last_updated_params(), - ], - r"Statement is not an update\(\) expression construct." - ), - ( - select([1]), - [ - lambda r: r.prefetch_cols(), - lambda r: r.postfetch_cols() - ], - r"Statement is not an insert\(\) " - r"or update\(\) expression construct." - ), - ] - - for stmt, meths, msg in statements: - r = testing.db.execute(stmt) - try: - for meth in meths: - assert_raises_message( - tsa.exc.InvalidRequestError, - msg, - meth, r - ) - - finally: - r.close() - - class ExecutionOptionsTest(fixtures.TestBase): def test_dialect_conn_options(self): @@ -1047,160 +895,6 @@ ) -class AlternateResultProxyTest(fixtures.TablesTest): - __requires__ = ('sqlite', ) - - @classmethod - def setup_bind(cls): - cls.engine = engine = testing_engine('sqlite://') - return engine - - @classmethod - def define_tables(cls, metadata): - Table( - 'test', metadata, - Column('x', Integer, primary_key=True), - Column('y', String(50, convert_unicode='force')) - ) - - @classmethod - def insert_data(cls): - cls.engine.execute(cls.tables.test.insert(), [ - {'x': i, 'y': "t_%d" % i} for i in range(1, 12) - ]) - - @contextmanager - def _proxy_fixture(self, cls): - self.table = self.tables.test - - class ExcCtx(default.DefaultExecutionContext): - - def get_result_proxy(self): - return cls(self) - self.patcher = patch.object( - self.engine.dialect, "execution_ctx_cls", ExcCtx) - with self.patcher: - yield - - def _test_proxy(self, cls): - with self._proxy_fixture(cls): - rows = [] - r = self.engine.execute(select([self.table])) - assert isinstance(r, cls) - for i in range(5): - rows.append(r.fetchone()) - eq_(rows, [(i, "t_%d" % i) for i in range(1, 6)]) - - rows = r.fetchmany(3) - eq_(rows, [(i, "t_%d" % i) for i in range(6, 9)]) - - rows = r.fetchall() - eq_(rows, [(i, "t_%d" % i) for i in range(9, 12)]) - - r = self.engine.execute(select([self.table])) - rows = r.fetchmany(None) - eq_(rows[0], (1, "t_1")) - # number of rows here could be one, or the whole thing - assert len(rows) == 1 or len(rows) == 11 - - r = self.engine.execute(select([self.table]).limit(1)) - r.fetchone() - eq_(r.fetchone(), None) - - r = self.engine.execute(select([self.table]).limit(5)) - rows = r.fetchmany(6) - eq_(rows, [(i, "t_%d" % i) for i in range(1, 6)]) - - # result keeps going just fine with blank results... - eq_(r.fetchmany(2), []) - - eq_(r.fetchmany(2), []) - - eq_(r.fetchall(), []) - - eq_(r.fetchone(), None) - - # until we close - r.close() - - self._assert_result_closed(r) - - r = self.engine.execute(select([self.table]).limit(5)) - eq_(r.first(), (1, "t_1")) - self._assert_result_closed(r) - - r = self.engine.execute(select([self.table]).limit(5)) - eq_(r.scalar(), 1) - self._assert_result_closed(r) - - def _assert_result_closed(self, r): - assert_raises_message( - tsa.exc.ResourceClosedError, - "object is closed", - r.fetchone - ) - - assert_raises_message( - tsa.exc.ResourceClosedError, - "object is closed", - r.fetchmany, 2 - ) - - assert_raises_message( - tsa.exc.ResourceClosedError, - "object is closed", - r.fetchall - ) - - def test_plain(self): - self._test_proxy(_result.ResultProxy) - - def test_buffered_row_result_proxy(self): - self._test_proxy(_result.BufferedRowResultProxy) - - def test_fully_buffered_result_proxy(self): - self._test_proxy(_result.FullyBufferedResultProxy) - - def test_buffered_column_result_proxy(self): - self._test_proxy(_result.BufferedColumnResultProxy) - - def test_buffered_row_growth(self): - with self._proxy_fixture(_result.BufferedRowResultProxy): - with self.engine.connect() as conn: - conn.execute(self.table.insert(), [ - {'x': i, 'y': "t_%d" % i} for i in range(15, 1200) - ]) - result = conn.execute(self.table.select()) - checks = { - 0: 5, 1: 10, 9: 20, 135: 250, 274: 500, - 1351: 1000 - } - for idx, row in enumerate(result, 0): - if idx in checks: - eq_(result._bufsize, checks[idx]) - le_( - len(result._BufferedRowResultProxy__rowbuffer), - 1000 - ) - - def test_max_row_buffer_option(self): - with self._proxy_fixture(_result.BufferedRowResultProxy): - with self.engine.connect() as conn: - conn.execute(self.table.insert(), [ - {'x': i, 'y': "t_%d" % i} for i in range(15, 1200) - ]) - result = conn.execution_options(max_row_buffer=27).execute( - self.table.select() - ) - for idx, row in enumerate(result, 0): - if idx in (16, 70, 150, 250): - eq_(result._bufsize, 27) - le_( - len(result._BufferedRowResultProxy__rowbuffer), - 27 - ) - - class EngineEventsTest(fixtures.TestBase): __requires__ = 'ad_hoc_engines', __backend__ = True diff -Nru sqlalchemy-1.0.12+ds1/test/engine/test_pool.py sqlalchemy-1.0.14+ds1/test/engine/test_pool.py --- sqlalchemy-1.0.12+ds1/test/engine/test_pool.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/test/engine/test_pool.py 2016-07-06 21:09:34.000000000 +0000 @@ -15,7 +15,7 @@ join_timeout = 10 -def MockDBAPI(): +def MockDBAPI(): # noqa def cursor(): return Mock() @@ -61,8 +61,9 @@ def _queuepool_dbapi_fixture(self, **kw): dbapi = MockDBAPI() - return dbapi, pool.QueuePool(creator=lambda: dbapi.connect('foo.db'), - **kw) + return dbapi, pool.QueuePool( + creator=lambda: dbapi.connect('foo.db'), + **kw) class PoolTest(PoolTestBase): @@ -95,7 +96,8 @@ assert c1 is not c2 assert c1 is c3 - eq_(dbapi.connect.mock_calls, + eq_( + dbapi.connect.mock_calls, [ call("foo.db"), call("foo.db"), @@ -156,7 +158,8 @@ for p in pool.QueuePool(creator=dbapi.connect, pool_size=3, max_overflow=-1, use_threadlocal=True), \ - pool.SingletonThreadPool(creator=dbapi.connect, + pool.SingletonThreadPool( + creator=dbapi.connect, use_threadlocal=True): c1 = p.connect() c2 = p.connect() @@ -348,14 +351,14 @@ def test_first_connect_event(self): p, canary = self._first_connect_event_fixture() - c1 = p.connect() + p.connect() eq_(canary, ['first_connect']) def test_first_connect_event_fires_once(self): p, canary = self._first_connect_event_fixture() - c1 = p.connect() - c2 = p.connect() + p.connect() + p.connect() eq_(canary, ['first_connect']) @@ -363,31 +366,31 @@ p, canary = self._first_connect_event_fixture() p2 = p.recreate() - c1 = p.connect() - c2 = p2.connect() + p.connect() + p2.connect() eq_(canary, ['first_connect', 'first_connect']) def test_first_connect_on_subsequently_recreated(self): p, canary = self._first_connect_event_fixture() - c1 = p.connect() + p.connect() p2 = p.recreate() - c2 = p2.connect() + p2.connect() eq_(canary, ['first_connect', 'first_connect']) def test_connect_event(self): p, canary = self._connect_event_fixture() - c1 = p.connect() + p.connect() eq_(canary, ['connect']) def test_connect_event_fires_subsequent(self): p, canary = self._connect_event_fixture() - c1 = p.connect() - c2 = p.connect() + c1 = p.connect() # noqa + c2 = p.connect() # noqa eq_(canary, ['connect', 'connect']) @@ -396,39 +399,39 @@ p2 = p.recreate() - c1 = p.connect() - c2 = p2.connect() + p.connect() + p2.connect() eq_(canary, ['connect', 'connect']) def test_connect_on_subsequently_recreated(self): p, canary = self._connect_event_fixture() - c1 = p.connect() + p.connect() p2 = p.recreate() - c2 = p2.connect() + p2.connect() eq_(canary, ['connect', 'connect']) def test_checkout_event(self): p, canary = self._checkout_event_fixture() - c1 = p.connect() + p.connect() eq_(canary, ['checkout']) def test_checkout_event_fires_subsequent(self): p, canary = self._checkout_event_fixture() - c1 = p.connect() - c2 = p.connect() + p.connect() + p.connect() eq_(canary, ['checkout', 'checkout']) def test_checkout_event_on_subsequently_recreated(self): p, canary = self._checkout_event_fixture() - c1 = p.connect() + p.connect() p2 = p.recreate() - c2 = p2.connect() + p2.connect() eq_(canary, ['checkout', 'checkout']) @@ -622,19 +625,21 @@ for th in threads: th.join(join_timeout) - eq_(evt.mock_calls, - [ - call.first_connect(), - call.connect(), - call.connect(), - call.connect()] - ) + eq_( + evt.mock_calls, + [ + call.first_connect(), + call.connect(), + call.connect(), + call.connect()] + ) class DeprecatedPoolListenerTest(PoolTestBase): @testing.requires.predictable_gc @testing.uses_deprecated(r".*Use event.listen") def test_listeners(self): + class InstrumentingListener(object): def __init__(self): if hasattr(self, 'connect'): @@ -653,18 +658,19 @@ self.checked_out = [] self.checked_in = [] - def assert_total(innerself, conn, fconn, cout, cin): - eq_(len(innerself.connected), conn) - eq_(len(innerself.first_connected), fconn) - eq_(len(innerself.checked_out), cout) - eq_(len(innerself.checked_in), cin) - - def assert_in(innerself, item, in_conn, in_fconn, - in_cout, in_cin): - self.assert_((item in innerself.connected) == in_conn) - self.assert_((item in innerself.first_connected) == in_fconn) - self.assert_((item in innerself.checked_out) == in_cout) - self.assert_((item in innerself.checked_in) == in_cin) + def assert_total(self, conn, fconn, cout, cin): + eq_(len(self.connected), conn) + eq_(len(self.first_connected), fconn) + eq_(len(self.checked_out), cout) + eq_(len(self.checked_in), cin) + + def assert_in( + self, item, in_conn, in_fconn, + in_cout, in_cin): + eq_((item in self.connected), in_conn) + eq_((item in self.first_connected), in_fconn) + eq_((item in self.checked_out), in_cout) + eq_((item in self.checked_in), in_cin) def inst_connect(self, con, record): print("connect(%s, %s)" % (con, record)) @@ -884,8 +890,9 @@ self._do_testqueuepool(useclose=True) def _do_testqueuepool(self, useclose=False): - p = self._queuepool_fixture(pool_size=3, - max_overflow=-1) + p = self._queuepool_fixture( + pool_size=3, + max_overflow=-1) def status(pool): return pool.size(), pool.checkedin(), pool.overflow(), \ @@ -934,18 +941,20 @@ @testing.requires.timing_intensive def test_timeout(self): - p = self._queuepool_fixture(pool_size=3, - max_overflow=0, - timeout=2) - c1 = p.connect() - c2 = p.connect() - c3 = p.connect() + p = self._queuepool_fixture( + pool_size=3, + max_overflow=0, + timeout=2) + c1 = p.connect() # noqa + c2 = p.connect() # noqa + c3 = p.connect() # noqa now = time.time() - try: - c4 = p.connect() - assert False - except tsa.exc.TimeoutError: - assert int(time.time() - now) == 2 + + assert_raises( + tsa.exc.TimeoutError, + p.connect + ) + assert int(time.time() - now) == 2 @testing.requires.threading_with_mock @testing.requires.timing_intensive @@ -959,9 +968,9 @@ # them back to the start of do_get() dbapi = MockDBAPI() p = pool.QueuePool( - creator=lambda: dbapi.connect(delay=.05), - pool_size=2, - max_overflow=1, use_threadlocal=False, timeout=3) + creator=lambda: dbapi.connect(delay=.05), + pool_size=2, + max_overflow=1, use_threadlocal=False, timeout=3) timeouts = [] def checkout(): @@ -1042,9 +1051,9 @@ return creator() p = pool.QueuePool(creator=create, pool_size=2, max_overflow=3) - c1 = self._with_teardown(p.connect()) - c2 = self._with_teardown(p.connect()) - c3 = self._with_teardown(p.connect()) + c1 = self._with_teardown(p.connect()) # noqa + c2 = self._with_teardown(p.connect()) # noqa + c3 = self._with_teardown(p.connect()) # noqa eq_(p._overflow, 1) creator = failing_dbapi assert_raises(Exception, p.connect) @@ -1088,15 +1097,15 @@ threads = [ threading.Thread( - target=run_test, args=("success_one", p, False)), + target=run_test, args=("success_one", p, False)), threading.Thread( - target=run_test, args=("success_two", p, False)), + target=run_test, args=("success_two", p, False)), threading.Thread( - target=run_test, args=("overflow_one", p, True)), + target=run_test, args=("overflow_one", p, True)), threading.Thread( - target=run_test, args=("overflow_two", p, False)), + target=run_test, args=("overflow_two", p, False)), threading.Thread( - target=run_test, args=("overflow_three", p, False)) + target=run_test, args=("overflow_three", p, False)) ] for t in threads: t.start() @@ -1142,13 +1151,14 @@ time.sleep(.1) conn.close() - c1 = p.connect() + c1 = p.connect() # noqa c2 = p.connect() threads = [] for i in range(2): - t = threading.Thread(target=waiter, - args=(p, timeout, max_overflow)) + t = threading.Thread( + target=waiter, + args=(p, timeout, max_overflow)) t.daemon = True t.start() threads.append(t) @@ -1167,7 +1177,8 @@ def test_connrec_invalidated_within_checkout_no_race(self): """Test that a concurrent ConnectionRecord.invalidate() which - occurs after the ConnectionFairy has called _ConnectionRecord.checkout() + occurs after the ConnectionFairy has called + _ConnectionRecord.checkout() but before the ConnectionFairy tests "fairy.connection is None" will not result in an InvalidRequestError. @@ -1202,7 +1213,6 @@ is_(conn._connection_record.connection, None) conn.close() - @testing.requires.threading_with_mock @testing.requires.timing_intensive def test_notify_waiters(self): @@ -1213,9 +1223,10 @@ def creator(): canary.append(1) return dbapi.connect() - p1 = pool.QueuePool(creator=creator, - pool_size=1, timeout=None, - max_overflow=0) + p1 = pool.QueuePool( + creator=creator, + pool_size=1, timeout=None, + max_overflow=0) def waiter(p): conn = p.connect() @@ -1309,8 +1320,9 @@ self._test_overflow_no_gc(False) def _test_overflow_no_gc(self, threadlocal): - p = self._queuepool_fixture(pool_size=2, - max_overflow=2) + p = self._queuepool_fixture( + pool_size=2, + max_overflow=2) # disable weakref collection of the # underlying connections @@ -1340,8 +1352,9 @@ @testing.requires.predictable_gc def test_weakref_kaboom(self): - p = self._queuepool_fixture(pool_size=3, - max_overflow=-1, use_threadlocal=True) + p = self._queuepool_fixture( + pool_size=3, + max_overflow=-1, use_threadlocal=True) c1 = p.connect() c2 = p.connect() c1.close() @@ -1360,8 +1373,9 @@ ConnectionFairy with an ambiguous counter. i.e. its not true reference counting.""" - p = self._queuepool_fixture(pool_size=3, - max_overflow=-1, use_threadlocal=True) + p = self._queuepool_fixture( + pool_size=3, + max_overflow=-1, use_threadlocal=True) c1 = p.connect() c2 = p.connect() assert c1 is c2 @@ -1373,28 +1387,36 @@ self.assert_(p.checkedout() == 0) def test_recycle(self): - p = self._queuepool_fixture(pool_size=1, - max_overflow=0, - recycle=3) - c1 = p.connect() - c_id = id(c1.connection) - c1.close() - c2 = p.connect() - assert id(c2.connection) == c_id - c2.close() - time.sleep(4) - c3 = p.connect() - assert id(c3.connection) != c_id + with patch("sqlalchemy.pool.time.time") as mock: + mock.return_value = 10000 + + p = self._queuepool_fixture( + pool_size=1, + max_overflow=0, + recycle=30) + c1 = p.connect() + c_ref = weakref.ref(c1.connection) + c1.close() + mock.return_value = 10001 + c2 = p.connect() + + is_(c2.connection, c_ref()) + c2.close() + + mock.return_value = 10035 + c3 = p.connect() + is_not_(c3.connection, c_ref()) @testing.requires.timing_intensive def test_recycle_on_invalidate(self): - p = self._queuepool_fixture(pool_size=1, - max_overflow=0) + p = self._queuepool_fixture( + pool_size=1, + max_overflow=0) c1 = p.connect() - c_id = id(c1.connection) + c_ref = weakref.ref(c1.connection) c1.close() c2 = p.connect() - assert id(c2.connection) == c_id + is_(c2.connection, c_ref()) c2_rec = c2._connection_record p._invalidate(c2) @@ -1402,28 +1424,30 @@ c2.close() time.sleep(.5) c3 = p.connect() - assert id(c3.connection) != c_id + + is_not_(c3.connection, c_ref()) @testing.requires.timing_intensive def test_recycle_on_soft_invalidate(self): - p = self._queuepool_fixture(pool_size=1, - max_overflow=0) + p = self._queuepool_fixture( + pool_size=1, + max_overflow=0) c1 = p.connect() - c_id = id(c1.connection) + c_ref = weakref.ref(c1.connection) c1.close() c2 = p.connect() - assert id(c2.connection) == c_id + is_(c2.connection, c_ref()) c2_rec = c2._connection_record c2.invalidate(soft=True) - assert c2_rec.connection is c2.connection + is_(c2_rec.connection, c2.connection) c2.close() time.sleep(.5) c3 = p.connect() - assert id(c3.connection) != c_id - assert c3._connection_record is c2_rec - assert c2_rec.connection is c3.connection + is_not_(c3.connection, c_ref()) + is_(c3._connection_record, c2_rec) + is_(c2_rec.connection, c3.connection) def _no_wr_finalize(self): finalize_fairy = pool._finalize_fairy @@ -1457,14 +1481,16 @@ dbapi.shutdown(False) - c1 = self._with_teardown(p.connect()) + c1 = self._with_teardown(p.connect()) # noqa assert p._pool.empty() # poolsize is one, so we're empty OK - c2 = self._with_teardown(p.connect()) + c2 = self._with_teardown(p.connect()) # noqa eq_(p._overflow, 1) # and not 2 # this hangs if p._overflow is 2 c3 = self._with_teardown(p.connect()) + c3.close() + def test_error_on_pooled_reconnect_cleanup_invalidate(self): dbapi, p = self._queuepool_dbapi_fixture(pool_size=1, max_overflow=2) c1 = p.connect() @@ -1474,8 +1500,9 @@ @testing.requires.timing_intensive def test_error_on_pooled_reconnect_cleanup_recycle(self): - dbapi, p = self._queuepool_dbapi_fixture(pool_size=1, - max_overflow=2, recycle=1) + dbapi, p = self._queuepool_dbapi_fixture( + pool_size=1, + max_overflow=2, recycle=1) c1 = p.connect() c1.close() time.sleep(1.5) @@ -1572,10 +1599,10 @@ c = p.connect() c.close() - def test_error_on_pooled_reconnect_cleanup_wcheckout_event(self): - dbapi, p = self._queuepool_dbapi_fixture(pool_size=1, - max_overflow=2) + dbapi, p = self._queuepool_dbapi_fixture( + pool_size=1, + max_overflow=2) c1 = p.connect() c1.close() @@ -1628,13 +1655,14 @@ def attempt(conn): time.sleep(random.random()) try: - conn._handle_dbapi_exception(Error(), "statement", {}, - Mock(), Mock()) + conn._handle_dbapi_exception( + Error(), "statement", {}, + Mock(), Mock()) except tsa.exc.DBAPIError: pass # run an error + invalidate operation on the remaining 7 open - #connections + # connections threads = [] for conn in conns: t = threading.Thread(target=attempt, args=(conn, )) @@ -1702,7 +1730,7 @@ dbapi, p = self._queuepool_dbapi_fixture(pool_size=1, max_overflow=0) c1 = p.connect() c1.detach() - c2 = p.connect() + c2 = p.connect() # noqa eq_(dbapi.connect.mock_calls, [call("foo.db"), call("foo.db")]) c1_con = c1.connection @@ -1739,8 +1767,9 @@ class ResetOnReturnTest(PoolTestBase): def _fixture(self, **kw): dbapi = Mock() - return dbapi, pool.QueuePool(creator=lambda: dbapi.connect('foo.db'), - **kw) + return dbapi, pool.QueuePool( + creator=lambda: dbapi.connect('foo.db'), + **kw) def test_plain_rollback(self): dbapi, p = self._fixture(reset_on_return='rollback') @@ -1889,7 +1918,7 @@ def test_connect_error(self): dbapi = MockDBAPI() p = pool.AssertionPool(creator=lambda: dbapi.connect('foo.db')) - c1 = p.connect() + c1 = p.connect() # noqa assert_raises(AssertionError, p.connect) def test_connect_multiple(self): @@ -1900,7 +1929,7 @@ c2 = p.connect() c2.close() - c3 = p.connect() + c3 = p.connect() # noqa assert_raises(AssertionError, p.connect) @@ -1918,16 +1947,19 @@ c1 = None c1 = p.connect() - dbapi.connect.assert_has_calls([ - call('foo.db'), - call('foo.db')], - any_order=True) + dbapi.connect.assert_has_calls( + [ + call('foo.db'), + call('foo.db')], + any_order=True) class StaticPoolTest(PoolTestBase): def test_recreate(self): dbapi = MockDBAPI() - creator = lambda: dbapi.connect('foo.db') + + def creator(): + return dbapi.connect('foo.db') p = pool.StaticPool(creator) p2 = p.recreate() assert p._creator is p2._creator @@ -1979,5 +2011,3 @@ conn = e.connect() conn.close() - - diff -Nru sqlalchemy-1.0.12+ds1/test/engine/test_reflection.py sqlalchemy-1.0.14+ds1/test/engine/test_reflection.py --- sqlalchemy-1.0.12+ds1/test/engine/test_reflection.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/test/engine/test_reflection.py 2016-07-06 21:09:34.000000000 +0000 @@ -801,7 +801,7 @@ @testing.provide_metadata def test_reserved(self): - # check a table that uses an SQL reserved name doesn't cause an + # check a table that uses a SQL reserved name doesn't cause an # error meta = self.metadata @@ -1304,6 +1304,31 @@ 'sa_fake_schema_123'), False) @testing.requires.schemas + @testing.requires.cross_schema_fk_reflection + @testing.provide_metadata + def test_blank_schema_arg(self): + metadata = self.metadata + + Table('some_table', metadata, + Column('id', Integer, primary_key=True), + Column('sid', Integer, sa.ForeignKey('some_other_table.id')), + schema=testing.config.test_schema + ) + Table('some_other_table', metadata, + Column('id', Integer, primary_key=True), + schema=None + ) + metadata.create_all() + with testing.db.connect() as conn: + meta2 = MetaData(conn, schema=testing.config.test_schema) + meta2.reflect() + + eq_(set(meta2.tables), set( + [ + 'some_other_table', + '%s.some_table' % testing.config.test_schema])) + + @testing.requires.schemas @testing.fails_on('sqlite', 'FIXME: unknown') @testing.fails_on('sybase', 'FIXME: unknown') def test_explicit_default_schema(self): diff -Nru sqlalchemy-1.0.12+ds1/test/ext/test_compiler.py sqlalchemy-1.0.14+ds1/test/ext/test_compiler.py --- sqlalchemy-1.0.12+ds1/test/ext/test_compiler.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/test/ext/test_compiler.py 2016-07-06 21:09:34.000000000 +0000 @@ -127,7 +127,7 @@ class MyThingy(ColumnClause): pass - @compiles(MyThingy, "psotgresql") + @compiles(MyThingy, 'postgresql') def visit_thingy(thingy, compiler, **kw): return "mythingy" diff -Nru sqlalchemy-1.0.12+ds1/test/orm/inheritance/test_basic.py sqlalchemy-1.0.14+ds1/test/orm/inheritance/test_basic.py --- sqlalchemy-1.0.12+ds1/test/orm/inheritance/test_basic.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/test/orm/inheritance/test_basic.py 2016-07-06 21:09:34.000000000 +0000 @@ -2098,7 +2098,7 @@ ) class TransientInheritingGCTest(fixtures.TestBase): - __requires__ = ('cpython',) + __requires__ = ('cpython', 'no_coverage') def _fixture(self): Base = declarative_base() diff -Nru sqlalchemy-1.0.12+ds1/test/orm/test_evaluator.py sqlalchemy-1.0.14+ds1/test/orm/test_evaluator.py --- sqlalchemy-1.0.12+ds1/test/orm/test_evaluator.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/test/orm/test_evaluator.py 2016-07-06 21:09:34.000000000 +0000 @@ -1,26 +1,30 @@ -"""Evluating SQL expressions on ORM objects""" -import sqlalchemy as sa -from sqlalchemy import testing -from sqlalchemy import String, Integer, select +"""Evaluating SQL expressions on ORM objects""" + +from sqlalchemy import String, Integer, bindparam from sqlalchemy.testing.schema import Table from sqlalchemy.testing.schema import Column -from sqlalchemy.orm import mapper, create_session -from sqlalchemy.testing import eq_ from sqlalchemy.testing import fixtures from sqlalchemy import and_, or_, not_ from sqlalchemy.orm import evaluator +from sqlalchemy.orm import mapper compiler = evaluator.EvaluatorCompiler() + + def eval_eq(clause, testcases=None): evaluator = compiler.process(clause) + def testeval(obj=None, expected_result=None): - assert evaluator(obj) == expected_result, "%s != %r for %s with %r" % (evaluator(obj), expected_result, clause, obj) + assert evaluator(obj) == expected_result, \ + "%s != %r for %s with %r" % ( + evaluator(obj), expected_result, clause, obj) if testcases: - for an_obj,result in testcases: + for an_obj, result in testcases: testeval(an_obj, result) return testeval + class EvaluateTest(fixtures.MappedTest): @classmethod def define_tables(cls, metadata): @@ -54,6 +58,18 @@ (User(id=None), None), ]) + def test_compare_to_callable_bind(self): + User = self.classes.User + + eval_eq( + User.name == bindparam('x', callable_=lambda: 'foo'), + testcases=[ + (User(name='foo'), True), + (User(name='bar'), False), + (User(name=None), None), + ] + ) + def test_compare_to_none(self): User = self.classes.User @@ -65,14 +81,16 @@ def test_true_false(self): User = self.classes.User - eval_eq(User.name == False, testcases=[ + eval_eq( + User.name == False, testcases=[ (User(name='foo'), False), (User(name=True), False), (User(name=False), True), ] ) - eval_eq(User.name == True, testcases=[ + eval_eq( + User.name == True, testcases=[ (User(name='foo'), False), (User(name=True), True), (User(name=False), False), diff -Nru sqlalchemy-1.0.12+ds1/test/orm/test_loading.py sqlalchemy-1.0.14+ds1/test/orm/test_loading.py --- sqlalchemy-1.0.12+ds1/test/orm/test_loading.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/test/orm/test_loading.py 2016-07-06 21:09:34.000000000 +0000 @@ -1,8 +1,11 @@ from . import _fixtures from sqlalchemy.orm import loading, Session, aliased -from sqlalchemy.testing.assertions import eq_, assert_raises +from sqlalchemy.testing.assertions import eq_, \ + assert_raises, assert_raises_message from sqlalchemy.util import KeyedTuple from sqlalchemy.testing import mock +from sqlalchemy import select +from sqlalchemy import exc # class GetFromIdentityTest(_fixtures.FixtureTest): # class LoadOnIdentTest(_fixtures.FixtureTest): # class InstanceProcessorTest(_fixture.FixtureTest): @@ -34,6 +37,19 @@ ) assert cursor.close.called, "Cursor wasn't closed" + def test_row_proc_not_created(self): + User = self.classes.User + s = Session() + + q = s.query(User.id, User.name) + stmt = select([User.id]) + + assert_raises_message( + exc.NoSuchColumnError, + "Could not locate column in row for column 'users.name'", + q.from_statement(stmt).all + ) + class MergeResultTest(_fixtures.FixtureTest): run_setup_mappers = 'once' diff -Nru sqlalchemy-1.0.12+ds1/test/orm/test_mapper.py sqlalchemy-1.0.14+ds1/test/orm/test_mapper.py --- sqlalchemy-1.0.12+ds1/test/orm/test_mapper.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/test/orm/test_mapper.py 2016-07-06 21:09:34.000000000 +0000 @@ -373,6 +373,47 @@ }) assert getattr(Foo().__class__, 'name').impl is not None + def test_class_hier_only_instrument_once_multiple_configure(self): + users, addresses = (self.tables.users, self.tables.addresses) + + class A(object): + pass + + class ASub(A): + pass + + class ASubSub(ASub): + pass + + class B(object): + pass + + from sqlalchemy.testing import mock + from sqlalchemy.orm.attributes import register_attribute_impl + + with mock.patch( + "sqlalchemy.orm.attributes.register_attribute_impl", + side_effect=register_attribute_impl + ) as some_mock: + + mapper(A, users, properties={ + 'bs': relationship(B) + }) + mapper(B, addresses) + + configure_mappers() + + mapper(ASub, inherits=A) + mapper(ASubSub, inherits=ASub) + + configure_mappers() + + b_calls = [ + c for c in some_mock.mock_calls if c[1][1] == 'bs' + ] + eq_(len(b_calls), 3) + + def test_check_descriptor_as_method(self): User, users = self.classes.User, self.tables.users diff -Nru sqlalchemy-1.0.12+ds1/test/orm/test_query.py sqlalchemy-1.0.14+ds1/test/orm/test_query.py --- sqlalchemy-1.0.12+ds1/test/orm/test_query.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/test/orm/test_query.py 2016-07-06 21:09:34.000000000 +0000 @@ -420,7 +420,7 @@ self.assert_compile( select([Foo]).where(Foo.foob == 'somename').order_by(Foo.foob), "SELECT users.id, users.name FROM users " - "WHERE coalesce(users.name) = :coalesce_1 " + "WHERE coalesce(users.name) = :param_1 " "ORDER BY coalesce(users.name)" ) @@ -1494,6 +1494,63 @@ }, with_polymorphic="*" if polymorphic else None) mapper(Address, addresses) + def _func_fixture(self, label=False): + User = self.classes.User + users = self.tables.users + + if label: + mapper(User, users, properties={ + "foobar": column_property( + func.foob(users.c.name).label(None) + ) + }) + else: + mapper(User, users, properties={ + "foobar": column_property( + func.foob(users.c.name) + ) + }) + + def test_anon_label_function_auto(self): + self._func_fixture() + User = self.classes.User + + s = Session() + + u1 = aliased(User) + self.assert_compile( + s.query(User.foobar, u1.foobar), + "SELECT foob(users.name) AS foob_1, foob(users_1.name) AS foob_2 " + "FROM users, users AS users_1" + ) + + def test_anon_label_function_manual(self): + self._func_fixture(label=True) + User = self.classes.User + + s = Session() + + u1 = aliased(User) + self.assert_compile( + s.query(User.foobar, u1.foobar), + "SELECT foob(users.name) AS foob_1, foob(users_1.name) AS foob_2 " + "FROM users, users AS users_1" + ) + + def test_anon_label_ad_hoc_labeling(self): + self._func_fixture() + User = self.classes.User + + s = Session() + + u1 = aliased(User) + self.assert_compile( + s.query(User.foobar.label('x'), u1.foobar.label('y')), + "SELECT foob(users.name) AS x, foob(users_1.name) AS y " + "FROM users, users AS users_1" + ) + + def test_order_by_column_prop_string(self): User, Address = self.classes("User", "Address") self._fixture(label=True) @@ -2921,6 +2978,25 @@ [User(id=7), User(id=8), User(id=9), User(id=10)] ) + def test_group_by_accepts_text(self): + User = self.classes.User + s = create_session() + + q = s.query(User).group_by(text("name")) + self.assert_compile( + q, + "SELECT users.id AS users_id, users.name AS users_name " + "FROM users GROUP BY name" + ) + + def test_orm_columns_accepts_text(self): + from sqlalchemy.orm.base import _orm_columns + t = text("x") + eq_( + _orm_columns(t), + [t] + ) + def test_order_by_w_eager_one(self): User = self.classes.User s = create_session() diff -Nru sqlalchemy-1.0.12+ds1/test/sql/test_compiler.py sqlalchemy-1.0.14+ds1/test/sql/test_compiler.py --- sqlalchemy-1.0.12+ds1/test/sql/test_compiler.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/test/sql/test_compiler.py 2016-07-06 21:09:34.000000000 +0000 @@ -637,6 +637,21 @@ "myothertable.otherid = :otherid_2)) AS anon_1" ) + self.assert_compile( + select([exists([1])]), + "SELECT EXISTS (SELECT 1) AS anon_1" + ) + + self.assert_compile( + select([~exists([1])]), + "SELECT NOT (EXISTS (SELECT 1)) AS anon_1" + ) + + self.assert_compile( + select([~(~exists([1]))]), + "SELECT NOT (NOT (EXISTS (SELECT 1))) AS anon_1" + ) + def test_where_subquery(self): s = select([addresses.c.street], addresses.c.user_id == users.c.user_id, correlate=True).alias('s') @@ -2774,48 +2789,6 @@ 'x2': 1, 'y': 2}) - def test_unconsumed_names(self): - t = table("t", column("x"), column("y")) - t2 = table("t2", column("q"), column("z")) - assert_raises_message( - exc.CompileError, - "Unconsumed column names: z", - t.insert().values(x=5, z=5).compile, - ) - assert_raises_message( - exc.CompileError, - "Unconsumed column names: z", - t.update().values(x=5, z=5).compile, - ) - - assert_raises_message( - exc.CompileError, - "Unconsumed column names: j", - t.update().values(x=5, j=7).values({t2.c.z: 5}). - where(t.c.x == t2.c.q).compile, - ) - - # bindparam names don't get counted - i = t.insert().values(x=3 + bindparam('x2')) - self.assert_compile( - i, - "INSERT INTO t (x) VALUES ((:param_1 + :x2))" - ) - - # even if in the params list - i = t.insert().values(x=3 + bindparam('x2')) - self.assert_compile( - i, - "INSERT INTO t (x) VALUES ((:param_1 + :x2))", - params={"x2": 1} - ) - - assert_raises_message( - exc.CompileError, - "Unconsumed column names: j", - t.update().values(x=5, j=7).compile, - column_keys=['j'] - ) def test_labels_no_collision(self): @@ -3587,3 +3560,66 @@ (table1.c.description, 'description', 'description'), table1.c.description.type)} ) + + def test_select_wraps_for_translate_ambiguity(self): + # test for issue #3657 + t = table('a', column('x'), column('y'), column('z')) + + l1, l2, l3 = t.c.z.label('a'), t.c.x.label('b'), t.c.x.label('c') + orig = [t.c.x, t.c.y, l1, l2, l3] + stmt = select(orig) + wrapped = stmt._generate() + wrapped = wrapped.column( + func.ROW_NUMBER().over(order_by=t.c.z)).alias() + + wrapped_again = select([c for c in wrapped.c]) + + compiled = wrapped_again.compile( + compile_kwargs={'select_wraps_for': stmt}) + + proxied = [obj[0] for (k, n, obj, type_) in compiled._result_columns] + for orig_obj, proxied_obj in zip( + orig, + proxied + ): + is_(orig_obj, proxied_obj) + + def test_select_wraps_for_translate_ambiguity_dupe_cols(self): + # test for issue #3657 + t = table('a', column('x'), column('y'), column('z')) + + l1, l2, l3 = t.c.z.label('a'), t.c.x.label('b'), t.c.x.label('c') + orig = [t.c.x, t.c.y, l1, l2, l3] + + # create the statement with some duplicate columns. right now + # the behavior is that these redundant columns are deduped. + stmt = select([t.c.x, t.c.y, l1, t.c.y, l2, t.c.x, l3]) + + # so the statement has 7 inner columns... + eq_(len(list(stmt.inner_columns)), 7) + + # but only exposes 5 of them, the other two are dupes of x and y + eq_(len(stmt.c), 5) + + # and when it generates a SELECT it will also render only 5 + eq_(len(stmt._columns_plus_names), 5) + + wrapped = stmt._generate() + wrapped = wrapped.column( + func.ROW_NUMBER().over(order_by=t.c.z)).alias() + + # so when we wrap here we're going to have only 5 columns + wrapped_again = select([c for c in wrapped.c]) + + # so the compiler logic that matches up the "wrapper" to the + # "select_wraps_for" can't use inner_columns to match because + # these collections are not the same + compiled = wrapped_again.compile( + compile_kwargs={'select_wraps_for': stmt}) + + proxied = [obj[0] for (k, n, obj, type_) in compiled._result_columns] + for orig_obj, proxied_obj in zip( + orig, + proxied + ): + is_(orig_obj, proxied_obj) diff -Nru sqlalchemy-1.0.12+ds1/test/sql/test_defaults.py sqlalchemy-1.0.14+ds1/test/sql/test_defaults.py --- sqlalchemy-1.0.12+ds1/test/sql/test_defaults.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/test/sql/test_defaults.py 2016-07-06 21:09:34.000000000 +0000 @@ -774,7 +774,8 @@ testing.db.execute(dataset_no_autoinc.insert()) eq_( - testing.db.scalar(dataset_no_autoinc.count()), 1 + testing.db.scalar( + select([func.count('*')]).select_from(dataset_no_autoinc)), 1 ) diff -Nru sqlalchemy-1.0.12+ds1/test/sql/test_generative.py sqlalchemy-1.0.14+ds1/test/sql/test_generative.py --- sqlalchemy-1.0.12+ds1/test/sql/test_generative.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/test/sql/test_generative.py 2016-07-06 21:09:34.000000000 +0000 @@ -475,6 +475,23 @@ "FROM table3 AS table3_1" ) + def test_cte_w_union(self): + t = select([func.values(1).label("n")]).cte("t", recursive=True) + t = t.union_all(select([t.c.n + 1]).where(t.c.n < 100)) + s = select([func.sum(t.c.n)]) + + from sqlalchemy.sql.visitors import cloned_traverse + cloned = cloned_traverse(s, {}, {}) + + self.assert_compile(cloned, + "WITH RECURSIVE t(n) AS " + "(SELECT values(:values_1) AS n " + "UNION ALL SELECT t.n + :n_1 AS anon_1 " + "FROM t " + "WHERE t.n < :n_2) " + "SELECT sum(t.n) AS sum_1 FROM t" + ) + def test_text(self): clause = text( "select * from table where foo=:bar", diff -Nru sqlalchemy-1.0.12+ds1/test/sql/test_insert.py sqlalchemy-1.0.14+ds1/test/sql/test_insert.py --- sqlalchemy-1.0.12+ds1/test/sql/test_insert.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/test/sql/test_insert.py 2016-07-06 21:09:34.000000000 +0000 @@ -1,14 +1,13 @@ #! coding:utf-8 from sqlalchemy import Column, Integer, MetaData, String, Table,\ - bindparam, exc, func, insert, select, column, text + bindparam, exc, func, insert, select, column, text, table from sqlalchemy.dialects import mysql, postgresql from sqlalchemy.engine import default from sqlalchemy.testing import AssertsCompiledSQL,\ assert_raises_message, fixtures, eq_ from sqlalchemy.sql import crud - class _InsertTestBase(object): @classmethod @@ -55,6 +54,69 @@ 'INSERT INTO mytable (myid, name) VALUES (:myid, :name)', checkparams=checkparams) + def test_unconsumed_names_kwargs(self): + t = table("t", column("x"), column("y")) + assert_raises_message( + exc.CompileError, + "Unconsumed column names: z", + t.insert().values(x=5, z=5).compile, + ) + + def test_bindparam_name_no_consume_error(self): + t = table("t", column("x"), column("y")) + # bindparam names don't get counted + i = t.insert().values(x=3 + bindparam('x2')) + self.assert_compile( + i, + "INSERT INTO t (x) VALUES ((:param_1 + :x2))" + ) + + # even if in the params list + i = t.insert().values(x=3 + bindparam('x2')) + self.assert_compile( + i, + "INSERT INTO t (x) VALUES ((:param_1 + :x2))", + params={"x2": 1} + ) + + def test_unconsumed_names_values_dict(self): + table1 = self.tables.mytable + + checkparams = { + 'myid': 3, + 'name': 'jack', + 'unknowncol': 'oops' + } + + stmt = insert(table1, values=checkparams) + assert_raises_message( + exc.CompileError, + 'Unconsumed column names: unknowncol', + stmt.compile, + dialect=postgresql.dialect() + ) + + def test_unconsumed_names_multi_values_dict(self): + table1 = self.tables.mytable + + checkparams = [{ + 'myid': 3, + 'name': 'jack', + 'unknowncol': 'oops' + }, { + 'myid': 4, + 'name': 'someone', + 'unknowncol': 'oops' + }] + + stmt = insert(table1, values=checkparams) + assert_raises_message( + exc.CompileError, + 'Unconsumed column names: unknowncol', + stmt.compile, + dialect=postgresql.dialect() + ) + def test_insert_with_values_tuple(self): table1 = self.tables.mytable diff -Nru sqlalchemy-1.0.12+ds1/test/sql/test_metadata.py sqlalchemy-1.0.14+ds1/test/sql/test_metadata.py --- sqlalchemy-1.0.12+ds1/test/sql/test_metadata.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/test/sql/test_metadata.py 2016-07-06 21:09:34.000000000 +0000 @@ -7,7 +7,7 @@ CheckConstraint, ForeignKey, MetaData, Sequence, \ ForeignKeyConstraint, PrimaryKeyConstraint, ColumnDefault, Index, event,\ events, Unicode, types as sqltypes, bindparam, \ - Table, Column, Boolean, Enum, func, text + Table, Column, Boolean, Enum, func, text, BLANK_SCHEMA from sqlalchemy import schema, exc from sqlalchemy.sql import elements, naming import sqlalchemy as tsa @@ -21,18 +21,6 @@ class MetaDataTest(fixtures.TestBase, ComparesTables): - def test_metadata_connect(self): - metadata = MetaData() - t1 = Table('table1', metadata, - Column('col1', Integer, primary_key=True), - Column('col2', String(20))) - metadata.bind = testing.db - metadata.create_all() - try: - assert t1.count().scalar() == 0 - finally: - metadata.drop_all() - def test_metadata_contains(self): metadata = MetaData() t1 = Table('t1', metadata, Column('x', Integer)) @@ -445,6 +433,7 @@ ('t2', m1, 'sch2', None, 'sch2', None), ('t3', m1, 'sch2', True, 'sch2', True), ('t4', m1, 'sch1', None, 'sch1', None), + ('t5', m1, BLANK_SCHEMA, None, None, None), ('t1', m2, None, None, 'sch1', True), ('t2', m2, 'sch2', None, 'sch2', None), ('t3', m2, 'sch2', True, 'sch2', True), @@ -457,6 +446,7 @@ ('t2', m4, 'sch2', None, 'sch2', None), ('t3', m4, 'sch2', True, 'sch2', True), ('t4', m4, 'sch1', None, 'sch1', None), + ('t5', m4, BLANK_SCHEMA, None, None, None), ]): kw = {} if schema is not None: @@ -1109,6 +1099,34 @@ eq_(str(table_c.join(table2_c).onclause), 'mytable.myid = othertable.myid') + def test_unique_true_flag(self): + meta = MetaData() + + table = Table('mytable', meta, Column('x', Integer, unique=True)) + + m2 = MetaData() + + t2 = table.tometadata(m2) + + eq_( + len([ + const for const + in t2.constraints + if isinstance(const, UniqueConstraint)]), + 1 + ) + + def test_index_true_flag(self): + meta = MetaData() + + table = Table('mytable', meta, Column('x', Integer, index=True)) + + m2 = MetaData() + + t2 = table.tometadata(m2) + + eq_(len(t2.indexes), 1) + class InfoTest(fixtures.TestBase): def test_metadata_info(self): diff -Nru sqlalchemy-1.0.12+ds1/test/sql/test_operators.py sqlalchemy-1.0.14+ds1/test/sql/test_operators.py --- sqlalchemy-1.0.12+ds1/test/sql/test_operators.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/test/sql/test_operators.py 2016-07-06 21:09:34.000000000 +0000 @@ -1573,6 +1573,12 @@ "SELECT mytable.myid, mytable.name FROM " "mytable WHERE mytable.myid != :myid_1 AND NOT mytable.name") + def test_negate_operator_type(self): + is_( + (-self.table1.c.myid).type, + self.table1.c.myid.type, + ) + class LikeTest(fixtures.TestBase, testing.AssertsCompiledSQL): __dialect__ = 'default' diff -Nru sqlalchemy-1.0.12+ds1/test/sql/test_resultset.py sqlalchemy-1.0.14+ds1/test/sql/test_resultset.py --- sqlalchemy-1.0.12+ds1/test/sql/test_resultset.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/test/sql/test_resultset.py 2016-07-06 21:09:34.000000000 +0000 @@ -1,15 +1,19 @@ from sqlalchemy.testing import eq_, assert_raises_message, assert_raises, \ - in_, not_in_, is_, ne_ + in_, not_in_, is_, ne_, le_ from sqlalchemy import testing from sqlalchemy.testing import fixtures, engines from sqlalchemy import util from sqlalchemy import ( exc, sql, func, select, String, Integer, MetaData, ForeignKey, VARCHAR, INT, CHAR, text, type_coerce, literal_column, - TypeDecorator, table, column) + TypeDecorator, table, column, literal) from sqlalchemy.engine import result as _result from sqlalchemy.testing.schema import Table, Column import operator +from sqlalchemy.engine import default +from contextlib import contextmanager +from sqlalchemy import exc as sa_exc +from sqlalchemy.testing.mock import Mock, patch class ResultProxyTest(fixtures.TablesTest): @@ -203,7 +207,8 @@ lambda: result[0][addresses.c.address_id]) def test_column_error_printing(self): - row = testing.db.execute(select([1])).first() + result = testing.db.execute(select([1])) + row = result.first() class unprintable(object): @@ -221,6 +226,14 @@ assert_raises_message( exc.NoSuchColumnError, msg % repl, + result._getter, accessor + ) + + is_(result._getter(accessor, False), None) + + assert_raises_message( + exc.NoSuchColumnError, + msg % repl, lambda: row[accessor] ) @@ -695,6 +708,21 @@ ) @testing.requires.duplicate_names_in_cursor_description + def test_ambiguous_column_case_sensitive(self): + eng = engines.testing_engine(options=dict(case_sensitive=False)) + + row = eng.execute(select([ + literal_column('1').label('SOMECOL'), + literal_column('1').label('SOMECOL'), + ])).first() + + assert_raises_message( + exc.InvalidRequestError, + "Ambiguous column name", + lambda: row['somecol'] + ) + + @testing.requires.duplicate_names_in_cursor_description def test_ambiguous_column_contains(self): users = self.tables.users addresses = self.tables.addresses @@ -925,6 +953,156 @@ eq_(r['_parent'], 'Hidden parent') eq_(r['_row'], 'Hidden row') + def test_nontuple_row(self): + """ensure the C version of BaseRowProxy handles + duck-type-dependent rows.""" + + from sqlalchemy.engine import RowProxy + + class MyList(object): + + def __init__(self, l): + self.l = l + + def __len__(self): + return len(self.l) + + def __getitem__(self, i): + return list.__getitem__(self.l, i) + + proxy = RowProxy(object(), MyList(['value']), [None], { + 'key': (None, None, 0), 0: (None, None, 0)}) + eq_(list(proxy), ['value']) + eq_(proxy[0], 'value') + eq_(proxy['key'], 'value') + + @testing.provide_metadata + def test_no_rowcount_on_selects_inserts(self): + """assert that rowcount is only called on deletes and updates. + + This because cursor.rowcount may can be expensive on some dialects + such as Firebird, however many dialects require it be called + before the cursor is closed. + + """ + + metadata = self.metadata + + engine = engines.testing_engine() + + t = Table('t1', metadata, + Column('data', String(10)) + ) + metadata.create_all(engine) + + with patch.object( + engine.dialect.execution_ctx_cls, "rowcount") as mock_rowcount: + mock_rowcount.__get__ = Mock() + engine.execute(t.insert(), + {'data': 'd1'}, + {'data': 'd2'}, + {'data': 'd3'}) + + eq_(len(mock_rowcount.__get__.mock_calls), 0) + + eq_( + engine.execute(t.select()).fetchall(), + [('d1', ), ('d2', ), ('d3', )] + ) + eq_(len(mock_rowcount.__get__.mock_calls), 0) + + engine.execute(t.update(), {'data': 'd4'}) + + eq_(len(mock_rowcount.__get__.mock_calls), 1) + + engine.execute(t.delete()) + eq_(len(mock_rowcount.__get__.mock_calls), 2) + + def test_rowproxy_is_sequence(self): + import collections + from sqlalchemy.engine import RowProxy + + row = RowProxy( + object(), ['value'], [None], + {'key': (None, None, 0), 0: (None, None, 0)}) + assert isinstance(row, collections.Sequence) + + @testing.requires.cextensions + def test_row_c_sequence_check(self): + import csv + + metadata = MetaData() + metadata.bind = 'sqlite://' + users = Table('users', metadata, + Column('id', Integer, primary_key=True), + Column('name', String(40)), + ) + users.create() + + users.insert().execute(name='Test') + row = users.select().execute().fetchone() + + s = util.StringIO() + writer = csv.writer(s) + # csv performs PySequenceCheck call + writer.writerow(row) + assert s.getvalue().strip() == '1,Test' + + @testing.requires.selectone + def test_empty_accessors(self): + statements = [ + ( + "select 1", + [ + lambda r: r.last_inserted_params(), + lambda r: r.last_updated_params(), + lambda r: r.prefetch_cols(), + lambda r: r.postfetch_cols(), + lambda r: r.inserted_primary_key + ], + "Statement is not a compiled expression construct." + ), + ( + select([1]), + [ + lambda r: r.last_inserted_params(), + lambda r: r.inserted_primary_key + ], + r"Statement is not an insert\(\) expression construct." + ), + ( + select([1]), + [ + lambda r: r.last_updated_params(), + ], + r"Statement is not an update\(\) expression construct." + ), + ( + select([1]), + [ + lambda r: r.prefetch_cols(), + lambda r: r.postfetch_cols() + ], + r"Statement is not an insert\(\) " + r"or update\(\) expression construct." + ), + ] + + for stmt, meths, msg in statements: + r = testing.db.execute(stmt) + try: + for meth in meths: + assert_raises_message( + sa_exc.InvalidRequestError, + msg, + meth, r + ) + + finally: + r.close() + + + class KeyTargetingTest(fixtures.TablesTest): run_inserts = 'once' @@ -1148,3 +1326,201 @@ in_(keyed2.c.b, row) in_(stmt.c.keyed2_a, row) in_(stmt.c.keyed2_b, row) + + +class AlternateResultProxyTest(fixtures.TablesTest): + __requires__ = ('sqlite', ) + + @classmethod + def setup_bind(cls): + cls.engine = engine = engines.testing_engine('sqlite://') + return engine + + @classmethod + def define_tables(cls, metadata): + Table( + 'test', metadata, + Column('x', Integer, primary_key=True), + Column('y', String(50, convert_unicode='force')) + ) + + @classmethod + def insert_data(cls): + cls.engine.execute(cls.tables.test.insert(), [ + {'x': i, 'y': "t_%d" % i} for i in range(1, 12) + ]) + + @contextmanager + def _proxy_fixture(self, cls): + self.table = self.tables.test + + class ExcCtx(default.DefaultExecutionContext): + + def get_result_proxy(self): + return cls(self) + self.patcher = patch.object( + self.engine.dialect, "execution_ctx_cls", ExcCtx) + with self.patcher: + yield + + def _test_proxy(self, cls): + with self._proxy_fixture(cls): + rows = [] + r = self.engine.execute(select([self.table])) + assert isinstance(r, cls) + for i in range(5): + rows.append(r.fetchone()) + eq_(rows, [(i, "t_%d" % i) for i in range(1, 6)]) + + rows = r.fetchmany(3) + eq_(rows, [(i, "t_%d" % i) for i in range(6, 9)]) + + rows = r.fetchall() + eq_(rows, [(i, "t_%d" % i) for i in range(9, 12)]) + + r = self.engine.execute(select([self.table])) + rows = r.fetchmany(None) + eq_(rows[0], (1, "t_1")) + # number of rows here could be one, or the whole thing + assert len(rows) == 1 or len(rows) == 11 + + r = self.engine.execute(select([self.table]).limit(1)) + r.fetchone() + eq_(r.fetchone(), None) + + r = self.engine.execute(select([self.table]).limit(5)) + rows = r.fetchmany(6) + eq_(rows, [(i, "t_%d" % i) for i in range(1, 6)]) + + # result keeps going just fine with blank results... + eq_(r.fetchmany(2), []) + + eq_(r.fetchmany(2), []) + + eq_(r.fetchall(), []) + + eq_(r.fetchone(), None) + + # until we close + r.close() + + self._assert_result_closed(r) + + r = self.engine.execute(select([self.table]).limit(5)) + eq_(r.first(), (1, "t_1")) + self._assert_result_closed(r) + + r = self.engine.execute(select([self.table]).limit(5)) + eq_(r.scalar(), 1) + self._assert_result_closed(r) + + def _assert_result_closed(self, r): + assert_raises_message( + sa_exc.ResourceClosedError, + "object is closed", + r.fetchone + ) + + assert_raises_message( + sa_exc.ResourceClosedError, + "object is closed", + r.fetchmany, 2 + ) + + assert_raises_message( + sa_exc.ResourceClosedError, + "object is closed", + r.fetchall + ) + + def test_basic_plain(self): + self._test_proxy(_result.ResultProxy) + + def test_basic_buffered_row_result_proxy(self): + self._test_proxy(_result.BufferedRowResultProxy) + + def test_basic_fully_buffered_result_proxy(self): + self._test_proxy(_result.FullyBufferedResultProxy) + + def test_basic_buffered_column_result_proxy(self): + self._test_proxy(_result.BufferedColumnResultProxy) + + def test_resultprocessor_plain(self): + self._test_result_processor(_result.ResultProxy, False) + + def test_resultprocessor_plain_cached(self): + self._test_result_processor(_result.ResultProxy, True) + + def test_resultprocessor_buffered_column(self): + self._test_result_processor(_result.BufferedColumnResultProxy, False) + + def test_resultprocessor_buffered_column_cached(self): + self._test_result_processor(_result.BufferedColumnResultProxy, True) + + def test_resultprocessor_buffered_row(self): + self._test_result_processor(_result.BufferedRowResultProxy, False) + + def test_resultprocessor_buffered_row_cached(self): + self._test_result_processor(_result.BufferedRowResultProxy, True) + + def test_resultprocessor_fully_buffered(self): + self._test_result_processor(_result.FullyBufferedResultProxy, False) + + def test_resultprocessor_fully_buffered_cached(self): + self._test_result_processor(_result.FullyBufferedResultProxy, True) + + def _test_result_processor(self, cls, use_cache): + class MyType(TypeDecorator): + impl = String() + + def process_result_value(self, value, dialect): + return "HI " + value + + with self._proxy_fixture(cls): + with self.engine.connect() as conn: + if use_cache: + cache = {} + conn = conn.execution_options(compiled_cache=cache) + + stmt = select([literal("THERE", type_=MyType())]) + for i in range(2): + r = conn.execute(stmt) + eq_(r.scalar(), "HI THERE") + + def test_buffered_row_growth(self): + with self._proxy_fixture(_result.BufferedRowResultProxy): + with self.engine.connect() as conn: + conn.execute(self.table.insert(), [ + {'x': i, 'y': "t_%d" % i} for i in range(15, 1200) + ]) + result = conn.execute(self.table.select()) + checks = { + 0: 5, 1: 10, 9: 20, 135: 250, 274: 500, + 1351: 1000 + } + for idx, row in enumerate(result, 0): + if idx in checks: + eq_(result._bufsize, checks[idx]) + le_( + len(result._BufferedRowResultProxy__rowbuffer), + 1000 + ) + + def test_max_row_buffer_option(self): + with self._proxy_fixture(_result.BufferedRowResultProxy): + with self.engine.connect() as conn: + conn.execute(self.table.insert(), [ + {'x': i, 'y': "t_%d" % i} for i in range(15, 1200) + ]) + result = conn.execution_options(max_row_buffer=27).execute( + self.table.select() + ) + for idx, row in enumerate(result, 0): + if idx in (16, 70, 150, 250): + eq_(result._bufsize, 27) + le_( + len(result._BufferedRowResultProxy__rowbuffer), + 27 + ) + + diff -Nru sqlalchemy-1.0.12+ds1/test/sql/test_selectable.py sqlalchemy-1.0.14+ds1/test/sql/test_selectable.py --- sqlalchemy-1.0.12+ds1/test/sql/test_selectable.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/test/sql/test_selectable.py 2016-07-06 21:09:34.000000000 +0000 @@ -2193,6 +2193,33 @@ [Boolean] ) + def test_plain_exists(self): + expr = exists([1]) + eq_(type(expr.type), Boolean) + eq_( + [type(entry[-1]) for + entry in select([expr]).compile()._result_columns], + [Boolean] + ) + + def test_plain_exists_negate(self): + expr = ~exists([1]) + eq_(type(expr.type), Boolean) + eq_( + [type(entry[-1]) for + entry in select([expr]).compile()._result_columns], + [Boolean] + ) + + def test_plain_exists_double_negate(self): + expr = ~(~exists([1])) + eq_(type(expr.type), Boolean) + eq_( + [type(entry[-1]) for + entry in select([expr]).compile()._result_columns], + [Boolean] + ) + def test_column_subquery_plain(self): t = self._fixture() s1 = select([t.c.x]).where(t.c.x > 5).as_scalar() diff -Nru sqlalchemy-1.0.12+ds1/test/sql/test_types.py sqlalchemy-1.0.14+ds1/test/sql/test_types.py --- sqlalchemy-1.0.12+ds1/test/sql/test_types.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/test/sql/test_types.py 2016-07-06 21:09:34.000000000 +0000 @@ -1247,8 +1247,8 @@ data = os.urandom(32) binary_table.insert().execute(data=data) eq_( - binary_table.select().where(binary_table.c.data == data).alias(). - count().scalar(), 1) + select([func.count('*')]).select_from(binary_table). + where(binary_table.c.data == data).scalar(), 1) @testing.requires.binary_literals def test_literal_roundtrip(self): diff -Nru sqlalchemy-1.0.12+ds1/test/sql/test_update.py sqlalchemy-1.0.14+ds1/test/sql/test_update.py --- sqlalchemy-1.0.12+ds1/test/sql/test_update.py 2016-02-15 17:27:54.000000000 +0000 +++ sqlalchemy-1.0.14+ds1/test/sql/test_update.py 2016-07-06 21:09:34.000000000 +0000 @@ -1,8 +1,10 @@ -from sqlalchemy import * +from sqlalchemy import Integer, String, ForeignKey, and_, or_, func, \ + literal, update, table, bindparam, column, select, exc from sqlalchemy import testing from sqlalchemy.dialects import mysql from sqlalchemy.engine import default -from sqlalchemy.testing import AssertsCompiledSQL, eq_, fixtures +from sqlalchemy.testing import AssertsCompiledSQL, eq_, fixtures, \ + assert_raises_message from sqlalchemy.testing.schema import Table, Column from sqlalchemy import util @@ -188,6 +190,36 @@ 'mytable.myid = hoho(:hoho_1) AND ' 'mytable.name = :param_2 || mytable.name || :param_3') + def test_unconsumed_names_kwargs(self): + t = table("t", column("x"), column("y")) + + assert_raises_message( + exc.CompileError, + "Unconsumed column names: z", + t.update().values(x=5, z=5).compile, + ) + + def test_unconsumed_names_values_dict(self): + t = table("t", column("x"), column("y")) + t2 = table("t2", column("q"), column("z")) + + assert_raises_message( + exc.CompileError, + "Unconsumed column names: j", + t.update().values(x=5, j=7).values({t2.c.z: 5}). + where(t.c.x == t2.c.q).compile, + ) + + def test_unconsumed_names_kwargs_w_keys(self): + t = table("t", column("x"), column("y")) + + assert_raises_message( + exc.CompileError, + "Unconsumed column names: j", + t.update().values(x=5, j=7).compile, + column_keys=['j'] + ) + def test_update_ordered_parameters_1(self): table1 = self.tables.mytable