]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- 2.3 fixup, part two: 100% passing for sqlite
authorJason Kirtland <jek@discorporate.us>
Mon, 21 Jan 2008 23:19:39 +0000 (23:19 +0000)
committerJason Kirtland <jek@discorporate.us>
Mon, 21 Jan 2008 23:19:39 +0000 (23:19 +0000)
  - added 2.4-style binops to util.Set on 2.3
  - OrderedSets pickle on 2.3
  - more lib/sqlalchemy set vs Set corrections
  - fixed InstrumentedSet.discard for 2.3
  - set, sorted compatibility for test suite
- added testing.fails_if decorator

lib/sqlalchemy/ext/associationproxy.py
lib/sqlalchemy/orm/collections.py
lib/sqlalchemy/util.py
test/base/utils.py
test/ext/alltests.py
test/orm/collection.py
test/profiling/compiler.py
test/sql/testtypes.py
test/testlib/compat.py
test/testlib/profiling.py
test/testlib/testing.py

index fefc289f8fa6bc5a1c34d9fefb3368c84c7e5a9c..e9f6040058569a8ed0d0b59ac24c3e0a62183859 100644 (file)
@@ -686,7 +686,7 @@ class _AssociationSet(object):
             self.add(value)
 
     def __ior__(self, other):
-        if util.duck_type_collection(other) is not set:
+        if util.duck_type_collection(other) is not util.Set:
             return NotImplemented
         for value in other:
             self.add(value)
@@ -710,7 +710,7 @@ class _AssociationSet(object):
             self.discard(value)
 
     def __isub__(self, other):
-        if util.duck_type_collection(other) is not set:
+        if util.duck_type_collection(other) is not util.Set:
             return NotImplemented
         for value in other:
             self.discard(value)
@@ -732,7 +732,7 @@ class _AssociationSet(object):
             self.add(value)
 
     def __iand__(self, other):
-        if util.duck_type_collection(other) is not set:
+        if util.duck_type_collection(other) is not util.Set:
             return NotImplemented
         want, have = self.intersection(other), util.Set(self)
 
@@ -760,7 +760,7 @@ class _AssociationSet(object):
             self.add(value)
 
     def __ixor__(self, other):
-        if util.duck_type_collection(other) is not set:
+        if util.duck_type_collection(other) is not util.Set:
             return NotImplemented
         want, have = self.symmetric_difference(other), util.Set(self)
 
index c63c4dc8c7bc250f7d6c9d1fe8b04b14dbf4be08..daf9dfb4500a2004444f9315fb7cd9eb5600cb07 100644 (file)
@@ -98,7 +98,7 @@ through the adapter, allowing for some very sophisticated behavior.
 import copy, inspect, sys, weakref
 
 from sqlalchemy import exceptions, schema, util as sautil
