[Python-modules-commits] [sqlalchemy] 02/06: Import sqlalchemy_1.1.3+ds1.orig.tar.gz
Piotr Ożarowski
piotr at moszumanska.debian.org
Sat Nov 5 22:31:43 UTC 2016
This is an automated email from the git hooks/post-receive script.
piotr pushed a commit to branch master
in repository sqlalchemy.
commit ea616a8a0fe33469561d26ce6adb5086ca68ccb9
Author: Piotr Ożarowski <piotr at debian.org>
Date: Sat Nov 5 22:07:16 2016 +0100
Import sqlalchemy_1.1.3+ds1.orig.tar.gz
---
PKG-INFO | 2 +-
doc/build/changelog/changelog_11.rst | 63 +++++++++++++++
doc/build/changelog/migration_11.rst | 122 +++++++++++++++++++++++------
doc/build/conf.py | 4 +-
lib/sqlalchemy/__init__.py | 2 +-
lib/sqlalchemy/dialects/postgresql/base.py | 3 +-
lib/sqlalchemy/ext/hybrid.py | 4 +-
lib/sqlalchemy/orm/persistence.py | 6 ++
lib/sqlalchemy/orm/query.py | 7 +-
lib/sqlalchemy/orm/session.py | 9 ++-
lib/sqlalchemy/orm/unitofwork.py | 3 +
lib/sqlalchemy/sql/base.py | 2 -
lib/sqlalchemy/sql/sqltypes.py | 20 +++++
lib/sqlalchemy/sql/type_api.py | 11 +++
test/dialect/postgresql/test_reflection.py | 16 ++++
test/orm/test_query.py | 30 +++++++
test/orm/test_session.py | 33 +++++++-
test/orm/test_unitofworkv2.py | 20 +++++
test/sql/test_metadata.py | 67 ++++++++++++++++
test/sql/test_types.py | 19 +++++
20 files changed, 406 insertions(+), 37 deletions(-)
diff --git a/PKG-INFO b/PKG-INFO
index c5de66b..612c0e7 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: SQLAlchemy
-Version: 1.1.2
+Version: 1.1.3
Summary: Database Abstraction Library
Home-page: http://www.sqlalchemy.org
Author: Mike Bayer
diff --git a/doc/build/changelog/changelog_11.rst b/doc/build/changelog/changelog_11.rst
index ea14432..9c43374 100644
--- a/doc/build/changelog/changelog_11.rst
+++ b/doc/build/changelog/changelog_11.rst
@@ -19,6 +19,69 @@
:start-line: 5
.. changelog::
+ :version: 1.1.3
+ :released: October 27, 2016
+
+ .. change::
+ :tags: bug, orm
+ :tickets: 3839
+
+ Fixed regression caused by :ticket:`2677` whereby calling
+ :meth:`.Session.delete` on an object that was already flushed as
+ deleted in that session would fail to set up the object in the
+ identity map (or reject the object), causing flush errors as the
+ object were in a state not accommodated by the unit of work.
+ The pre-1.1 behavior in this case has been restored, which is that
+ the object is put back into the identity map so that the DELETE
+ statement will be attempted again, which emits a warning that the number
+ of expected rows was not matched (unless the row were restored outside
+ of the session).
+
+ .. change::
+ :tags: bug, postgresql
+ :tickets: 3835
+
+ Postgresql table reflection will ensure that the
+ :paramref:`.Column.autoincrement` flag is set to False when reflecting
+ a primary key column that is not of an :class:`.Integer` datatype,
+ even if the default is related to an integer-generating sequence.
+ This can happen if a column is created as SERIAL and the datatype
+ is changed. The autoincrement flag can only be True if the datatype
+ is of integer affinity in the 1.1 series.
+
+ .. change::
+ :tags: bug, orm
+ :tickets: 3836
+
+ Fixed regression where some :class:`.Query` methods like
+ :meth:`.Query.update` and others would fail if the :class:`.Query`
+ were against a series of mapped columns, rather than the mapped
+ entity as a whole.
+
+ .. change::
+ :tags: bug, sql
+ :tickets: 3833
+
+ Fixed bug involving new value translation and validation feature
+ in :class:`.Enum` whereby using the enum object in a string
+ concatenation would maintain the :class:`.Enum` type as the type
+ of the expression overall, producing missing lookups. A string
+ concatenation against an :class:`.Enum`-typed column now uses
+ :class:`.String` as the datatype of the expression itself.
+
+ .. change::
+ :tags: bug, sql
+ :tickets: 3832
+
+ Fixed regression which occurred as a side effect of :ticket:`2919`,
+ which in the less typical case of a user-defined
+ :class:`.TypeDecorator` that was also itself an instance of
+ :class:`.SchemaType` (rather than the implementation being such)
+ would cause the column attachment events to be skipped for the
+ type itself.
+
+
+.. changelog::
:version: 1.1.2
:released: October 17, 2016
diff --git a/doc/build/changelog/migration_11.rst b/doc/build/changelog/migration_11.rst
index eddb203..04bdd72 100644
--- a/doc/build/changelog/migration_11.rst
+++ b/doc/build/changelog/migration_11.rst
@@ -1528,6 +1528,13 @@ string values::
e.execute(t.insert(), {"value": MyEnum.two})
assert e.scalar(t.select()) is MyEnum.two
+The ``Enum.enums`` collection is now a list instead of a tuple
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+As part of the changes to :class:`.Enum`, the :attr:`.Enum.enums` collection
+of elements is now a list instead of a tuple. This because lists
+are appropriate for variable length sequences of homogeneous items where
+the position of the element is not semantically significant.
:ticket:`3292`
@@ -1676,30 +1683,64 @@ NULL values as well as expression handling.
.. _change_3514:
-JSON "null" is inserted as expected with ORM operations, regardless of column default present
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+JSON "null" is inserted as expected with ORM operations, omitted when not present
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The :class:`.types.JSON` type and its descendant types :class:`.postgresql.JSON`
and :class:`.mysql.JSON` have a flag :paramref:`.types.JSON.none_as_null` which
when set to True indicates that the Python value ``None`` should translate
into a SQL NULL rather than a JSON NULL value. This flag defaults to False,
-which means that the column should *never* insert SQL NULL or fall back
-to a default unless the :func:`.null` constant were used. However, this would
-fail in the ORM under two circumstances; one is when the column also contained
-a default or server_default value, a positive value of ``None`` on the mapped
-attribute would still result in the column-level default being triggered,
+which means that the Python value ``None`` should result in a JSON NULL value.
+
+This logic would fail, and is now corrected, in the following circumstances:
+
+1. When the column also contained a default or server_default value,
+a positive value of ``None`` on the mapped attribute that expects to persist
+JSON "null" would still result in the column-level default being triggered,
replacing the ``None`` value::
+ class MyObject(Base):
+ # ...
+
+ json_value = Column(JSON(none_as_null=False), default="some default")
+
+ # would insert "some default" instead of "'null'",
+ # now will insert "'null'"
obj = MyObject(json_value=None)
session.add(obj)
- session.commit() # would fire off default / server_default, not encode "'none'"
+ session.commit()
-The other is when the :meth:`.Session.bulk_insert_mappings`
-method were used, ``None`` would be ignored in all cases::
+2. When the column *did not* contain a default or server_default value, a missing
+value on a JSON column configured with none_as_null=False would still render
+JSON NULL rather than falling back to not inserting any value, behaving
+inconsistently vs. all other datatypes::
+ class MyObject(Base):
+ # ...
+
+ some_other_value = Column(String(50))
+ json_value = Column(JSON(none_as_null=False))
+
+ # would result in NULL for some_other_value,
+ # but json "'null'" for json_value. Now results in NULL for both
+ # (the json_value is omitted from the INSERT)
+ obj = MyObject()
+ session.add(obj)
+ session.commit()
+
+This is a behavioral change that is backwards incompatible for an application
+that was relying upon this to default a missing value as JSON null. This
+essentially establishes that a **missing value is distinguished from a present
+value of None**. See :ref:`behavior_change_3514` for further detail.
+
+3. When the :meth:`.Session.bulk_insert_mappings` method were used, ``None``
+would be ignored in all cases::
+
+ # would insert SQL NULL and/or trigger defaults,
+ # now inserts "'null'"
session.bulk_insert_mappings(
MyObject,
- [{"json_value": None}]) # would insert SQL NULL and/or trigger defaults
+ [{"json_value": None}])
The :class:`.types.JSON` type now implements the
:attr:`.TypeEngine.should_evaluate_none` flag,
@@ -1708,18 +1749,6 @@ automatically based on the value of :paramref:`.types.JSON.none_as_null`.
Thanks to :ticket:`3061`, we can differentiate when the value ``None`` is actively
set by the user versus when it was never set at all.
-If the attribute is not set at all, then column level defaults *will*
-fire off and/or SQL NULL will be inserted as expected, as was the behavior
-previously. Below, the two variants are illustrated::
-
- obj = MyObject(json_value=None)
- session.add(obj)
- session.commit() # *will not* fire off column defaults, will insert JSON 'null'
-
- obj = MyObject()
- session.add(obj)
- session.commit() # *will* fire off column defaults, and/or insert SQL NULL
-
The feature applies as well to the new base :class:`.types.JSON` type
and its descendant types.
@@ -2063,6 +2092,53 @@ as intended by the :func:`.type_coerce` function.
Key Behavioral Changes - ORM
============================
+.. _behavior_change_3514:
+
+JSON Columns will not insert JSON NULL if no value is supplied and no default is established
+--------------------------------------------------------------------------------------------
+
+As detailed in :ref:`change_3514`, :class:`.types.JSON` will not render
+a JSON "null" value if the value is missing entirely. To prevent SQL NULL,
+a default should be set up. Given the following mapping::
+
+ class MyObject(Base):
+ # ...
+
+ json_value = Column(JSON(none_as_null=False), nullable=False)
+
+The following flush operation will fail with an integrity error::
+
+ obj = MyObject() # note no json_value
+ session.add(obj)
+ session.commit() # will fail with integrity error
+
+If the default for the column should be JSON NULL, set this on the
+Column::
+
+ class MyObject(Base):
+ # ...
+
+ json_value = Column(
+ JSON(none_as_null=False), nullable=False, default=JSON.NULL)
+
+Or, ensure the value is present on the object::
+
+ obj = MyObject(json_value=None)
+ session.add(obj)
+ session.commit() # will insert JSON NULL
+
+Note that setting ``None`` for the default is the same as omitting it entirely;
+the :paramref:`.types.JSON.none_as_null` flag does not impact the value of ``None``
+passed to :paramref:`.Column.default` or :paramref:`.Column.server_default`::
+
+ # default=None is the same as omitting it entirely, does not apply JSON NULL
+ json_value = Column(JSON(none_as_null=False), nullable=False, default=None)
+
+
+.. seealso::
+
+ :ref:`change_3514`
+
.. _change_3641:
Columns no longer added redundantly with DISTINCT + ORDER BY
diff --git a/doc/build/conf.py b/doc/build/conf.py
index 9050b53..f5785a7 100644
--- a/doc/build/conf.py
+++ b/doc/build/conf.py
@@ -107,9 +107,9 @@ copyright = u'2007-2016, the SQLAlchemy authors and contributors'
# The short X.Y version.
version = "1.1"
# The full version, including alpha/beta/rc tags.
-release = "1.1.2"
+release = "1.1.3"
-release_date = "October 17, 2016"
+release_date = "October 27, 2016"
site_base = os.environ.get("RTD_SITE_BASE", "http://www.sqlalchemy.org")
site_adapter_template = "docs_adapter.mako"
diff --git a/lib/sqlalchemy/__init__.py b/lib/sqlalchemy/__init__.py
index 74e7685..ca681a0 100644
--- a/lib/sqlalchemy/__init__.py
+++ b/lib/sqlalchemy/__init__.py
@@ -128,7 +128,7 @@ from .schema import (
from .inspection import inspect
from .engine import create_engine, engine_from_config
-__version__ = '1.1.2'
+__version__ = '1.1.3'
def __go(lcls):
diff --git a/lib/sqlalchemy/dialects/postgresql/base.py b/lib/sqlalchemy/dialects/postgresql/base.py
index 85f82ec..9898e4b 100644
--- a/lib/sqlalchemy/dialects/postgresql/base.py
+++ b/lib/sqlalchemy/dialects/postgresql/base.py
@@ -2442,7 +2442,8 @@ class PGDialect(default.DefaultDialect):
if default is not None:
match = re.search(r"""(nextval\(')([^']+)('.*$)""", default)
if match is not None:
- autoincrement = True
+ if issubclass(coltype._type_affinity, sqltypes.Integer):
+ autoincrement = True
# the default is related to a Sequence
sch = schema
if '.' not in match.group(2) and sch is not None:
diff --git a/lib/sqlalchemy/ext/hybrid.py b/lib/sqlalchemy/ext/hybrid.py
index 99f938e..90e4818 100644
--- a/lib/sqlalchemy/ext/hybrid.py
+++ b/lib/sqlalchemy/ext/hybrid.py
@@ -188,7 +188,7 @@ Working with Relationships
There's no essential difference when creating hybrids that work with
related objects as opposed to column-based data. The need for distinct
-expressions tends to be greater. Two variants of we'll illustrate
+expressions tends to be greater. The two variants we'll illustrate
are the "join-dependent" hybrid, and the "correlated subquery" hybrid.
Join-Dependent Relationship Hybrid
@@ -505,7 +505,7 @@ into a hierarchical tree pattern::
class Node(Base):
__tablename__ = 'node'
- id =Column(Integer, primary_key=True)
+ id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('node.id'))
parent = relationship("Node", remote_side=id)
diff --git a/lib/sqlalchemy/orm/persistence.py b/lib/sqlalchemy/orm/persistence.py
index 24a33ee..2bc189c 100644
--- a/lib/sqlalchemy/orm/persistence.py
+++ b/lib/sqlalchemy/orm/persistence.py
@@ -396,6 +396,12 @@ def _collect_insert_commands(
params[col.key] = value
if not bulk:
+ # for all the columns that have no default and we don't have
+ # a value and where "None" is not a special value, add
+ # explicit None to the INSERT. This is a legacy behavior
+ # which might be worth removing, as it should not be necessary
+ # and also produces confusion, given that "missing" and None
+ # now have distinct meanings
for colkey in mapper._insert_cols_as_none[table].\
difference(params).difference(value_params):
params[colkey] = None
diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py
index d6a81ff..23d33b0 100644
--- a/lib/sqlalchemy/orm/query.py
+++ b/lib/sqlalchemy/orm/query.py
@@ -165,7 +165,7 @@ class Query(object):
info = inspect(from_obj)
if hasattr(info, 'mapper') and \
(info.is_mapper or info.is_aliased_class):
- self._select_from_entity = from_obj
+ self._select_from_entity = info
if set_base_alias:
raise sa_exc.ArgumentError(
"A selectable (FromClause) instance is "
@@ -3940,8 +3940,10 @@ class _ColumnEntity(_QueryEntity):
self.entity_zero = _entity
if _entity:
self.entities = [_entity]
+ self.mapper = _entity.mapper
else:
self.entities = []
+ self.mapper = None
self._from_entities = set(self.entities)
else:
all_elements = [
@@ -3963,10 +3965,13 @@ class _ColumnEntity(_QueryEntity):
])
if self.entities:
self.entity_zero = self.entities[0]
+ self.mapper = self.entity_zero.mapper
elif self.namespace is not None:
self.entity_zero = self.namespace
+ self.mapper = None
else:
self.entity_zero = None
+ self.mapper = None
supports_single_entity = False
diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py
index b492cbb..b39ba14 100644
--- a/lib/sqlalchemy/orm/session.py
+++ b/lib/sqlalchemy/orm/session.py
@@ -1726,8 +1726,9 @@ class Session(_SessionClassMethods):
if state in self._deleted:
return
+ self.identity_map.add(state)
+
if to_attach:
- self.identity_map.add(state)
self._after_attach(state, obj)
if head:
@@ -2196,7 +2197,8 @@ class Session(_SessionClassMethods):
for state in proc:
is_orphan = (
_state_mapper(state)._is_orphan(state) and state.has_identity)
- flush_context.register_object(state, isdelete=is_orphan)
+ _reg = flush_context.register_object(state, isdelete=is_orphan)
+ assert _reg, "Failed to add object to the flush context!"
processed.add(state)
# put all remaining deletes into the flush context.
@@ -2205,7 +2207,8 @@ class Session(_SessionClassMethods):
else:
proc = deleted.difference(processed)
for state in proc:
- flush_context.register_object(state, isdelete=True)
+ _reg = flush_context.register_object(state, isdelete=True)
+ assert _reg, "Failed to add object to the flush context!"
if not flush_context.has_work:
return
diff --git a/lib/sqlalchemy/orm/unitofwork.py b/lib/sqlalchemy/orm/unitofwork.py
index f3e39d9..de4842d 100644
--- a/lib/sqlalchemy/orm/unitofwork.py
+++ b/lib/sqlalchemy/orm/unitofwork.py
@@ -242,6 +242,9 @@ class UOWTransaction(object):
listonly=False, cancel_delete=False,
operation=None, prop=None):
if not self.session._contains_state(state):
+ # this condition is normal when objects are registered
+ # as part of a relationship cascade operation. it should
+ # not occur for the top-level register from Session.flush().
if not state.deleted and operation is not None:
util.warn("Object of type %s not in session, %s operation "
"along '%s' will not proceed" %
diff --git a/lib/sqlalchemy/sql/base.py b/lib/sqlalchemy/sql/base.py
index cf7dcfd..0b03684 100644
--- a/lib/sqlalchemy/sql/base.py
+++ b/lib/sqlalchemy/sql/base.py
@@ -426,8 +426,6 @@ class SchemaEventTarget(object):
def _set_parent(self, parent):
"""Associate with this SchemaEvent's parent object."""
- raise NotImplementedError()
-
def _set_parent_with_dispatch(self, parent):
self.dispatch.before_parent_attach(self, parent)
self._set_parent(parent)
diff --git a/lib/sqlalchemy/sql/sqltypes.py b/lib/sqlalchemy/sql/sqltypes.py
index 118c260..ef1624f 100644
--- a/lib/sqlalchemy/sql/sqltypes.py
+++ b/lib/sqlalchemy/sql/sqltypes.py
@@ -1296,6 +1296,19 @@ class Enum(String, SchemaType):
raise LookupError(
'"%s" is not among the defined enum values' % elem)
+ class Comparator(String.Comparator):
+
+ def _adapt_expression(self, op, other_comparator):
+ op, typ = super(Enum.Comparator, self)._adapt_expression(
+ op, other_comparator)
+ if op is operators.concat_op:
+ typ = String(
+ self.type.length,
+ convert_unicode=self.type.convert_unicode)
+ return op, typ
+
+ comparator_factory = Comparator
+
def _object_value_for_elem(self, elem):
try:
return self._object_lookup[elem]
@@ -1796,6 +1809,13 @@ class JSON(Indexable, TypeEngine):
from sqlalchemy import null
conn.execute(table.insert(), data=null())
+ .. note::
+
+ :paramref:`.JSON.none_as_null` does **not** apply to the
+ values passed to :paramref:`.Column.default` and
+ :paramref:`.Column.server_default`; a value of ``None`` passed for
+ these parameters means "no default present".
+
.. seealso::
:attr:`.types.JSON.NULL`
diff --git a/lib/sqlalchemy/sql/type_api.py b/lib/sqlalchemy/sql/type_api.py
index 217f701..98ede4e 100644
--- a/lib/sqlalchemy/sql/type_api.py
+++ b/lib/sqlalchemy/sql/type_api.py
@@ -182,6 +182,13 @@ class TypeEngine(Visitable):
the :obj:`~.expression.null` SQL construct in an INSERT statement
or associated with an ORM-mapped attribute.
+ .. note::
+
+ The "evaulates none" flag does **not** apply to a value
+ of ``None`` passed to :paramref:`.Column.default` or
+ :paramref:`.Column.server_default`; in these cases, ``None``
+ still means "no default".
+
.. versionadded:: 1.1
.. seealso::
@@ -853,12 +860,16 @@ class TypeDecorator(SchemaEventTarget, TypeEngine):
def _set_parent(self, column):
"""Support SchemaEentTarget"""
+ super(TypeDecorator, self)._set_parent(column)
+
if isinstance(self.impl, SchemaEventTarget):
self.impl._set_parent(column)
def _set_parent_with_dispatch(self, parent):
"""Support SchemaEentTarget"""
+ super(TypeDecorator, self)._set_parent_with_dispatch(parent)
+
if isinstance(self.impl, SchemaEventTarget):
self.impl._set_parent_with_dispatch(parent)
diff --git a/test/dialect/postgresql/test_reflection.py b/test/dialect/postgresql/test_reflection.py
index 84aeef1..5f9e6df 100644
--- a/test/dialect/postgresql/test_reflection.py
+++ b/test/dialect/postgresql/test_reflection.py
@@ -344,6 +344,22 @@ class ReflectionTest(fixtures.TestBase):
eq_(r.inserted_primary_key, [2])
@testing.provide_metadata
+ def test_altered_type_autoincrement_pk_reflection(self):
+ metadata = self.metadata
+ t = Table(
+ 't', metadata,
+ Column('id', Integer, primary_key=True),
+ Column('x', Integer)
+ )
+ metadata.create_all()
+ testing.db.connect().execution_options(autocommit=True).\
+ execute('alter table t alter column id type varchar(50)')
+ m2 = MetaData(testing.db)
+ t2 = Table('t', m2, autoload=True)
+ eq_(t2.c.id.autoincrement, False)
+ eq_(t2.c.x.autoincrement, False)
+
+ @testing.provide_metadata
def test_renamed_pk_reflection(self):
metadata = self.metadata
t = Table('t', metadata, Column('id', Integer, primary_key=True))
diff --git a/test/orm/test_query.py b/test/orm/test_query.py
index 493f6a7..57408e1 100644
--- a/test/orm/test_query.py
+++ b/test/orm/test_query.py
@@ -813,6 +813,36 @@ class InvalidGenerationsTest(QueryTest, AssertsCompiledSQL):
q = s.query(User, Address)
assert_raises(sa_exc.InvalidRequestError, q.get, 5)
+ def test_entity_or_mapper_zero(self):
+ User, Address = self.classes.User, self.classes.Address
+ s = create_session()
+
+ q = s.query(User, Address)
+ is_(q._mapper_zero(), inspect(User))
+ is_(q._entity_zero(), inspect(User))
+
+ u1 = aliased(User)
+ q = s.query(u1, Address)
+ is_(q._mapper_zero(), inspect(User))
+ is_(q._entity_zero(), inspect(u1))
+
+ q = s.query(User).select_from(Address)
+ is_(q._mapper_zero(), inspect(User))
+ is_(q._entity_zero(), inspect(Address))
+
+ q = s.query(User.name, Address)
+ is_(q._mapper_zero(), inspect(User))
+ is_(q._entity_zero(), inspect(User))
+
+ q = s.query(u1.name, Address)
+ is_(q._mapper_zero(), inspect(User))
+ is_(q._entity_zero(), inspect(u1))
+
+ q1 = s.query(User).exists()
+ q = s.query(q1)
+ is_(q._mapper_zero(), None)
+ is_(q._entity_zero(), None)
+
def test_from_statement(self):
User = self.classes.User
diff --git a/test/orm/test_session.py b/test/orm/test_session.py
index caeb085..24666d0 100644
--- a/test/orm/test_session.py
+++ b/test/orm/test_session.py
@@ -1,5 +1,5 @@
from sqlalchemy.testing import eq_, assert_raises, \
- assert_raises_message
+ assert_raises_message, assertions
from sqlalchemy.testing.util import gc_collect
from sqlalchemy.testing import pickleable
from sqlalchemy.util import pickle
@@ -347,6 +347,37 @@ class SessionStateTest(_fixtures.FixtureTest):
eq_(sess.query(User).count(), 1)
+ def test_deleted_adds_to_imap_unconditionally(self):
+ users, User = self.tables.users, self.classes.User
+
+ mapper(User, users)
+
+ sess = Session()
+ u1 = User(name='u1')
+ sess.add(u1)
+ sess.commit()
+
+ sess.delete(u1)
+ sess.flush()
+
+ # object is not in session
+ assert u1 not in sess
+
+ # but it *is* attached
+ assert u1._sa_instance_state.session_id == sess.hash_key
+
+ # mark as deleted again
+ sess.delete(u1)
+
+ # in the session again
+ assert u1 in sess
+
+ # commit proceeds w/ warning
+ with assertions.expect_warnings(
+ "DELETE statement on table 'users' "
+ r"expected to delete 1 row\(s\); 0 were matched."):
+ sess.commit()
+
def test_autoflush_expressions(self):
"""test that an expression which is dependent on object state is
evaluated after the session autoflushes. This is the lambda
diff --git a/test/orm/test_unitofworkv2.py b/test/orm/test_unitofworkv2.py
index ae8454f..b2fefe6 100644
--- a/test/orm/test_unitofworkv2.py
+++ b/test/orm/test_unitofworkv2.py
@@ -1567,6 +1567,26 @@ class BasicStaleChecksTest(fixtures.MappedTest):
)
@testing.requires.sane_multi_rowcount
+ def test_delete_twice(self):
+ Parent, Child = self._fixture()
+ sess = Session()
+ p1 = Parent(id=1, data=2, child=None)
+ sess.add(p1)
+ sess.commit()
+
+ sess.delete(p1)
+ sess.flush()
+
+ sess.delete(p1)
+
+ assert_raises_message(
+ exc.SAWarning,
+ "DELETE statement on table 'parent' expected to "
+ "delete 1 row\(s\); 0 were matched.",
+ sess.commit
+ )
+
+ @testing.requires.sane_multi_rowcount
def test_delete_multi_missing_warning(self):
Parent, Child = self._fixture()
sess = Session()
diff --git a/test/sql/test_metadata.py b/test/sql/test_metadata.py
index f2df4da..f790c2a 100644
--- a/test/sql/test_metadata.py
+++ b/test/sql/test_metadata.py
@@ -1574,6 +1574,66 @@ class SchemaTypeTest(fixtures.TestBase):
class MyTypeImpl(MyTypeWImpl):
pass
+ class MyTypeDecAndSchema(TypeDecorator, sqltypes.SchemaType):
+ impl = String()
+
+ evt_targets = ()
+
+ def __init__(self):
+ TypeDecorator.__init__(self)
+ sqltypes.SchemaType.__init__(self)
+
+ def _on_table_create(self, target, bind, **kw):
+ self.evt_targets += (target,)
+
+ def _on_metadata_create(self, target, bind, **kw):
+ self.evt_targets += (target,)
+
+ def test_before_parent_attach_plain(self):
+ typ = self.MyType()
+ self._test_before_parent_attach(typ)
+
+ def test_before_parent_attach_typedec_enclosing_schematype(self):
+ # additional test for [ticket:2919] as part of test for
+ # [ticket:3832]
+
+ class MySchemaType(sqltypes.TypeEngine, sqltypes.SchemaType):
+ pass
+
+ target_typ = MySchemaType()
+
+ class MyType(TypeDecorator):
+ impl = target_typ
+
+ typ = MyType()
+ self._test_before_parent_attach(typ, target_typ)
+
+ def test_before_parent_attach_typedec_of_schematype(self):
+ class MyType(TypeDecorator, sqltypes.SchemaType):
+ impl = String
+
+ typ = MyType()
+ self._test_before_parent_attach(typ)
+
+ def test_before_parent_attach_schematype_of_typedec(self):
+ class MyType(sqltypes.SchemaType, TypeDecorator):
+ impl = String
+
+ typ = MyType()
+ self._test_before_parent_attach(typ)
+
+ def _test_before_parent_attach(self, typ, evt_target=None):
+ canary = mock.Mock()
+
+ if evt_target is None:
+ evt_target = typ
+
+ event.listen(evt_target, "before_parent_attach", canary.go)
+
+ c = Column('q', typ)
+
+ eq_(canary.mock_calls, [mock.call.go(evt_target, c)])
+
def test_independent_schema(self):
m = MetaData()
type_ = self.MyType(schema="q")
@@ -1709,6 +1769,13 @@ class SchemaTypeTest(fixtures.TestBase):
dialect_impl = typ.dialect_impl(testing.db.dialect)
eq_(dialect_impl.evt_targets, (m1, ))
+ def test_table_dispatch_decorator_schematype(self):
+ m1 = MetaData()
+ typ = self.MyTypeDecAndSchema()
+ t1 = Table('t1', m1, Column('x', typ))
+ m1.dispatch.before_create(t1, testing.db)
+ eq_(typ.evt_targets, (t1, ))
+
def test_table_dispatch_no_new_impl(self):
m1 = MetaData()
typ = self.MyType()
diff --git a/test/sql/test_types.py b/test/sql/test_types.py
index 3374a67..7f49991 100644
--- a/test/sql/test_types.py
+++ b/test/sql/test_types.py
@@ -1264,6 +1264,25 @@ class EnumTest(AssertsCompiledSQL, fixtures.TablesTest):
]
)
+ def test_validators_not_in_concatenate_roundtrip(self):
+ enum_table = self.tables['non_native_enum_table']
+
+ enum_table.insert().execute([
+ {'id': 1, 'someenum': 'two'},
+ {'id': 2, 'someenum': 'two'},
+ {'id': 3, 'someenum': 'one'},
+ ])
+
+ eq_(
+ select(['foo' + enum_table.c.someenum]).
+ order_by(enum_table.c.id).execute().fetchall(),
+ [
+ ('footwo', ),
+ ('footwo', ),
+ ('fooone', )
+ ]
+ )
+
@testing.fails_on(
'postgresql+zxjdbc',
'zxjdbc fails on ENUM: column "XXX" is of type XXX '
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/sqlalchemy.git
More information about the Python-modules-commits
mailing list