From 63c5b8ef5cc710980575985ed05ef3822bc62143 Mon Sep 17 00:00:00 2001 From: Maciej Urbanski Date: Thu, 21 Sep 2017 14:58:50 +0200 Subject: [PATCH] drop python2.6 and use stdlib collections.OrderedDict --- docs/basics/install.rst | 2 +- schematics/datastructures.py | 146 --------------- schematics/deprecated.py | 3 +- schematics/models.py | 8 +- schematics/schema.py | 3 +- schematics/transforms.py | 6 +- schematics/types/base.py | 3 +- schematics/types/union.py | 2 +- setup.py | 1 - tests/test_list_type.py | 3 +- tests/test_ordered_dict.py | 351 ----------------------------------- tox.ini | 2 +- 12 files changed, 17 insertions(+), 513 deletions(-) delete mode 100644 tests/test_ordered_dict.py diff --git a/docs/basics/install.rst b/docs/basics/install.rst index 6746a3da..d50febdf 100644 --- a/docs/basics/install.rst +++ b/docs/basics/install.rst @@ -12,7 +12,7 @@ The latest development version can be obtained via git:: $ pip install git+https://github.com/schematics/schematics.git#egg=schematics -Schematics currently supports Python versions 2.6, 2.7, 3.3, and 3.4. +Schematics currently supports Python versions 2.7, 3.3, 3.4, 3.5 and 3.6. .. _install_dependencies: diff --git a/schematics/datastructures.py b/schematics/datastructures.py index 13e98158..db8ee831 100644 --- a/schematics/datastructures.py +++ b/schematics/datastructures.py @@ -12,152 +12,6 @@ -class OrderedDict(MutableMapping, dict): - """ - An ordered dictionary. - - The implementation is based on ``collections.OrderedDict`` of the standard library. - It preserves the original technique of storing the keys as a regular list, whereas - the reference implementation now uses a linked list. The built-in list gives better - performance in use cases that are typical with Schematics. - """ - - def __init__(*args, **kwargs): - if not args: - raise TypeError("OrderedDict.__init__() needs an instance as the first argument") - self = args[0] - args = args[1:] - if len(args) > 1: - raise TypeError("OrderedDict() takes at most 1 positional argument, got %d" % len(args)) - dict.__init__(self) - if not self: - self._keys = [] - MutableMapping.update(self, *args, **kwargs) - - __contains__ = dict.__contains__ - __getitem__ = dict.__getitem__ - __len__ = dict.__len__ - - get = dict.get - - def __setitem__(self, key, item, setitem=dict.__setitem__): - if key not in self: - self._keys.append(key) - setitem(self, key, item) - - def __delitem__(self, key, delitem=dict.__delitem__): - delitem(self, key) - self._keys.remove(key) - - def __iter__(self): - return iter(self._keys) - - def __reversed__(self): - return reversed(self._keys) - - def clear(self): - del self._keys[:] - dict.clear(self) - - def copy(self): - return self.__class__(self) - - __copy__ = copy - - def move_to_end(self, key, last=True): - if key not in self: - raise KeyError(key) - self._keys.remove(key) - if last: - self._keys.append(key) - else: - self._keys.insert(0, key) - - __token = object() - - def pop(self, key, default=__token): - if key in self: - self._keys.remove(key) - return dict.pop(self, key) - elif default is self.__token: - raise KeyError(key) - else: - return default - - def popitem(self, last=True): - if not self: - raise KeyError('dictionary is empty') - key = self._keys.pop(-1 if last else 0) - value = dict.pop(self, key) - return key, value - - def setdefault(self, key, default=None): - if key in self: - return self[key] - else: - self[key] = default - return default - - def sort(self, key=None, reverse=False): - if key is not None: - _key = lambda k: key((k, self[k])) - else: - _key = None - self._keys.sort(key=_key, reverse=reverse) - - def reverse(self): - self._keys.reverse() - - @classmethod - def fromkeys(cls, iterable, value=None): - return cls((key, value) for key in iterable) - - def __eq__(self, other): - if isinstance(other, OrderedDict): - return dict.__eq__(self, other) and all(map(eq, self, other)) - else: - return dict.__eq__(self, other) - - def __ne__(self, other): - return not self == other - - def __reduce_ex__(self, protocol=0): - attrs = vars(self).copy() - for k in vars(self.__class__()): - attrs.pop(k, None) - if protocol <= 2: - # Express tuples as lists to enable proper PyYAML serialization. - items = [[k, self[k]] for k in self] - return (self.__class__, (items,), attrs or None) - else: - # Provide items as an iterator. This variant can handle recursive dictionaries. - return (self.__class__, (), attrs or None, None, iter(self.items())) - - __reduce__ = __reduce_ex__ - - def __repr__(self, memo=set()): - call_key = (id(self), get_ident()) - if call_key in memo: - return '...' - else: - memo.add(call_key) - try: - return '%s(%s)' % (self.__class__.__name__, repr(list(self.items())) if self else '') - finally: - memo.remove(call_key) - - if PY3: - - def keys(self): - return _ODKeysView(self) - - def values(self): - return _ODValuesView(self) - - def items(self): - return _ODItemsView(self) - - class _ODKeysView(KeysView): def __reversed__(self): for key in reversed(self._mapping): diff --git a/schematics/deprecated.py b/schematics/deprecated.py index 3941dda7..0196c92d 100644 --- a/schematics/deprecated.py +++ b/schematics/deprecated.py @@ -1,6 +1,7 @@ +from collections import OrderedDict + from .compat import iteritems -from .datastructures import OrderedDict from .types.serializable import Serializable from . import transforms diff --git a/schematics/models.py b/schematics/models.py index 91a5c634..edf6c027 100644 --- a/schematics/models.py +++ b/schematics/models.py @@ -4,11 +4,12 @@ from copy import deepcopy import inspect +from collections import OrderedDict from types import FunctionType from .common import * # pylint: disable=redefined-builtin from .compat import str_compat, repr_compat, _dict -from .datastructures import OrderedDict, Context, ChainMap, MappingProxyType +from .datastructures import Context, ChainMap, MappingProxyType from .exceptions import * from .iteration import atoms from .transforms import ( @@ -97,7 +98,10 @@ def __new__(mcs, name, bases, attrs): fields[key] = value # Convert declared fields into descriptors for new class - fields.sort(key=lambda i: i[1]._position_hint) + fields = OrderedDict(sorted( + (kv for kv in fields.items()), + key=lambda i: i[1]._position_hint, + )) for key, field in iteritems(fields): if isinstance(field, BaseType): attrs[key] = FieldDescriptor(key) diff --git a/schematics/schema.py b/schematics/schema.py index 5f2b991e..b2ca95fd 100644 --- a/schematics/schema.py +++ b/schematics/schema.py @@ -1,7 +1,8 @@ +from collections import OrderedDict + from .compat import itervalues from .common import DEFAULT, NONEMPTY -from .datastructures import OrderedDict from .types import BaseType from .types.serializable import Serializable diff --git a/schematics/transforms.py b/schematics/transforms.py index b2bca966..c8ab3099 100644 --- a/schematics/transforms.py +++ b/schematics/transforms.py @@ -4,6 +4,7 @@ import itertools import types +from collections import OrderedDict from .common import * # pylint: disable=redefined-builtin from .datastructures import Context @@ -13,11 +14,6 @@ from .iteration import atoms, atom_filter from .role import Role -try: - from collections import OrderedDict -except ImportError: - from .datastructures import OrderedDict - ### # Transform loops diff --git a/schematics/types/base.py b/schematics/types/base.py index 92248810..54633670 100644 --- a/schematics/types/base.py +++ b/schematics/types/base.py @@ -16,11 +16,10 @@ import re import string import uuid -from collections import Iterable +from collections import Iterable, OrderedDict from ..common import * # pylint: disable=redefined-builtin from ..compat import string_type -from ..datastructures import OrderedDict from ..exceptions import * from ..translator import _ from ..undefined import Undefined diff --git a/schematics/types/union.py b/schematics/types/union.py index 0b87258e..f7144afd 100644 --- a/schematics/types/union.py +++ b/schematics/types/union.py @@ -1,7 +1,7 @@ import inspect +from collections import OrderedDict from ..common import * -from ..datastructures import OrderedDict from ..exceptions import ConversionError from ..translator import _ from ..transforms import get_import_context, get_export_context diff --git a/setup.py b/setup.py index 56b8d8ba..b99b93b7 100755 --- a/setup.py +++ b/setup.py @@ -26,7 +26,6 @@ 'License :: OSI Approved :: BSD License', 'Programming Language :: Python', 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.2', diff --git a/tests/test_list_type.py b/tests/test_list_type.py index f82f437a..476ba07e 100644 --- a/tests/test_list_type.py +++ b/tests/test_list_type.py @@ -1,6 +1,7 @@ +from collections import OrderedDict + import pytest -from schematics.datastructures import OrderedDict from schematics.models import Model from schematics.types import IntType, StringType from schematics.types.compound import ModelType, ListType diff --git a/tests/test_ordered_dict.py b/tests/test_ordered_dict.py deleted file mode 100644 index cba2d074..00000000 --- a/tests/test_ordered_dict.py +++ /dev/null @@ -1,351 +0,0 @@ -# Most of these tests have been adapted from the stdlib OrderedDict tests. - -from collections import MutableMapping -import copy -from operator import itemgetter -import pickle -from random import shuffle -import sys -from test import mapping_tests - -import pytest - -from schematics.common import PY2, PY3 -from schematics.datastructures import OrderedDict - - -class GeneralMappingTests(mapping_tests.BasicTestMappingProtocol): - - type2test = OrderedDict - - def test_popitem(self): - d = self._empty_mapping() - with pytest.raises(KeyError): - d.popitem() - - -class MyOrderedDict(OrderedDict): - pass - - -class SubclassMappingTests(mapping_tests.BasicTestMappingProtocol): - - type2test = MyOrderedDict - - def test_popitem(self): - d = self._empty_mapping() - with pytest.raises(KeyError): - d.popitem() - - -def test_init(): - - with pytest.raises(TypeError): - OrderedDict([('a', 1), ('b', 2)], None) # too many args - pairs = [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5)] - assert sorted(OrderedDict(dict(pairs)).items()) == pairs # dict input - assert sorted(OrderedDict(**dict(pairs)).items()) == pairs # kwds input - assert list(OrderedDict(pairs).items()) == pairs # pairs input - assert list(OrderedDict( - [('a', 1), ('b', 2), ('c', 9), ('d', 4)], c=3, e=5).items()) == pairs # mixed input - - # make sure no positional args conflict with possible kwdargs - if PY2 and sys.version_info >= (2, 7, 1) or PY3 and sys.version_info >= (3, 2): - assert list(OrderedDict(self=42).items()) == [('self', 42)] - assert list(OrderedDict(other=42).items()) == [('other', 42)] - - with pytest.raises(TypeError): - OrderedDict(42) - with pytest.raises(TypeError): - OrderedDict((), ()) - with pytest.raises(TypeError): - OrderedDict.__init__() - - # Make sure that direct calls to __init__ do not clear previous contents - d = OrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 44), ('e', 55)]) - d.__init__([('e', 5), ('f', 6)], g=7, d=4) - assert (list(d.items()) == - [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5), ('f', 6), ('g', 7)]) - - -def test_update(): - - with pytest.raises(TypeError): - OrderedDict().update([('a', 1), ('b', 2)], None) # too many args - pairs = [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5)] - od = OrderedDict() - od.update(dict(pairs)) - assert sorted(od.items()) == pairs # dict input - od = OrderedDict() - od.update(**dict(pairs)) - assert sorted(od.items()) == pairs # kwds input - od = OrderedDict() - od.update(pairs) - assert list(od.items()) == pairs # pairs input - od = OrderedDict() - od.update([('a', 1), ('b', 2), ('c', 9), ('d', 4)], c=3, e=5) - assert list(od.items()) == pairs # mixed input - - # Issue 9137: Named argument called 'other' or 'self' - # shouldn't be treated specially. - if PY2 and sys.version_info >= (2, 7, 1) or PY3 and sys.version_info >= (3, 2): - od = OrderedDict() - od.update(self=23) - assert list(od.items()) == [('self', 23)] - od = OrderedDict() - od.update(other={}) - assert list(od.items()) == [('other', {})] - od = OrderedDict() - od.update(red=5, blue=6, other=7, self=8) - assert sorted(list(od.items())) == [('blue', 6), ('other', 7), ('red', 5), ('self', 8)] - - # Make sure that direct calls to update do not clear previous contents - # add that updates items are not moved to the end - d = OrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 44), ('e', 55)]) - d.update([('e', 5), ('f', 6)], g=7, d=4) - assert (list(d.items()) == - [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5), ('f', 6), ('g', 7)]) - - with pytest.raises(TypeError): - OrderedDict().update(42) - with pytest.raises(TypeError): - OrderedDict().update((), ()) - with pytest.raises(TypeError): - OrderedDict.update() - - -def test_abc(): - assert isinstance(OrderedDict(), MutableMapping) - assert issubclass(OrderedDict, MutableMapping) - - -def test_clear(): - pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)] - shuffle(pairs) - od = OrderedDict(pairs) - assert len(od) == len(pairs) - od.clear() - assert len(od) == 0 - - -def test_delitem(): - pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)] - od = OrderedDict(pairs) - del od['a'] - assert 'a' not in od - with pytest.raises(KeyError): - del od['a'] - assert list(od.items()) == pairs[:2] + pairs[3:] - - -def test_setitem(): - od = OrderedDict([('d', 1), ('b', 2), ('c', 3), ('a', 4), ('e', 5)]) - od['c'] = 10 # existing element - od['f'] = 20 # new element - assert (list(od.items()) == - [('d', 1), ('b', 2), ('c', 10), ('a', 4), ('e', 5), ('f', 20)]) - - -@pytest.mark.parametrize('f', [lambda x: x, reversed]) -def test_iterators(f): - pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)] - shuffle(pairs) - od = OrderedDict(pairs) - assert list(f(od)) == [t[0] for t in f(pairs)] - assert list(f(od.keys())) == [t[0] for t in f(pairs)] - assert list(f(od.values())) == [t[1] for t in f(pairs)] - assert list(f(od.items())) == list(f(pairs)) - - -def test_popitem(): - pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)] - shuffle(pairs) - od = OrderedDict(pairs) - while pairs: - assert od.popitem() == pairs.pop() - with pytest.raises(KeyError): - od.popitem() - assert len(od) == 0 - - -def test_pop(): - - pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)] - shuffle(pairs) - od = OrderedDict(pairs) - shuffle(pairs) - while pairs: - k, v = pairs.pop() - assert od.pop(k) == v - with pytest.raises(KeyError): - od.pop('xyz') - assert len(od) == 0 - assert od.pop(k, 12345) == 12345 - - # make sure pop still works when __missing__ is defined - class Missing(OrderedDict): - def __missing__(self, key): - return 0 - m = Missing(a=1) - assert m.pop('b', 5) == 5 - assert m.pop('a', 6) == 1 - assert m.pop('a', 6) == 6 - with pytest.raises(KeyError): - m.pop('a') - - -def test_equality(): - pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)] - shuffle(pairs) - od1 = OrderedDict(pairs) - od2 = OrderedDict(pairs) - assert od1 == od2 # same order implies equality - pairs = pairs[2:] + pairs[:2] - od2 = OrderedDict(pairs) - assert od1 != od2 # different order implies inequality - # comparison to regular dict is not order sensitive - assert od1 == dict(od2) - assert dict(od2) == od1 - # different length implied inequality - assert od1 != OrderedDict(pairs[:-1]) - - -def test_copying(): - # Check that ordered dicts are copyable, deepcopyable, picklable, - # and have a repr/eval round-trip - pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)] - od = OrderedDict(pairs) - def check(dup): - assert dup is not od - assert dup == od - check(od.copy()) - check(copy.copy(od)) - check(copy.deepcopy(od)) - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - check(pickle.loads(pickle.dumps(od, proto))) - check(eval(repr(od))) - update_test = OrderedDict() - update_test.update(od) - check(update_test) - check(OrderedDict(od)) - - -def test_yaml_linkage(): - # Verify that __reduce__ is setup in a way that supports PyYAML's dump() feature. - # In yaml, lists are native but tuples are not. - pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)] - od = OrderedDict(pairs) - # yaml.dump(od) --> - # '!!python/object/apply:__main__.OrderedDict\n- - [a, 1]\n - [b, 2]\n' - assert all(type(pair)==list for pair in od.__reduce__()[1][0]) - - -def test_reduce_not_too_fat(): - # do not save instance dictionary if not needed - pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)] - od = OrderedDict(pairs) - assert od.__reduce__()[2] is None - od.x = 10 - assert od.__reduce__()[2] is not None - - -@pytest.mark.skipif(PY2, reason='CPython issue #17900') -def test_pickle_recursive(): - od = OrderedDict() - od['x'] = od - rec = pickle.loads(pickle.dumps(od)) - assert list(od.keys()) == list(rec.keys()) - assert od is not rec - assert rec['x'] is rec - - -def test_repr(): - od = OrderedDict([('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)]) - assert (repr(od) == - "OrderedDict([('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)])") - assert eval(repr(od)) == od - assert repr(OrderedDict()) == "OrderedDict()" - - -def test_repr_recursive(): - # See issue #9826 - od = OrderedDict.fromkeys('abc') - od['x'] = od - assert repr(od) == "OrderedDict([('a', None), ('b', None), ('c', None), ('x', ...)])" - - -def test_setdefault(): - - pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)] - shuffle(pairs) - od = OrderedDict(pairs) - pair_order = list(od.items()) - assert od.setdefault('a', 10) == 3 - # make sure order didn't change - assert list(od.items()) == pair_order - assert od.setdefault('x', 10) == 10 - # make sure 'x' is added to the end - assert list(od.items())[-1] == ('x', 10) - - # make sure setdefault still works when __missing__ is defined - class Missing(OrderedDict): - def __missing__(self, key): - return 0 - assert Missing().setdefault(5, 9) == 9 - - -def test_reinsert(): - # Given insert a, insert b, delete a, re-insert a, - # verify that a is now later than b. - od = OrderedDict() - od['a'] = 1 - od['b'] = 2 - del od['a'] - od['a'] = 1 - assert list(od.items()) == [('b', 2), ('a', 1)] - - -def test_move_to_end(): - od = OrderedDict.fromkeys('abcde') - assert list(od) == list('abcde') - od.move_to_end('c') - assert list(od) == list('abdec') - od.move_to_end('c', 0) - assert list(od) == list('cabde') - od.move_to_end('c', 0) - assert list(od) == list('cabde') - od.move_to_end('e') - assert list(od) == list('cabde') - with pytest.raises(KeyError): - od.move_to_end('x') - - -def test_override_update(): - # Verify that subclasses can override update() without breaking __init__() - class MyOD(OrderedDict): - def update(self, *args, **kwds): - raise Exception() - items = [('a', 1), ('c', 3), ('b', 2)] - assert list(MyOD(items).items()) == items - - -def test_sort(): - - pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)] - shuffled = pairs[:] - - shuffle(shuffled) - od = OrderedDict(shuffled) - od.sort(key=itemgetter(1)) - assert list(od.items()) == pairs - - shuffle(shuffled) - od = OrderedDict(shuffled) - od.sort() - assert list(od.items()) == list(sorted(pairs)) - - shuffle(shuffled) - od = OrderedDict(shuffled) - od.sort(reverse=True) - assert list(od.items()) == list(reversed(sorted(pairs))) - diff --git a/tox.ini b/tox.ini index fecbf4cb..142e10b1 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py26, py27, py33, py34, py35, py36, pypy, pypy3 +envlist = py27, py33, py34, py35, py36, pypy, pypy3 [testenv] deps = -r{toxinidir}/test-requirements.txt