-from sqlalchemy.util import attrgetter
+from sqlalchemy.util import attrgetter, Set
 
 
 __all__ = ['collection', 'collection_adapter',
@@ -1098,7 +1098,7 @@ def _set_decorators():
 
     def _tidy(fn):
         setattr(fn, '_sa_instrumented', True)
-        fn.__doc__ = getattr(getattr(set, fn.__name__), '__doc__')
+        fn.__doc__ = getattr(getattr(Set, fn.__name__), '__doc__')
 
     Unspecified=object()
 
@@ -1110,15 +1110,23 @@ def _set_decorators():
         _tidy(add)
         return add
 
-    def discard(fn):
-        def discard(self, value, _sa_initiator=None):
-            # testlib.pragma exempt:__hash__
-            if value in self:
-                __del(self, value, _sa_initiator)
-            # testlib.pragma exempt:__hash__
-            fn(self, value)
-        _tidy(discard)
-        return discard
+    if sys.version_info < (2, 4):
+        def discard(fn):
+            def discard(self, value, _sa_initiator=None):
+                if value in self:
+                    self.remove(value, _sa_initiator)
+            _tidy(discard)
+            return discard
+    else:
+        def discard(fn):
+            def discard(self, value, _sa_initiator=None):
+                # testlib.pragma exempt:__hash__
+                if value in self:
+                    __del(self, value, _sa_initiator)
+                    # testlib.pragma exempt:__hash__
+                fn(self, value)
+            _tidy(discard)
+            return discard
 
     def remove(fn):
         def remove(self, value, _sa_initiator=None):
@@ -1156,7 +1164,7 @@ def _set_decorators():
 
     def __ior__(fn):
         def __ior__(self, value):
-            if sautil.duck_type_collection(value) is not set:
+            if sautil.duck_type_collection(value) is not Set:
                 return NotImplemented
             for item in value:
                 if item not in self:
@@ -1174,7 +1182,7 @@ def _set_decorators():
 
     def __isub__(fn):
         def __isub__(self, value):
-            if sautil.duck_type_collection(value) is not set:
+            if sautil.duck_type_collection(value) is not Set:
                 return NotImplemented
             for item in value:
                 self.discard(item)
@@ -1184,7 +1192,7 @@ def _set_decorators():
 
     def intersection_update(fn):
         def intersection_update(self, other):
-            want, have = self.intersection(other), sautil.Set(self)
+            want, have = self.intersection(other), Set(self)
             remove, add = have - want, want - have
 
             for item in remove:
@@ -1196,9 +1204,9 @@ def _set_decorators():
 
     def __iand__(fn):
         def __iand__(self, other):
-            if sautil.duck_type_collection(other) is not set:
+            if sautil.duck_type_collection(other) is not Set:
                 return NotImplemented
-            want, have = self.intersection(other), sautil.Set(self)
+            want, have = self.intersection(other), Set(self)
             remove, add = have - want, want - have
 
             for item in remove:
@@ -1211,7 +1219,7 @@ def _set_decorators():
 
     def symmetric_difference_update(fn):
         def symmetric_difference_update(self, other):
-            want, have = self.symmetric_difference(other), sautil.Set(self)
+            want, have = self.symmetric_difference(other), Set(self)
             remove, add = have - want, want - have
 
             for item in remove:
@@ -1223,9 +1231,9 @@ def _set_decorators():
 
     def __ixor__(fn):
         def __ixor__(self, other):
-            if sautil.duck_type_collection(other) is not set:
+            if sautil.duck_type_collection(other) is not Set:
                 return NotImplemented
-            want, have = self.symmetric_difference(other), sautil.Set(self)
+            want, have = self.symmetric_difference(other), Set(self)
             remove, add = have - want, want - have
 
             for item in remove:
@@ -1250,7 +1258,7 @@ class InstrumentedList(list):
        'remover': 'remove',
        'iterator': '__iter__', }
 
