From d13fd9bb8a0ad1486df06d5fa6133d849529d3f2 Mon Sep 17 00:00:00 2001 From: jab Date: Mon, 19 Nov 2018 14:37:38 +0000 Subject: [PATCH] resurrect delegating mixins instead of __delegate__ + other refinements e.g. s/__repr_delegate__/_repr_delegate Also - update - update from pytest 3 to 4 - add CII best practices badge - prepare for 0.17.5 release --- CHANGELOG.rst | 32 +++++- README.rst | 4 + assets/bidict-types-diagram.dot | 2 +- assets/custom.css | 2 +- bidict/__init__.py | 4 +- bidict/_abc.py | 2 +- bidict/_base.py | 73 ++++-------- bidict/_bidict.py | 143 +----------------------- bidict/_delegating_mixins.py | 92 ++++++++++++++++ bidict/_dup.py | 2 +- bidict/_exc.py | 2 +- bidict/_frozenbidict.py | 9 +- bidict/_frozenordered.py | 67 ++++------- bidict/_marker.py | 2 +- bidict/_miss.py | 2 +- bidict/_mut.py | 177 ++++++++++++++++++++++++++++++ bidict/_named.py | 4 +- bidict/_noop.py | 2 +- bidict/_orderedbase.py | 14 +-- bidict/_orderedbidict.py | 8 +- bidict/_util.py | 2 +- bidict/compat.py | 2 +- bidict/metadata.py | 2 +- build-docs.sh | 2 +- docs/conf.py | 2 +- docs/extending.rst | 33 ++++-- run-tests.py | 2 +- setup.cfg | 2 +- setup.py | 4 +- tests/test_benchmark.py | 2 +- tests/test_bidict.txt | 2 +- tests/test_class_relationships.py | 21 ++-- tests/test_hypothesis.py | 2 +- tests/test_metadata.py | 2 +- tests/test_orderedbidict.txt | 2 +- 35 files changed, 420 insertions(+), 305 deletions(-) create mode 100644 bidict/_delegating_mixins.py create mode 100644 bidict/_mut.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 686b998..e5fcf22 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -22,6 +22,26 @@ Tip: `Subscribe to bidict releases `__ on libraries.io to be notified when new versions of bidict are released. +0.17.5 (2018-11-19) +------------------- + +Improvements to performance and delegation logic, +with minor breaking changes to semi-private APIs. + +- Remove the ``__delegate__`` instance attribute added in the previous release. + It was overly general and not worth the cost. + + Instead of checking ``self.__delegate__`` and delegating accordingly + each time a possibly-delegating method is called, + revert back to using "delegated-to-fwdm" mixin classes + (now found in ``bidict._delegating_mixins``), + and resurrect a mutable bidict parent class that omits the mixins + as :class:`bidict.MutableBidict`. + +- Rename ``__repr_delegate__`` to + :class:`~bidict.BidictBase._repr_delegate`. + + 0.17.4 (2018-11-14) ------------------- @@ -39,8 +59,8 @@ Minor code, interop, and (semi-)private API improvements. and instead move their methods into :class:`~bidict.BidictBase`, which now checks for an object defined by the - :attr:`~bidict.BidictBase.__delegate__` attribute. - The :attr:`~bidict.BidictBase.__delegate__` object + ``BidictBase.__delegate__`` attribute. + The ``BidictBase.__delegate__`` object will be delegated to if the method is available on it, otherwise a default implementation (e.g. inherited from :class:`~collections.abc.Mapping`) @@ -50,8 +70,8 @@ Minor code, interop, and (semi-)private API improvements. Consolidate ``_MutableBidict`` into :class:`bidict.bidict` now that the dropped mixin classes make it unnecessary. -- Change :attr:`~bidict.BidictBase.__repr_delegate__` - to take simply a type like :class:`dict` or :class:`list`. +- Change ``__repr_delegate__`` + to simply take a type like :class:`dict` or :class:`list`. - Upgrade to latest major `sortedcontainers `__ @@ -64,7 +84,7 @@ Minor code, interop, and (semi-)private API improvements. :class:`~collections.abc.Mapping` interface, and provides fallback implementations when the methods are unavailable. This allows the :ref:`extending:Sorted Bidict Recipes` - to continue to work with sortedcontainers v2 on Python2. + to continue to work with sortedcontainers v2 on Python 2. 0.17.3 (2018-09-18) @@ -221,7 +241,7 @@ Miscellaneous - :func:`~bidict.BidictBase.__repr__` no longer checks for a ``__reversed__`` method to determine whether to use an ordered or unordered-style repr. - It now calls the new :attr:`~bidict.BidictBase.__repr_delegate__` instead + It now calls the new ``__repr_delegate__`` instead (which may be overridden if needed), for better composability. Minor Breaking API Changes diff --git a/README.rst b/README.rst index 0e5c6f0..8097846 100644 --- a/README.rst +++ b/README.rst @@ -41,6 +41,10 @@ Status :target: https://www.codacy.com/app/jab/bidict :alt: Codacy grade +.. image:: https://bestpractices.coreinfrastructure.org/projects/2354/badge + :target: https://bestpractices.coreinfrastructure.org/en/projects/2354 + :alt: CII best practices badge + .. image:: https://tidelift.com/badges/github/jab/bidict :target: https://tidelift.com/subscription/pkg/pypi-bidict?utm_source=pypi-bidict&utm_medium=referral&utm_campaign=docs :alt: Tidelift dependency badge diff --git a/assets/bidict-types-diagram.dot b/assets/bidict-types-diagram.dot index 7c7062d..7da5394 100644 --- a/assets/bidict-types-diagram.dot +++ b/assets/bidict-types-diagram.dot @@ -1,4 +1,4 @@ -// Copyright 2018 Joshua Bronson. All Rights Reserved. +// Copyright 2009-2018 Joshua Bronson. All Rights Reserved. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/assets/custom.css b/assets/custom.css index 044f0e7..627c11e 100644 --- a/assets/custom.css +++ b/assets/custom.css @@ -1,5 +1,5 @@ /* - * Copyright 2018 Joshua Bronson. All Rights Reserved. + * Copyright 2009-2018 Joshua Bronson. All Rights Reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/bidict/__init__.py b/bidict/__init__.py index 9d91bc0..80b67fd 100644 --- a/bidict/__init__.py +++ b/bidict/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2018 Joshua Bronson. All Rights Reserved. +# Copyright 2009-2018 Joshua Bronson. All Rights Reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -44,6 +44,7 @@ Efficient, Pythonic bidirectional map implementation and related functionality. from ._abc import BidirectionalMapping from ._base import BidictBase +from ._mut import MutableBidict from ._bidict import bidict from ._dup import DuplicationPolicy, IGNORE, OVERWRITE, RAISE from ._exc import ( @@ -84,6 +85,7 @@ __all__ = ( 'ValueDuplicationError', 'KeyAndValueDuplicationError', 'BidictBase', + 'MutableBidict', 'frozenbidict', 'bidict', 'namedbidict', diff --git a/bidict/_abc.py b/bidict/_abc.py index b865025..0a1aae0 100644 --- a/bidict/_abc.py +++ b/bidict/_abc.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2018 Joshua Bronson. All Rights Reserved. +# Copyright 2009-2018 Joshua Bronson. All Rights Reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/bidict/_base.py b/bidict/_base.py index fe1f441..7eef277 100644 --- a/bidict/_base.py +++ b/bidict/_base.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2018 Joshua Bronson. All Rights Reserved. +# Copyright 2009-2018 Joshua Bronson. All Rights Reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -22,7 +22,7 @@ # * Code review nav * #============================================================================== -# ← Prev: _abc.py Current: _base.py Next: _frozenbidict.py → +# ← Prev: _abc.py Current: _base.py Next: _delegating_mixins.py → #============================================================================== @@ -38,7 +38,7 @@ from ._exc import ( from ._miss import _MISS from ._noop import _NOOP from ._util import _iteritems_args_kw -from .compat import PY2, KeysView, ItemsView, iteritems, Mapping +from .compat import PY2, KeysView, ItemsView, Mapping, iteritems # Since BidirectionalMapping implements __subclasshook__, and BidictBase @@ -52,10 +52,7 @@ from .compat import PY2, KeysView, ItemsView, iteritems, Mapping class BidictBase(BidirectionalMapping): """Base class implementing :class:`BidirectionalMapping`.""" - __slots__ = ['_fwdm', '_invm', '_inv', '_invweak', '_hash'] - - if not PY2: - __slots__.append('__weakref__') + __slots__ = ('_fwdm', '_invm', '_inv', '_invweak', '_hash') + (() if PY2 else ('__weakref__',)) #: The default :class:`DuplicationPolicy` #: (in effect during e.g. :meth:`~bidict.bidict.__init__` calls) @@ -94,7 +91,7 @@ class BidictBase(BidirectionalMapping): _invm_cls = dict #: The object used by :meth:`__repr__` for printing the contained items. - __repr_delegate__ = dict + _repr_delegate = dict def __init__(self, *args, **kw): # pylint: disable=super-init-not-called """Make a new bidirectional dictionary. @@ -194,7 +191,7 @@ class BidictBase(BidirectionalMapping): clsname = self.__class__.__name__ if not self: return '%s()' % clsname - return '%s(%r)' % (clsname, self.__repr_delegate__(iteritems(self))) + return '%s(%r)' % (clsname, self._repr_delegate(iteritems(self))) # The inherited Mapping.__eq__ implementation would work, but it's implemented in terms of an # inefficient ``dict(self.items()) == dict(other.items())`` comparison, so override it with a @@ -400,28 +397,16 @@ class BidictBase(BidirectionalMapping): """The number of contained items.""" return len(self._fwdm) - @property - def __delegate__(self): - """An object to delegate to for optimized implementations - of various operations (e.g. :meth:`keys`) if available. - Override or set e.g. ``__delegate__ = None`` in a subclass to disable. - """ - return self._fwdm - def __iter__(self): # lgtm [py/inheritance/incorrect-overridden-signature] """Iterator over the contained items.""" - delegate = getattr(self.__delegate__, '__iter__', lambda: iter(self.keys())) - return delegate() + # No default implementation for __iter__ inherited from Mapping -> + # always delegate to _fwdm. + return iter(self._fwdm) def __getitem__(self, key): u"""*x.__getitem__(key) ⟺ x[key]*""" return self._fwdm[key] - def keys(self): - """A set-like object providing a view on the contained keys.""" - delegate = getattr(self.__delegate__, 'keys', super(BidictBase, self).keys) - return delegate() - def values(self): """A set-like object providing a view on the contained values. @@ -434,18 +419,16 @@ class BidictBase(BidirectionalMapping): """ return self.inv.keys() - def items(self): - """A set-like object providing a view on the contained items.""" - delegate = getattr(self.__delegate__, 'items', super(BidictBase, self).items) - return delegate() - if PY2: - def viewkeys(self): # noqa: D102; pylint: disable=missing-docstring - delegate = getattr(self.__delegate__, 'viewkeys', lambda: KeysView(self)) - return delegate() + # For iterkeys and iteritems, inheriting from Mapping already provides + # the best default implementations so no need to define here. - viewkeys.__doc__ = keys.__doc__ - keys.__doc__ = 'A list of the contained keys.' + def itervalues(self): + """An iterator over the contained values.""" + return self.inv.iterkeys() + + def viewkeys(self): # noqa: D102; pylint: disable=missing-docstring + return KeysView(self) def viewvalues(self): # noqa: D102; pylint: disable=missing-docstring return self.inv.viewkeys() @@ -454,25 +437,7 @@ class BidictBase(BidirectionalMapping): values.__doc__ = 'A list of the contained values.' def viewitems(self): # noqa: D102; pylint: disable=missing-docstring - delegate = getattr(self.__delegate__, 'viewitems', lambda: ItemsView(self)) - return delegate() - - viewitems.__doc__ = items.__doc__ - items.__doc__ = 'A list of the contained items.' - - def iterkeys(self): - """An iterator over the contained keys.""" - delegate = getattr(self.__delegate__, 'iterkeys', super(BidictBase, self).iterkeys) - return delegate() - - def itervalues(self): - """An iterator over the contained values.""" - return self.inv.iterkeys() - - def iteritems(self): - """An iterator over the contained items.""" - delegate = getattr(self.__delegate__, 'iteritems', super(BidictBase, self).iteritems) - return delegate() + return ItemsView(self) # __ne__ added automatically in Python 3 when you implement __eq__, but not in Python 2. def __ne__(self, other): # noqa: N802 @@ -487,5 +452,5 @@ _NODUP = _DedupResult(False, False, _MISS, _MISS) # * Code review nav * #============================================================================== -# ← Prev: _abc.py Current: _base.py Next: _frozenbidict.py → +# ← Prev: _abc.py Current: _base.py Next: _delegating_mixins.py → #============================================================================== diff --git a/bidict/_bidict.py b/bidict/_bidict.py index 3205dbf..8fa7a4f 100644 --- a/bidict/_bidict.py +++ b/bidict/_bidict.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2018 Joshua Bronson. All Rights Reserved. +# Copyright 2009-2018 Joshua Bronson. All Rights Reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -22,156 +22,25 @@ # * Code review nav * #============================================================================== -# ← Prev: _frozenbidict.py Current: _bidict.py Next: _orderedbase.py → +# ← Prev: _mut.py Current: _bidict.py Next: _orderedbase.py → #============================================================================== """Provides :class:`bidict`.""" -from ._base import BidictBase -from ._dup import OVERWRITE, RAISE, _OnDup -from ._miss import _MISS -from .compat import MutableMapping +from ._mut import MutableBidict +from ._delegating_mixins import _DelegateKeysAndItemsToFwdm -# Extend MutableMapping explicitly because it doesn't implement __subclasshook__, as well as to -# inherit method implementations it provides that bidict can reuse (namely `setdefault`). -class bidict(BidictBase, MutableMapping): # noqa: N801; pylint: disable=invalid-name +class bidict(_DelegateKeysAndItemsToFwdm, MutableBidict): # noqa: N801,E501; pylint: disable=invalid-name """Base class for mutable bidirectional mappings.""" __slots__ = () __hash__ = None # since this class is mutable; explicit > implicit. - _ON_DUP_OVERWRITE = _OnDup(key=OVERWRITE, val=OVERWRITE, kv=OVERWRITE) - - def __delitem__(self, key): - u"""*x.__delitem__(y) ⟺ del x[y]*""" - self._pop(key) - - def __setitem__(self, key, val): - """ - Set the value for *key* to *val*. - - If *key* is already associated with *val*, this is a no-op. - - If *key* is already associated with a different value, - the old value will be replaced with *val*, - as with dict's :meth:`__setitem__`. - - If *val* is already associated with a different key, - an exception is raised - to protect against accidental removal of the key - that's currently associated with *val*. - - Use :meth:`put` instead if you want to specify different policy in - the case that the provided key or value duplicates an existing one. - Or use :meth:`forceput` to unconditionally associate *key* with *val*, - replacing any existing items as necessary to preserve uniqueness. - - :raises bidict.ValueDuplicationError: if *val* duplicates that of an - existing item. - - :raises bidict.KeyAndValueDuplicationError: if *key* duplicates the key of an - existing item and *val* duplicates the value of a different - existing item. - """ - on_dup = self._get_on_dup() - self._put(key, val, on_dup) - - def put(self, key, val, on_dup_key=RAISE, on_dup_val=RAISE, on_dup_kv=None): - """ - Associate *key* with *val* with the specified duplication policies. - - If *on_dup_kv* is ``None``, the *on_dup_val* policy will be used for it. - - For example, if all given duplication policies are :attr:`~bidict.RAISE`, - then *key* will be associated with *val* if and only if - *key* is not already associated with an existing value and - *val* is not already associated with an existing key, - otherwise an exception will be raised. - - If *key* is already associated with *val*, this is a no-op. - - :raises bidict.KeyDuplicationError: if attempting to insert an item - whose key only duplicates an existing item's, and *on_dup_key* is - :attr:`~bidict.RAISE`. - - :raises bidict.ValueDuplicationError: if attempting to insert an item - whose value only duplicates an existing item's, and *on_dup_val* is - :attr:`~bidict.RAISE`. - - :raises bidict.KeyAndValueDuplicationError: if attempting to insert an - item whose key duplicates one existing item's, and whose value - duplicates another existing item's, and *on_dup_kv* is - :attr:`~bidict.RAISE`. - """ - on_dup = self._get_on_dup((on_dup_key, on_dup_val, on_dup_kv)) - self._put(key, val, on_dup) - - def forceput(self, key, val): - """ - Associate *key* with *val* unconditionally. - - Replace any existing mappings containing key *key* or value *val* - as necessary to preserve uniqueness. - """ - self._put(key, val, self._ON_DUP_OVERWRITE) - - def clear(self): - """Remove all items.""" - self._fwdm.clear() - self._invm.clear() - - def pop(self, key, default=_MISS): - u"""*x.pop(k[, d]) → v* - - Remove specified key and return the corresponding value. - - :raises KeyError: if *key* is not found and no *default* is provided. - """ - try: - return self._pop(key) - except KeyError: - if default is _MISS: - raise - return default - - def popitem(self): - u"""*x.popitem() → (k, v)* - - Remove and return some item as a (key, value) pair. - - :raises KeyError: if *x* is empty. - """ - if not self: - raise KeyError('mapping is empty') - key, val = self._fwdm.popitem() - del self._invm[val] - return key, val - - def update(self, *args, **kw): - """Like :meth:`putall` with default duplication policies.""" - if args or kw: - self._update(False, None, *args, **kw) - - def forceupdate(self, *args, **kw): - """Like a bulk :meth:`forceput`.""" - self._update(False, self._ON_DUP_OVERWRITE, *args, **kw) - - def putall(self, items, on_dup_key=RAISE, on_dup_val=RAISE, on_dup_kv=None): - """ - Like a bulk :meth:`put`. - - If one of the given items causes an exception to be raised, - none of the items is inserted. - """ - if items: - on_dup = self._get_on_dup((on_dup_key, on_dup_val, on_dup_kv)) - self._update(False, on_dup, items) - # * Code review nav * #============================================================================== -# ← Prev: _frozenbidict.py Current: _bidict.py Next: _orderedbase.py → +# ← Prev: _mut.py Current: _bidict.py Next: _orderedbase.py → #============================================================================== diff --git a/bidict/_delegating_mixins.py b/bidict/_delegating_mixins.py new file mode 100644 index 0000000..05d7146 --- /dev/null +++ b/bidict/_delegating_mixins.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- +# Copyright 2009-2018 Joshua Bronson. All Rights Reserved. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +#============================================================================== +# * Welcome to the bidict source code * +#============================================================================== + +# Doing a code review? You'll find a "Code review nav" comment like the one +# below at the top and bottom of the most important source files. This provides +# a suggested initial path through the source when reviewing. +# +# Note: If you aren't reading this on https://github.com/jab/bidict, you may be +# viewing an outdated version of the code. Please head to GitHub to review the +# latest version, which contains important improvements over older versions. +# +# Thank you for reading and for any feedback you provide. + +# * Code review nav * +#============================================================================== +# ← Prev: _base.py Current: _delegating_mixins.py Next: _frozenbidict.py → +#============================================================================== + + +r"""Provides mixin classes that delegate to ``self._fwdm`` for various operations. + +This allows methods such as :meth:`bidict.bidict.items` +to be implemented in terms of a ``self._fwdm.items()`` call, +which is potentially much more efficient (e.g. in CPython 2) +compared to the implementation inherited from :class:`~collections.abc.Mapping` +(which returns ``[(key, self[key]) for key in self]`` in Python 2). + +Because this depends on implementation details that aren't necessarily true +(such as the bidict's values being the same as its ``self._fwdm.values()``, +which is not true for e.g. ordered bidicts where ``_fwdm``\'s values are nodes), +these should always be mixed in at a layer below a more general layer, +as they are in e.g. :class:`~bidict.frozenbidict` +which extends :class:`~bidict.BidictBase`. + +See the :ref:`extending:Sorted Bidict Recipes` +for another example of where this comes into play. +``SortedBidict`` extends :class:`bidict.MutableBidict` +rather than :class:`bidict.bidict` +to avoid inheriting these mixins, +which are incompatible with the backing +:class:`sortedcontainers.SortedDict`s. +""" + +from .compat import PY2 + + +_KEYS_METHODS = ('keys',) + (('viewkeys', 'iterkeys') if PY2 else ()) +_ITEMS_METHODS = ('items',) + (('viewitems', 'iteritems') if PY2 else ()) +_DOCSTRING_BY_METHOD = { + 'keys': 'A set-like object providing a view on the contained keys.', + 'items': 'A set-like object providing a view on the contained items.', +} +if PY2: + _DOCSTRING_BY_METHOD['viewkeys'] = _DOCSTRING_BY_METHOD['keys'] + _DOCSTRING_BY_METHOD['viewitems'] = _DOCSTRING_BY_METHOD['items'] + _DOCSTRING_BY_METHOD['keys'] = 'A list of the contained keys.' + _DOCSTRING_BY_METHOD['items'] = 'A list of the contained items.' + + +def _make_method(methodname): + def method(self): + return getattr(self._fwdm, methodname)() # pylint: disable=protected-access + method.__name__ = methodname + method.__doc__ = _DOCSTRING_BY_METHOD.get(methodname, '') + return method + + +def _make_fwdm_delegating_mixin(clsname, methodnames): + clsdict = dict({name: _make_method(name) for name in methodnames}, __slots__=()) + return type(clsname, (object,), clsdict) + + +_DelegateKeysToFwdm = _make_fwdm_delegating_mixin('_DelegateKeysToFwdm', _KEYS_METHODS) +_DelegateItemsToFwdm = _make_fwdm_delegating_mixin('_DelegateItemsToFwdm', _ITEMS_METHODS) +_DelegateKeysAndItemsToFwdm = type( + '_DelegateKeysAndItemsToFwdm', + (_DelegateKeysToFwdm, _DelegateItemsToFwdm), + {'__slots__': ()}) + +# * Code review nav * +#============================================================================== +# ← Prev: _base.py Current: _delegating_mixins.py Next: _frozenbidict.py → +#============================================================================== diff --git a/bidict/_dup.py b/bidict/_dup.py index 7912f0e..20db698 100644 --- a/bidict/_dup.py +++ b/bidict/_dup.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2018 Joshua Bronson. All Rights Reserved. +# Copyright 2009-2018 Joshua Bronson. All Rights Reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/bidict/_exc.py b/bidict/_exc.py index 2bda7f5..82dc023 100644 --- a/bidict/_exc.py +++ b/bidict/_exc.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2018 Joshua Bronson. All Rights Reserved. +# Copyright 2009-2018 Joshua Bronson. All Rights Reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/bidict/_frozenbidict.py b/bidict/_frozenbidict.py index 8ca1f1e..050f2c6 100644 --- a/bidict/_frozenbidict.py +++ b/bidict/_frozenbidict.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2018 Joshua Bronson. All Rights Reserved. +# Copyright 2009-2018 Joshua Bronson. All Rights Reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -22,16 +22,17 @@ # * Code review nav * #============================================================================== -# ← Prev: _base.py Current: _frozenbidict.py Next: _bidict.py → +# ← Prev: _delegating_mixins.py Current: _frozenbidict.py Next: _mut.py → #============================================================================== """Provides :class:`frozenbidict`, an immutable, hashable bidirectional mapping type.""" from ._base import BidictBase +from ._delegating_mixins import _DelegateKeysAndItemsToFwdm from .compat import ItemsView -class frozenbidict(BidictBase): # noqa: N801; pylint: disable=invalid-name +class frozenbidict(_DelegateKeysAndItemsToFwdm, BidictBase): # noqa: N801,E501; pylint: disable=invalid-name """Immutable, hashable bidict type.""" __slots__ = () @@ -46,5 +47,5 @@ class frozenbidict(BidictBase): # noqa: N801; pylint: disable=invalid-name # * Code review nav * #============================================================================== -# ← Prev: _base.py Current: _frozenbidict.py Next: _bidict.py → +# ← Prev: _delegating_mixins.py Current: _frozenbidict.py Next: _mut.py → #============================================================================== diff --git a/bidict/_frozenordered.py b/bidict/_frozenordered.py index 2d253c6..57b97b1 100644 --- a/bidict/_frozenordered.py +++ b/bidict/_frozenordered.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2018 Joshua Bronson. All Rights Reserved. +# Copyright 2009-2018 Joshua Bronson. All Rights Reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -27,57 +27,36 @@ """Provides :class:`FrozenOrderedBidict`, an immutable, hashable, ordered bidict.""" -from ._base import BidictBase +from ._delegating_mixins import _DelegateKeysToFwdm from ._frozenbidict import frozenbidict from ._orderedbase import OrderedBidictBase -from .compat import DICTS_ORDERED, ItemsView, PY2, izip +from .compat import DICTS_ORDERED, PY2, izip -# FrozenOrderedBidict intentionally does not subclass frozenbidict because it only complicates the -# inheritance hierarchy without providing any actual code reuse: The only thing from frozenbidict -# that FrozenOrderedBidict uses is frozenbidict.__hash__(), but Python specifically prevents -# __hash__ from being inherited; it must instead always be defined explicitly as below. Users who -# need an `is_frozenbidict(..)` test that succeeds for both frozenbidicts and FrozenOrderedBidicts -# should therefore not use isinstance(foo, frozenbidict), but should instead use the appropriate -# ABCs, e.g. `isinstance(foo, BidirectionalMapping) and not isinstance(foo, MutableMapping)`. -class FrozenOrderedBidict(OrderedBidictBase): - """Hashable, immutable, ordered bidict type.""" - - __slots__ = () - - # frozenbidict.__hash__ can be resued for FrozenOrderedBidict: +# If the Python implementation's dict type is ordered (e.g. PyPy or CPython >= 3.6), then +# `FrozenOrderedBidict` can delegate to `_fwdm` for keys: Both `_fwdm` and `_invm` will always +# be initialized with the provided items in the correct order, and since `FrozenOrderedBidict` +# is immutable, their respective orders can't get out of sync after a mutation. (Can't delegate +# to `_fwdm` for items though because values in `_fwdm` are nodes.) +_BASES = ((_DelegateKeysToFwdm,) if DICTS_ORDERED else ()) + (OrderedBidictBase,) +_CLSDICT = dict( + __slots__=(), + # Must set __hash__ explicitly, Python prevents inheriting it. + # frozenbidict.__hash__ can be reused for FrozenOrderedBidict: # FrozenOrderedBidict inherits BidictBase.__eq__ which is order-insensitive, # and frozenbidict.__hash__ is consistent with BidictBase.__eq__. - __hash__ = frozenbidict.__hash__ # Must define __hash__ explicitly, Python prevents inheriting - if PY2: - # Must grab the __func__ attribute off the method in Python 2, or else get "TypeError: - # unbound method __hash__() must be called with frozenbidict instance as first argument" - __hash__ = __hash__.__func__ + __hash__=frozenbidict.__hash__.__func__ if PY2 else frozenbidict.__hash__, + __doc__='Hashable, immutable, ordered bidict type.', + __module__=__name__, # Otherwise unpickling fails in Python 2. +) +# When PY2 (so we provide iteritems) and DICTS_ORDERED, e.g. on PyPy, the following implementation +# of iteritems may be more efficient than that inherited from `Mapping`. This exploits the property +# that the keys in `_fwdm` and `_invm` are already in the right order: +if PY2 and DICTS_ORDERED: + _CLSDICT['iteritems'] = lambda self: izip(self._fwdm, self._invm) # noqa: E501; pylint: disable=protected-access -if DICTS_ORDERED: - # If the Python implementation's dict type is ordered (e.g. PyPy or CPython >= 3.6), then - # `FrozenOrderedBidict` can set `__delegate__` to `_fwdm`, allowing the more efficient - # implementations of `keys` and `values`, rather than the less efficient implementations - # inherited from `Mapping.keys` and `OrderedBidictBase.values`. - # Both the `_fwdm` and `_invm` backing dicts will always be initialized with the provided - # items in the correct order, and since `FrozenOrderedBidict` is immutable, their respective - # orders can't get out of sync after a mutation, like they can with a mutable `OrderedBidict`. - FrozenOrderedBidict.__delegate__ = property(lambda self: self._fwdm) # noqa: E501; pylint: disable=protected-access - - # (`FrozenOrderedBidict` can't use the more efficient `_fwdm.items` and `_fwdm.viewitems` - # implementations because the values in `_fwdm` are nodes. - FrozenOrderedBidict.items = super(BidictBase, FrozenOrderedBidict).items - FrozenOrderedBidict.viewitems = lambda s: ItemsView(s) # pylint: disable=unnecessary-lambda - - if PY2: - # We can do better than the `iteritems` implementation inherited from `Mapping`; - # zipping together the keys from `_fwdm` and `_invm` runs faster (e.g. C speed on CPython). - def iteritems(self): - """An iterator over the contained items.""" - return izip(self._fwdm, self._invm) # pylint: disable=protected-access - - FrozenOrderedBidict.iteritems = iteritems +FrozenOrderedBidict = type('FrozenOrderedBidict', _BASES, _CLSDICT) # pylint: disable=invalid-name # * Code review nav * diff --git a/bidict/_marker.py b/bidict/_marker.py index a55c4ef..74bcdb4 100644 --- a/bidict/_marker.py +++ b/bidict/_marker.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2018 Joshua Bronson. All Rights Reserved. +# Copyright 2009-2018 Joshua Bronson. All Rights Reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/bidict/_miss.py b/bidict/_miss.py index 92be75c..c76bc6a 100644 --- a/bidict/_miss.py +++ b/bidict/_miss.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2018 Joshua Bronson. All Rights Reserved. +# Copyright 2009-2018 Joshua Bronson. All Rights Reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/bidict/_mut.py b/bidict/_mut.py new file mode 100644 index 0000000..72f2558 --- /dev/null +++ b/bidict/_mut.py @@ -0,0 +1,177 @@ +# -*- coding: utf-8 -*- +# Copyright 2009-2018 Joshua Bronson. All Rights Reserved. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +#============================================================================== +# * Welcome to the bidict source code * +#============================================================================== + +# Doing a code review? You'll find a "Code review nav" comment like the one +# below at the top and bottom of the most important source files. This provides +# a suggested initial path through the source when reviewing. +# +# Note: If you aren't reading this on https://github.com/jab/bidict, you may be +# viewing an outdated version of the code. Please head to GitHub to review the +# latest version, which contains important improvements over older versions. +# +# Thank you for reading and for any feedback you provide. + +# * Code review nav * +#============================================================================== +# ← Prev: _frozenbidict.py Current: _mut.py Next: _bidict.py → +#============================================================================== + + +"""Provides :class:`bidict`.""" + +from ._base import BidictBase +from ._dup import OVERWRITE, RAISE, _OnDup +from ._miss import _MISS +from .compat import MutableMapping + + +# Extend MutableMapping explicitly because it doesn't implement __subclasshook__, as well as to +# inherit method implementations it provides that we can reuse (namely `setdefault`). +class MutableBidict(BidictBase, MutableMapping): + """Base class for mutable bidirectional mappings.""" + + __slots__ = () + + __hash__ = None # since this class is mutable; explicit > implicit. + + _ON_DUP_OVERWRITE = _OnDup(key=OVERWRITE, val=OVERWRITE, kv=OVERWRITE) + + def __delitem__(self, key): + u"""*x.__delitem__(y) ⟺ del x[y]*""" + self._pop(key) + + def __setitem__(self, key, val): + """ + Set the value for *key* to *val*. + + If *key* is already associated with *val*, this is a no-op. + + If *key* is already associated with a different value, + the old value will be replaced with *val*, + as with dict's :meth:`__setitem__`. + + If *val* is already associated with a different key, + an exception is raised + to protect against accidental removal of the key + that's currently associated with *val*. + + Use :meth:`put` instead if you want to specify different policy in + the case that the provided key or value duplicates an existing one. + Or use :meth:`forceput` to unconditionally associate *key* with *val*, + replacing any existing items as necessary to preserve uniqueness. + + :raises bidict.ValueDuplicationError: if *val* duplicates that of an + existing item. + + :raises bidict.KeyAndValueDuplicationError: if *key* duplicates the key of an + existing item and *val* duplicates the value of a different + existing item. + """ + on_dup = self._get_on_dup() + self._put(key, val, on_dup) + + def put(self, key, val, on_dup_key=RAISE, on_dup_val=RAISE, on_dup_kv=None): + """ + Associate *key* with *val* with the specified duplication policies. + + If *on_dup_kv* is ``None``, the *on_dup_val* policy will be used for it. + + For example, if all given duplication policies are :attr:`~bidict.RAISE`, + then *key* will be associated with *val* if and only if + *key* is not already associated with an existing value and + *val* is not already associated with an existing key, + otherwise an exception will be raised. + + If *key* is already associated with *val*, this is a no-op. + + :raises bidict.KeyDuplicationError: if attempting to insert an item + whose key only duplicates an existing item's, and *on_dup_key* is + :attr:`~bidict.RAISE`. + + :raises bidict.ValueDuplicationError: if attempting to insert an item + whose value only duplicates an existing item's, and *on_dup_val* is + :attr:`~bidict.RAISE`. + + :raises bidict.KeyAndValueDuplicationError: if attempting to insert an + item whose key duplicates one existing item's, and whose value + duplicates another existing item's, and *on_dup_kv* is + :attr:`~bidict.RAISE`. + """ + on_dup = self._get_on_dup((on_dup_key, on_dup_val, on_dup_kv)) + self._put(key, val, on_dup) + + def forceput(self, key, val): + """ + Associate *key* with *val* unconditionally. + + Replace any existing mappings containing key *key* or value *val* + as necessary to preserve uniqueness. + """ + self._put(key, val, self._ON_DUP_OVERWRITE) + + def clear(self): + """Remove all items.""" + self._fwdm.clear() + self._invm.clear() + + def pop(self, key, default=_MISS): + u"""*x.pop(k[, d]) → v* + + Remove specified key and return the corresponding value. + + :raises KeyError: if *key* is not found and no *default* is provided. + """ + try: + return self._pop(key) + except KeyError: + if default is _MISS: + raise + return default + + def popitem(self): + u"""*x.popitem() → (k, v)* + + Remove and return some item as a (key, value) pair. + + :raises KeyError: if *x* is empty. + """ + if not self: + raise KeyError('mapping is empty') + key, val = self._fwdm.popitem() + del self._invm[val] + return key, val + + def update(self, *args, **kw): + """Like :meth:`putall` with default duplication policies.""" + if args or kw: + self._update(False, None, *args, **kw) + + def forceupdate(self, *args, **kw): + """Like a bulk :meth:`forceput`.""" + self._update(False, self._ON_DUP_OVERWRITE, *args, **kw) + + def putall(self, items, on_dup_key=RAISE, on_dup_val=RAISE, on_dup_kv=None): + """ + Like a bulk :meth:`put`. + + If one of the given items causes an exception to be raised, + none of the items is inserted. + """ + if items: + on_dup = self._get_on_dup((on_dup_key, on_dup_val, on_dup_kv)) + self._update(False, on_dup, items) + + +# * Code review nav * +#============================================================================== +# ← Prev: _frozenbidict.py Current: _mut.py Next: _bidict.py → +#============================================================================== diff --git a/bidict/_named.py b/bidict/_named.py index 03ffc66..e0772e3 100644 --- a/bidict/_named.py +++ b/bidict/_named.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2018 Joshua Bronson. All Rights Reserved. +# Copyright 2009-2018 Joshua Bronson. All Rights Reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -55,7 +55,7 @@ def namedbidict(typename, keyname, valname, base_type=bidict): if not all(map(_VALID_NAME.match, names)) or keyname == valname: raise ValueError(names) - class _Named(base_type): + class _Named(base_type): # pylint: disable=too-many-ancestors __slots__ = () diff --git a/bidict/_noop.py b/bidict/_noop.py index 2fef6a2..8618f5b 100644 --- a/bidict/_noop.py +++ b/bidict/_noop.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2018 Joshua Bronson. All Rights Reserved. +# Copyright 2009-2018 Joshua Bronson. All Rights Reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/bidict/_orderedbase.py b/bidict/_orderedbase.py index b49be10..25b1ab6 100644 --- a/bidict/_orderedbase.py +++ b/bidict/_orderedbase.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2018 Joshua Bronson. All Rights Reserved. +# Copyright 2009-2018 Joshua Bronson. All Rights Reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -22,7 +22,7 @@ # * Code review nav * #============================================================================== -# ← Prev: _bidict.py Current: _orderedbase.py Next: _frozenordered.py → +# ← Prev: _bidict.py Current: _orderedbase.py Next: _frozenordered.py → #============================================================================== @@ -138,14 +138,8 @@ class OrderedBidictBase(BidictBase): # lgtm [py/missing-equals] _fwdm_cls = bidict _invm_cls = bidict - #: Can't delegate to e.g. :attr:`_fwdm` for more efficient implementations - #: of methods like :meth:`keys` because it isn't ordered. - #: Set to None to opt out of optimized-method delegation and - #: use default implementations from :class:`Mapping` instead. - __delegate__ = None - #: The object used by :meth:`__repr__` for printing the contained items. - __repr_delegate__ = list + _repr_delegate = list def __init__(self, *args, **kw): """Make a new ordered bidirectional mapping. @@ -303,5 +297,5 @@ class OrderedBidictBase(BidictBase): # lgtm [py/missing-equals] # * Code review nav * #============================================================================== -# ← Prev: _bidict.py Current: _orderedbase.py Next: _frozenordered.py → +# ← Prev: _bidict.py Current: _orderedbase.py Next: _frozenordered.py → #============================================================================== diff --git a/bidict/_orderedbidict.py b/bidict/_orderedbidict.py index 3611fab..3fff763 100644 --- a/bidict/_orderedbidict.py +++ b/bidict/_orderedbidict.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2018 Joshua Bronson. All Rights Reserved. +# Copyright 2009-2018 Joshua Bronson. All Rights Reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -28,16 +28,14 @@ """Provides :class:`OrderedBidict`.""" -from ._bidict import bidict +from ._mut import MutableBidict from ._orderedbase import OrderedBidictBase -# Inherit ordered behavior from OrderedBidict and mutable behavior from bidict. -class OrderedBidict(OrderedBidictBase, bidict): +class OrderedBidict(OrderedBidictBase, MutableBidict): """Mutable bidict type that maintains items in insertion order.""" __slots__ = () - __hash__ = None # since this class is mutable; explicit > implicit. def clear(self): diff --git a/bidict/_util.py b/bidict/_util.py index 429ec91..1bd248a 100644 --- a/bidict/_util.py +++ b/bidict/_util.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2018 Joshua Bronson. All Rights Reserved. +# Copyright 2009-2018 Joshua Bronson. All Rights Reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/bidict/compat.py b/bidict/compat.py index 5349dbc..5371ffc 100644 --- a/bidict/compat.py +++ b/bidict/compat.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2018 Joshua Bronson. All Rights Reserved. +# Copyright 2009-2018 Joshua Bronson. All Rights Reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/bidict/metadata.py b/bidict/metadata.py index 17f97b4..4452009 100644 --- a/bidict/metadata.py +++ b/bidict/metadata.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2018 Joshua Bronson. All Rights Reserved. +# Copyright 2009-2018 Joshua Bronson. All Rights Reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/build-docs.sh b/build-docs.sh index 7f49161..34c26ef 100755 --- a/build-docs.sh +++ b/build-docs.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# Copyright 2018 Joshua Bronson. All Rights Reserved. +# Copyright 2009-2018 Joshua Bronson. All Rights Reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/docs/conf.py b/docs/conf.py index 92264c1..86dad0a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2018 Joshua Bronson. All Rights Reserved. +# Copyright 2009-2018 Joshua Bronson. All Rights Reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/docs/extending.rst b/docs/extending.rst index c3321ee..2dc1e31 100644 --- a/docs/extending.rst +++ b/docs/extending.rst @@ -87,16 +87,23 @@ creating a sorted bidict type is dead simple: .. doctest:: + >>> # As an optimization, bidict.bidict includes a mixin class that + >>> # we can't use here (namely bidict._delegating_mixins._DelegateKeysAndItemsToFwdm), + >>> # so extend the parent class, bidict.MutableBidict, instead. + >>> from bidict import MutableBidict + >>> import sortedcontainers - >>> # a sorted bidict whose forward items stay sorted by their keys, - >>> # and whose inverse items stay sorted by *their* keys (i.e. it and - >>> # its inverse iterate over their items in different orders): - - >>> class SortedBidict(bidict): + >>> class SortedBidict(MutableBidict): + ... """A sorted bidict whose forward items stay sorted by their keys, + ... and whose inverse items stay sorted by *their* keys. + ... Note: As a result, an instance and its inverse yield their items + ... in different orders. + ... """ + ... ... _fwdm_cls = sortedcontainers.SortedDict ... _invm_cls = sortedcontainers.SortedDict - ... __repr_delegate__ = list + ... _repr_delegate = list >>> b = SortedBidict({'Tokyo': 'Japan', 'Cairo': 'Egypt'}) >>> b @@ -108,21 +115,23 @@ creating a sorted bidict type is dead simple: >>> list(b.items()) [('Cairo', 'Egypt'), ('Lima', 'Peru'), ('Tokyo', 'Japan')] - >>> # b.inv stays sorted by *its* keys (b's values!) + >>> # b.inv stays sorted by *its* keys (b's values) >>> list(b.inv.items()) [('Egypt', 'Cairo'), ('Japan', 'Tokyo'), ('Peru', 'Lima')] - >>> # a sorted bidict whose forward items stay sorted by their keys, - >>> # and whose inverse items stay sorted by their values (i.e. it and - >>> # its inverse iterate over their items in the same order): +Here's a recipe for a sorted bidict whose forward items stay sorted by their keys, +and whose inverse items stay sorted by their values. i.e. An instance and its inverse +will yield their items in *the same* order: + +.. doctest:: >>> import sortedcollections - >>> class KeySortedBidict(bidict): + >>> class KeySortedBidict(MutableBidict): ... _fwdm_cls = sortedcontainers.SortedDict ... _invm_cls = sortedcollections.ValueSortedDict - ... __repr_delegate__ = list + ... _repr_delegate = list >>> element_by_atomic_number = KeySortedBidict({ ... 3: 'lithium', 1: 'hydrogen', 2: 'helium'}) diff --git a/run-tests.py b/run-tests.py index 7d352d5..e748fa3 100755 --- a/run-tests.py +++ b/run-tests.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright 2018 Joshua Bronson. All Rights Reserved. +# Copyright 2009-2018 Joshua Bronson. All Rights Reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/setup.cfg b/setup.cfg index 9739b12..d2c784f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,4 +1,4 @@ -# Copyright 2018 Joshua Bronson. All Rights Reserved. +# Copyright 2009-2018 Joshua Bronson. All Rights Reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/setup.py b/setup.py index 808f7d6..e8b949b 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2018 Joshua Bronson. All Rights Reserved. +# Copyright 2009-2018 Joshua Bronson. All Rights Reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -52,7 +52,7 @@ TEST_REQS = [ 'hypothesis < 4', 'hypothesis-pytest < 1', 'py < 2', - 'pytest < 4', + 'pytest < 5', 'pytest-benchmark < 4', 'sortedcollections < 2', 'sortedcontainers < 3', diff --git a/tests/test_benchmark.py b/tests/test_benchmark.py index 150ea93..44df8fa 100644 --- a/tests/test_benchmark.py +++ b/tests/test_benchmark.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2018 Joshua Bronson. All Rights Reserved. +# Copyright 2009-2018 Joshua Bronson. All Rights Reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/tests/test_bidict.txt b/tests/test_bidict.txt index 687f46b..7d986fd 100644 --- a/tests/test_bidict.txt +++ b/tests/test_bidict.txt @@ -1,4 +1,4 @@ -# Copyright 2018 Joshua Bronson. All Rights Reserved. +# Copyright 2009-2018 Joshua Bronson. All Rights Reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/tests/test_class_relationships.py b/tests/test_class_relationships.py index c7bd12e..7b8f202 100644 --- a/tests/test_class_relationships.py +++ b/tests/test_class_relationships.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2018 Joshua Bronson. All Rights Reserved. +# Copyright 2009-2018 Joshua Bronson. All Rights Reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -111,16 +111,21 @@ def test_issubclass_internal(): The relationships tested here are not guaranteed to hold in the future, but are still tested so that any unintentional changes won't go unnoticed. """ - assert issubclass(OrderedBidict, bidict) - - assert not issubclass(frozenbidict, bidict) - assert not issubclass(FrozenOrderedBidict, bidict) - + assert not issubclass(bidict, FrozenOrderedBidict) + assert not issubclass(bidict, OrderedBidict) assert not issubclass(bidict, frozenbidict) - assert not issubclass(OrderedBidict, FrozenOrderedBidict) - assert not issubclass(FrozenOrderedBidict, frozenbidict) assert not issubclass(FrozenOrderedBidict, OrderedBidict) + assert not issubclass(FrozenOrderedBidict, bidict) + assert not issubclass(FrozenOrderedBidict, frozenbidict) + + assert not issubclass(OrderedBidict, FrozenOrderedBidict) + assert not issubclass(OrderedBidict, bidict) + assert not issubclass(OrderedBidict, frozenbidict) + + assert not issubclass(frozenbidict, FrozenOrderedBidict) + assert not issubclass(frozenbidict, OrderedBidict) + assert not issubclass(frozenbidict, bidict) def test_abstract_bimap_init_fails(): diff --git a/tests/test_hypothesis.py b/tests/test_hypothesis.py index feb68f5..aaf6680 100644 --- a/tests/test_hypothesis.py +++ b/tests/test_hypothesis.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2018 Joshua Bronson. All Rights Reserved. +# Copyright 2009-2018 Joshua Bronson. All Rights Reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/tests/test_metadata.py b/tests/test_metadata.py index 4bb7afc..5e2e219 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2018 Joshua Bronson. All Rights Reserved. +# Copyright 2009-2018 Joshua Bronson. All Rights Reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/tests/test_orderedbidict.txt b/tests/test_orderedbidict.txt index 2ae0679..d9d9192 100644 --- a/tests/test_orderedbidict.txt +++ b/tests/test_orderedbidict.txt @@ -1,4 +1,4 @@ -# Copyright 2018 Joshua Bronson. All Rights Reserved. +# Copyright 2009-2018 Joshua Bronson. All Rights Reserved. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this