mirror of
https://github.com/sqlalchemy/sqlalchemy.git
synced 2026-06-04 06:48:27 -04:00
Adjusted inplace-binops on set-based collections and association proxies to
more closely follow builtin (2.4+) set semantics. Formerly any set duck-type was accepted, now only types or subtypes of set, frozenset or the collection type itself are accepted.
This commit is contained in:
@@ -60,6 +60,11 @@ CHANGES
|
||||
- Fixed duplicate append event emission on repeated
|
||||
instrumented set.add() operations.
|
||||
|
||||
- set-based collections |=, -=, ^= and &= are stricter about
|
||||
their operands and only operate on sets, frozensets or
|
||||
subclasses of the collection type. Previously, they would
|
||||
accept any duck-typed set.
|
||||
|
||||
- declarative extension
|
||||
- Joined table inheritance mappers use a slightly relaxed
|
||||
function to create the "inherit condition" to the parent
|
||||
@@ -94,6 +99,12 @@ CHANGES
|
||||
the databsae state cleanup (eg. issuing a rollback()) when
|
||||
connections are returned to the pool.
|
||||
|
||||
-ext
|
||||
- set-based association proxies |=, -=, ^= and &= are
|
||||
stricter about their operands and only operate on sets,
|
||||
frozensets or other association proxies. Previously, they
|
||||
would accept any duck-typed set.
|
||||
|
||||
- mssql
|
||||
- Added "odbc_autotranslate" parameter to engine / dburi
|
||||
parameters. Any given string will be passed through to the
|
||||
|
||||
@@ -6,10 +6,12 @@ transparent proxied access to the endpoint of an association object.
|
||||
See the example ``examples/association/proxied_association.py``.
|
||||
"""
|
||||
|
||||
import weakref, itertools
|
||||
import sqlalchemy.exceptions as exceptions
|
||||
import sqlalchemy.orm as orm
|
||||
import sqlalchemy.util as util
|
||||
import itertools
|
||||
import weakref
|
||||
from sqlalchemy import exceptions
|
||||
from sqlalchemy import orm
|
||||
from sqlalchemy import util
|
||||
from sqlalchemy.orm import collections
|
||||
|
||||
|
||||
def association_proxy(targetcollection, attr, **kw):
|
||||
@@ -702,7 +704,7 @@ class _AssociationSet(object):
|
||||
self.add(value)
|
||||
|
||||
def __ior__(self, other):
|
||||
if util.duck_type_collection(other) is not util.Set:
|
||||
if not collections._set_binops_check_strict(self, other):
|
||||
return NotImplemented
|
||||
for value in other:
|
||||
self.add(value)
|
||||
@@ -726,7 +728,7 @@ class _AssociationSet(object):
|
||||
self.discard(value)
|
||||
|
||||
def __isub__(self, other):
|
||||
if util.duck_type_collection(other) is not util.Set:
|
||||
if not collections._set_binops_check_strict(self, other):
|
||||
return NotImplemented
|
||||
for value in other:
|
||||
self.discard(value)
|
||||
@@ -748,7 +750,7 @@ class _AssociationSet(object):
|
||||
self.add(value)
|
||||
|
||||
def __iand__(self, other):
|
||||
if util.duck_type_collection(other) is not util.Set:
|
||||
if not collections._set_binops_check_strict(self, other):
|
||||
return NotImplemented
|
||||
want, have = self.intersection(other), util.Set(self)
|
||||
|
||||
@@ -776,7 +778,7 @@ class _AssociationSet(object):
|
||||
self.add(value)
|
||||
|
||||
def __ixor__(self, other):
|
||||
if util.duck_type_collection(other) is not util.Set:
|
||||
if not collections._set_binops_check_strict(self, other):
|
||||
return NotImplemented
|
||||
want, have = self.symmetric_difference(other), util.Set(self)
|
||||
|
||||
|
||||
@@ -95,7 +95,11 @@ The owning object and InstrumentedCollectionAttribute are also reachable
|
||||
through the adapter, allowing for some very sophisticated behavior.
|
||||
"""
|
||||
|
||||
import copy, inspect, sys, weakref
|
||||
import copy
|
||||
import inspect
|
||||
import sets
|
||||
import sys
|
||||
import weakref
|
||||
|
||||
from sqlalchemy import exceptions, schema, util as sautil
|
||||
from sqlalchemy.util import attrgetter, Set
|
||||
@@ -1136,6 +1140,22 @@ def _dict_decorators():
|
||||
l.pop('Unspecified')
|
||||
return l
|
||||
|
||||
|
||||
try:
|
||||
_set_binop_bases = (set, frozenset, sets.BaseSet)
|
||||
except NameError:
|
||||
_set_binop_bases = (sets.BaseSet,)
|
||||
|
||||
def _set_binops_check_strict(self, obj):
|
||||
"""Allow only set, frozenset and self.__class__-derived objects in binops."""
|
||||
return isinstance(obj, _set_binop_bases + (self.__class__,))
|
||||
|
||||
def _set_binops_check_loose(self, obj):
|
||||
"""Allow anything set-like to participate in set binops."""
|
||||
return (isinstance(obj, _set_binop_bases + (self.__class__,)) or
|
||||
sautil.duck_type_collection(obj) == sautil.Set)
|
||||
|
||||
|
||||
def _set_decorators():
|
||||
"""Hand-turned instrumentation wrappers that can decorate any set-like
|
||||
sequence class."""
|
||||
@@ -1208,7 +1228,7 @@ def _set_decorators():
|
||||
|
||||
def __ior__(fn):
|
||||
def __ior__(self, value):
|
||||
if sautil.duck_type_collection(value) is not Set:
|
||||
if not _set_binops_check_strict(self, value):
|
||||
return NotImplemented
|
||||
for item in value:
|
||||
self.add(item)
|
||||
@@ -1225,7 +1245,7 @@ def _set_decorators():
|
||||
|
||||
def __isub__(fn):
|
||||
def __isub__(self, value):
|
||||
if sautil.duck_type_collection(value) is not Set:
|
||||
if not _set_binops_check_strict(self, value):
|
||||
return NotImplemented
|
||||
for item in value:
|
||||
self.discard(item)
|
||||
@@ -1247,7 +1267,7 @@ def _set_decorators():
|
||||
|
||||
def __iand__(fn):
|
||||
def __iand__(self, other):
|
||||
if sautil.duck_type_collection(other) is not Set:
|
||||
if not _set_binops_check_strict(self, other):
|
||||
return NotImplemented
|
||||
want, have = self.intersection(other), Set(self)
|
||||
remove, add = have - want, want - have
|
||||
@@ -1274,7 +1294,7 @@ def _set_decorators():
|
||||
|
||||
def __ixor__(fn):
|
||||
def __ixor__(self, other):
|
||||
if sautil.duck_type_collection(other) is not Set:
|
||||
if not _set_binops_check_strict(self, other):
|
||||
return NotImplemented
|
||||
want, have = self.symmetric_difference(other), Set(self)
|
||||
remove, add = have - want, want - have
|
||||
|
||||
@@ -534,6 +534,7 @@ class SetTest(_CollectionOperations):
|
||||
for other in (set(['a','b','c']), set(['a','b','c','d']),
|
||||
set(['a']), set(['a','b']),
|
||||
set(['c','d']), set(['e', 'f', 'g']),
|
||||
frozenset(['e', 'f', 'g']),
|
||||
set()):
|
||||
p = Parent('p')
|
||||
p.children = base[:]
|
||||
|
||||
@@ -484,6 +484,11 @@ class CollectionsTest(TestBase):
|
||||
control |= values
|
||||
assert_eq()
|
||||
|
||||
values = frozenset([e, creator()])
|
||||
obj.attr |= values
|
||||
control |= values
|
||||
assert_eq()
|
||||
|
||||
try:
|
||||
direct |= [e, creator()]
|
||||
assert False
|
||||
@@ -529,6 +534,11 @@ class CollectionsTest(TestBase):
|
||||
control -= values
|
||||
assert_eq()
|
||||
|
||||
values = frozenset([creator()])
|
||||
obj.attr -= values
|
||||
control -= values
|
||||
assert_eq()
|
||||
|
||||
try:
|
||||
direct -= [e, creator()]
|
||||
assert False
|
||||
|
||||
Reference in New Issue
Block a user