-class InstrumentedSet(sautil.Set):
+class InstrumentedSet(Set):
     """An instrumented version of the built-in set (or Set)."""
 
     __instrumentation__ = {
@@ -1266,7 +1274,7 @@ class InstrumentedDict(dict):
 
 __canned_instrumentation = {
     list: InstrumentedList,
-    sautil.Set: InstrumentedSet,
+    Set: InstrumentedSet,
     dict: InstrumentedDict,
     }
 
@@ -1275,10 +1283,10 @@ __interfaces = {
             'remover':  'remove',
             'iterator': '__iter__',
             '_decorators': _list_decorators(), },
-    sautil.Set: { 'appender': 'add',
-                  'remover': 'remove',
-                  'iterator': '__iter__',
-                  '_decorators': _set_decorators(), },
+    Set: { 'appender': 'add',
+           'remover': 'remove',
+           'iterator': '__iter__',
+           '_decorators': _set_decorators(), },
     # decorators are required for dicts and object collections.
     dict: { 'iterator': 'itervalues',
             '_decorators': _dict_decorators(), },
index 05990e4ed6d8106c88b0e759c4378e339923e38e..4f30f76ba50fe881011d2b92275d15aaba7fa646 100644 (file)
@@ -19,8 +19,49 @@ try:
     Set = set
     set_types = set, sets.Set
 except NameError:
-    Set = sets.Set
     set_types = sets.Set,
+    # layer some of __builtin__.set's binop behavior onto sets.Set
+    class Set(sets.Set):
+        def _binary_sanity_check(self, other):
+            pass
+
+        def issubset(self, iterable):
+            other = type(self)(iterable)
+            return sets.Set.issubset(self, other)
+        def __le__(self, other):
+            sets.Set._binary_sanity_check(self, other)
+            return sets.Set.__le__(self, other)
+        def issuperset(self, iterable):
+            other = type(self)(iterable)
+            return sets.Set.issuperset(self, other)
+        def __ge__(self, other):
+            sets.Set._binary_sanity_check(self, other)
+            return sets.Set.__ge__(self, other)
+
+        # lt and gt still require a BaseSet
+        def __lt__(self, other):
+            sets.Set._binary_sanity_check(self, other)
+            return sets.Set.__lt__(self, other)
+        def __gt__(self, other):
+            sets.Set._binary_sanity_check(self, other)
+            return sets.Set.__gt__(self, other)
+
+        def __ior__(self, other):
+            if not isinstance(other, sets.BaseSet):
+                return NotImplemented
+            return sets.Set.__ior__(self, other)
+        def __iand__(self, other):
+            if not isinstance(other, sets.BaseSet):
+                return NotImplemented
+            return sets.Set.__iand__(self, other)
+        def __ixor__(self, other):
+            if not isinstance(other, sets.BaseSet):
+                return NotImplemented
+            return sets.Set.__ixor__(self, other)
+        def __isub__(self, other):
+            if not isinstance(other, sets.BaseSet):
+                return NotImplemented
+            return sets.Set.__isub__(self, other)
 
 try:
     import cPickle as pickle
@@ -186,11 +227,16 @@ def duck_type_collection(specimen, default=None):
     """
 
     if hasattr(specimen, '__emulates__'):
-        return specimen.__emulates__
+        # canonicalize set vs sets.Set to a standard: util.Set
+        if (specimen.__emulates__ is not None and
+            issubclass(specimen.__emulates__, set_types)):
+            return Set
+        else:
+            return specimen.__emulates__
 
     isa = isinstance(specimen, type) and issubclass or isinstance
     if isa(specimen, list): return list
-    if isa(specimen, Set): return Set
+    if isa(specimen, set_types): return Set
     if isa(specimen, dict): return dict
 
     if hasattr(specimen, 'append'):
@@ -349,7 +395,8 @@ class OrderedDict(dict):
     def __init__(self, ____sequence=None, **kwargs):
         self._list = []
         if ____sequence is None:
-            self.update(**kwargs)
+            if kwargs:
+                self.update(**kwargs)
         else:
             self.update(____sequence, **kwargs)
 
@@ -539,6 +586,14 @@ class OrderedSet(Set):
 
     __isub__ = difference_update
 
+    if hasattr(Set, '__getstate__'):
+        def __getstate__(self):
+            base = Set.__getstate__(self)
+            return base, self._list
+
+        def __setstate__(self, state):
+            Set.__setstate__(self, state[0])
+            self._list = state[1]
 
 class IdentitySet(object):
     """A set that considers only object id() for uniqueness.
index 61e2b95a8a692c701da72ffef57355a142b56343..5a034e0b0f5d235d86945345c984fcb3f22fa1bb 100644 (file)
@@ -138,8 +138,8 @@ class HashEqOverride(object):
 
 class IdentitySetTest(unittest.TestCase):
     def assert_eq(self, identityset, expected_iterable):
-        found = sorted(list(identityset))
-        expected = sorted(expected_iterable)
+        expected = sorted([id(o) for o in expected_iterable])
+        found = sorted([id(o) for o in identityset])
         self.assertEquals(found, expected)
 
     def test_init(self):
index 7639cd71e262b75704a4141da086b79f26c91a87..6f74e3dbced33736842af0d7d385bc9dcaff1bb2 100644 (file)
@@ -1,12 +1,16 @@
 import testenv; testenv.configure_for_tests()
-import unittest, doctest
+import doctest, sys, unittest
 
 def suite():
     unittest_modules = ['ext.activemapper',
                         'ext.assignmapper',
                         'ext.orderinglist',
                         'ext.associationproxy']
-    doctest_modules = ['sqlalchemy.ext.sqlsoup']
+
+    if sys.version_info >= (2, 4):
+        doctest_modules = ['sqlalchemy.ext.sqlsoup']
+    else:
+        doctest_modules = []
 
     alltests = unittest.TestSuite()
     for name in unittest_modules:
index 7addb8687428ef45935a90d2fa464da9c9a46773..d67ec980cf1fefe75fb998cd6a52f2404af811db 100644 (file)
@@ -1,4 +1,6 @@
 import testenv; testenv.configure_for_tests()
+import sys
+from operator import and_
 from sqlalchemy import *
 import sqlalchemy.exceptions as exceptions
 from sqlalchemy.orm import create_session, mapper, relation, \
@@ -6,9 +8,14 @@ from sqlalchemy.orm import create_session, mapper, relation, \
 import sqlalchemy.orm.collections as collections
 from sqlalchemy.orm.collections import collection
 from sqlalchemy import util
-from operator import and_
 from testlib import *
 
+try:
+    py_set = __builtins__.set
+except AttributeError:
+    import sets
+    py_set = sets.Set
+
 class Canary(interfaces.AttributeExtension):
     def __init__(self):
         self.data = set()
@@ -703,7 +710,7 @@ class CollectionsTest(PersistTest):
 
     def test_set_emulates(self):
         class SetIsh(object):
-            __emulates__ = set
+            __emulates__ = py_set
             def __init__(self):
                 self.data = set()
             def add(self, item):
@@ -839,10 +846,11 @@ class CollectionsTest(PersistTest):
             control.update(d)
             assert_eq()
 
-            kw = dict([(ee.a, ee) for ee in [e, creator()]])
-            direct.update(**kw)
-            control.update(**kw)
-            assert_eq()
+            if sys.version_info >= (2, 4):
+                kw = dict([(ee.a, ee) for ee in [e, creator()]])
+                direct.update(**kw)
+                control.update(**kw)
+                assert_eq()
 
     def _test_dict_bulk(self, typecallable, creator=dictable_entity):
         class Foo(object):
index 58533f70079c3b8a3e391926d8be68a182094932..330271a1cb20cd17a31026afbb866b2ff728729f 100644 (file)
@@ -15,16 +15,16 @@ class CompileTest(AssertMixin):
             Column('c1', Integer, primary_key=True),
             Column('c2', String(30)))
 
-    @profiling.profiled('ctest_insert', call_range=(40, 50), always=True)
+    @profiling.function_call_count(42, {'2.3': 44})
     def test_insert(self):
         t1.insert().compile()
 
-    @profiling.profiled('ctest_update', call_range=(40, 50), always=True)
+    @profiling.function_call_count(42, {'2.3': 47})
     def test_update(self):
         t1.update().compile()
 
     # TODO: this is alittle high
-    @profiling.profiled('ctest_select', call_range=(110, 140), always=True)
+    @profiling.function_call_count(125, versions={'2.3': 180})
     def test_select(self):
         s = select([t1], t1.c.c2==t2.c.c1)
         s.compile()
index fdb6f3cc23508311f2340d07245f5a7e30574234..4d97801556e34cf0a40e87257515e879269f5c5b 100644 (file)
@@ -690,6 +690,14 @@ class StringTest(AssertMixin):
         finally:
             bar.drop()
 
+def _missing_decimal():
+    """Python implementation supports decimals"""
+    try:
+        import decimal
+        return False
+    except ImportError:
+        return True
+
 class NumericTest(AssertMixin):
     def setUpAll(self):
         global numeric_table, metadata
@@ -709,6 +717,7 @@ class NumericTest(AssertMixin):
     def tearDown(self):
         numeric_table.delete().execute()
 
+    @testing.fails_if(_missing_decimal)
     def test_decimal(self):
         from decimal import Decimal
         numeric_table.insert().execute(numericcol=3.5, floatcol=5.6, ncasdec=12.4, fcasdec=15.75)
index 590bf50f42d5e14aec2e2d2955382a76bbdcf812..8d2b35d4a2710e72beb84bf7be21685c8ccb4f2d 100644 (file)
@@ -1,17 +1,66 @@
-import new
+import itertools, new
 
 __all__ = 'set', 'sorted', '_function_named'
 
 try:
     set = set
 except NameError:
-    from sets import Set as set
+    import sets
+
+    # keep this in sync with sqlalchemy.util.Set
+    # can't just import it in testlib because of coverage, load order, etc.
+    class set(sets.Set):
+        def _binary_sanity_check(self, other):
+            pass
+
+        def issubset(self, iterable):
+            other = type(self)(iterable)
+            return sets.Set.issubset(self, other)
+        def __le__(self, other):
+            sets.Set._binary_sanity_check(self, other)
+            return sets.Set.__le__(self, other)
+        def issuperset(self, iterable):
+            other = type(self)(iterable)
+            return sets.Set.issuperset(self, other)
+        def __ge__(self, other):
+            sets.Set._binary_sanity_check(self, other)
+            return sets.Set.__ge__(self, other)
+
+        # lt and gt still require a BaseSet
+        def __lt__(self, other):
+            sets.Set._binary_sanity_check(self, other)
+            return sets.Set.__lt__(self, other)
+        def __gt__(self, other):
+            sets.Set._binary_sanity_check(self, other)
+            return sets.Set.__gt__(self, other)
+
+        def __ior__(self, other):
+            if not isinstance(other, sets.BaseSet):
+                return NotImplemented
+            return sets.Set.__ior__(self, other)
+        def __iand__(self, other):
+            if not isinstance(other, sets.BaseSet):
+                return NotImplemented
+            return sets.Set.__iand__(self, other)
+        def __ixor__(self, other):
+            if not isinstance(other, sets.BaseSet):
+                return NotImplemented
+            return sets.Set.__ixor__(self, other)
+        def __isub__(self, other):
+            if not isinstance(other, sets.BaseSet):
+                return NotImplemented
+            return sets.Set.__isub__(self, other)
 
 try:
     sorted = sorted
 except NameError:
-    def sorted(iterable):
-        return list(iterable).sort()
+    def sorted(iterable, cmp=None):
+        l = list(iterable)
+        if cmp:
+            l.sort(cmp)
+        else:
+            l.sort()
+        return l
 
 def _function_named(fn, newname):
     try:
index 8867d016923ce8cc30d2d8174a82ac4a5d80dba2..9e2b6ed8741361f61109097945546304c2f45608 100644 (file)
@@ -143,7 +143,7 @@ def function_call_count(count=None, versions={}, variance=0.05):
                     raise AssertionError(
                         "Function call count %s not within %s%% "
                         "of expected %s. (Python version %s)" % (
-                        calls, variance, count, py_version))
+                        calls, (variance * 100), count, py_version))
                 return result
             finally:
                 if os.path.exists(filename):
index b05795efdf390c47d3a0eff1c58438c0adf15a00..8b64ce7db2a293689af547b4f303ad558de31602 100644 (file)
@@ -25,6 +25,37 @@ _ops = { '<': operator.lt,
 # sugar ('testing.db'); set here by config() at runtime
 db = None
 
+def fails_if(callable_):
+    """Mark a test as expected to fail if callable_ returns True.
+
+    If the callable returns false, the test is run and reported as normal.
+    However if the callable returns true, the test is expected to fail and the
+    unit test logic is inverted: if the test fails, a success is reported.  If
+    the test succeeds, a failure is reported.
+    """
+
+    docstring = getattr(callable_, '__doc__', callable_.__name__)
+    description = docstring.split('\n')[0]
+
+    def decorate(fn):
+        fn_name = fn.__name__
+        def maybe(*args, **kw):
+            if not callable_():
+                return fn(*args, **kw)
+            else:
+                try:
+                    fn(*args, **kw)
+                except Exception, ex:
+                    print ("'%s' failed as expected (condition: %s): %s " % (
+                        fn_name, description, str(ex)))
+                    return True
+                else:
+                    raise AssertionError(
+                        "Unexpected success for '%s' (condition: %s)" %
+                        (fn_name, description))
+        return _function_named(maybe, fn_name)
+    return decorate
+
 
 def fails_on(*dbs):
     """Mark a test as expected to fail on one or more database implementations.