mirror of https://github.com/jab/bidict.git
Add tests for better bidict.compat.{iter,view}* compatibility
No longer assume the target object implements these methods on Python 2. Update compat autodocs.
This commit is contained in:
parent
3a8339d29c
commit
ba6e1fff13
|
@ -58,7 +58,7 @@ Minor code, interop, and (semi-)private API improvements.
|
|||
version (from v1 to v2)
|
||||
for the :ref:`extending:Sorted Bidict Recipes`.
|
||||
|
||||
- ``bidict.compat.{view,iter}{keys,values,items}`` on Python2
|
||||
- ``bidict.compat.{view,iter}{keys,values,items}`` on Python 2
|
||||
no longer assumes the target object implements these methods,
|
||||
as they're not actually part of the
|
||||
:class:`~collections.abc.Mapping` interface,
|
||||
|
|
|
@ -6,57 +6,23 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
|
||||
u"""Compatibility helpers.
|
||||
|
||||
.. py:attribute:: PY2
|
||||
|
||||
True iff running on Python < 3.
|
||||
|
||||
.. py:attribute:: PYPY
|
||||
|
||||
True iff running on PyPy.
|
||||
|
||||
.. py:attribute:: viewkeys
|
||||
|
||||
``viewkeys(x) → x.viewkeys() if PY2 else x.keys()``
|
||||
|
||||
.. py:attribute:: viewvalues
|
||||
|
||||
``viewvalues(x) → x.viewvalues() if PY2 else x.values()``
|
||||
|
||||
.. py:attribute:: viewitems
|
||||
|
||||
``viewitems(x) → x.viewitems() if PY2 else x.items()``
|
||||
|
||||
.. py:attribute:: iterkeys
|
||||
|
||||
``iterkeys(x) → x.iterkeys() if PY2 else iter(x.keys())``
|
||||
|
||||
.. py:attribute:: itervalues
|
||||
|
||||
``itervalues(x) → x.itervalues() if PY2 else iter(x.values())``
|
||||
|
||||
.. py:attribute:: iteritems
|
||||
|
||||
``iteritems(x) → x.iteritems() if PY2 else iter(x.items())``
|
||||
|
||||
.. py:attribute:: izip
|
||||
|
||||
``itertools.izip() if PY2 else zip``
|
||||
|
||||
"""
|
||||
"""Compatibility helpers."""
|
||||
|
||||
from operator import methodcaller
|
||||
from platform import python_implementation
|
||||
from sys import version_info
|
||||
from warnings import warn
|
||||
|
||||
PYMAJOR, PYMINOR = version_info[:2]
|
||||
PY2 = PYMAJOR == 2
|
||||
PYIMPL = python_implementation()
|
||||
CPY = PYIMPL == 'CPython'
|
||||
PYPY = PYIMPL == 'PyPy'
|
||||
DICTS_ORDERED = PYPY or (CPY and (PYMAJOR, PYMINOR) >= (3, 6))
|
||||
|
||||
# Use #: (before or) at the end of each line with a member we want to show up in the docs,
|
||||
# otherwise Sphinx won't include (even though we configure automodule with undoc-members).
|
||||
|
||||
PYMAJOR, PYMINOR = version_info[:2] #:
|
||||
PY2 = PYMAJOR == 2 #:
|
||||
PYIMPL = python_implementation() #:
|
||||
CPY = PYIMPL == 'CPython' #:
|
||||
PYPY = PYIMPL == 'PyPy' #:
|
||||
DICTS_ORDERED = PYPY or (CPY and (PYMAJOR, PYMINOR) >= (3, 6)) #:
|
||||
|
||||
# Without the following, pylint gives lots of false positives.
|
||||
# pylint: disable=invalid-name,unused-import,ungrouped-imports,no-name-in-module
|
||||
|
@ -68,22 +34,22 @@ if PY2:
|
|||
|
||||
# abstractproperty deprecated in Python 3.3 in favor of using @property with @abstractmethod.
|
||||
# Before 3.3, this silently fails to detect when an abstract property has not been overridden.
|
||||
from abc import abstractproperty
|
||||
from abc import abstractproperty #:
|
||||
|
||||
from itertools import izip
|
||||
from itertools import izip #:
|
||||
|
||||
# In Python 3, the collections ABCs were moved into collections.abc, which does not exist in
|
||||
# Python 2. Support for importing them directly from collections is dropped in Python 3.8.
|
||||
from collections import ( # noqa: F401 (imported but unused)
|
||||
Mapping, MutableMapping, KeysView, ValuesView, ItemsView)
|
||||
|
||||
viewkeys = lambda m: m.viewkeys() if hasattr(m, 'viewkeys') else KeysView(m)
|
||||
viewvalues = lambda m: m.viewvalues() if hasattr(m, 'viewvalues') else ValuesView(m)
|
||||
viewitems = lambda m: m.viewitems() if hasattr(m, 'viewitems') else ItemsView(m)
|
||||
viewkeys = lambda m: m.viewkeys() if hasattr(m, 'viewkeys') else KeysView(m) #:
|
||||
viewvalues = lambda m: m.viewvalues() if hasattr(m, 'viewvalues') else ValuesView(m) #:
|
||||
viewitems = lambda m: m.viewitems() if hasattr(m, 'viewitems') else ItemsView(m) #:
|
||||
|
||||
iterkeys = lambda m: m.iterkeys() if hasattr(m, 'iterkeys') else iter(m.keys())
|
||||
itervalues = lambda m: m.itervalues() if hasattr(m, 'itervalues') else iter(m.values())
|
||||
iteritems = lambda m: m.iteritems() if hasattr(m, 'iteritems') else iter(m.items())
|
||||
iterkeys = lambda m: m.iterkeys() if hasattr(m, 'iterkeys') else iter(m.keys()) #:
|
||||
itervalues = lambda m: m.itervalues() if hasattr(m, 'itervalues') else iter(m.values()) #:
|
||||
iteritems = lambda m: m.iteritems() if hasattr(m, 'iteritems') else iter(m.items()) #:
|
||||
|
||||
else:
|
||||
# Assume Python 3 when not PY2, but explicitly check before showing this warning.
|
||||
|
@ -93,18 +59,18 @@ else:
|
|||
from collections.abc import ( # noqa: F401 (imported but unused)
|
||||
Mapping, MutableMapping, KeysView, ValuesView, ItemsView)
|
||||
|
||||
viewkeys = methodcaller('keys')
|
||||
viewvalues = methodcaller('values')
|
||||
viewitems = methodcaller('items')
|
||||
viewkeys = methodcaller('keys') #:
|
||||
viewvalues = methodcaller('values') #:
|
||||
viewitems = methodcaller('items') #:
|
||||
|
||||
def _compose(f, g):
|
||||
return lambda x: f(g(x))
|
||||
|
||||
iterkeys = _compose(iter, viewkeys)
|
||||
itervalues = _compose(iter, viewvalues)
|
||||
iteritems = _compose(iter, viewitems)
|
||||
iterkeys = _compose(iter, viewkeys) #:
|
||||
itervalues = _compose(iter, viewvalues) #:
|
||||
iteritems = _compose(iter, viewitems) #:
|
||||
|
||||
from abc import abstractmethod
|
||||
abstractproperty = _compose(property, abstractmethod)
|
||||
abstractproperty = _compose(property, abstractmethod) #:
|
||||
|
||||
izip = zip
|
||||
izip = zip #:
|
||||
|
|
|
@ -38,3 +38,5 @@ bidict.compat
|
|||
|
||||
.. automodule:: bidict.compat
|
||||
:members:
|
||||
:member-order: bysource
|
||||
:undoc-members:
|
||||
|
|
|
@ -166,10 +166,7 @@ creating a sorted bidict type is dead simple:
|
|||
... try:
|
||||
... return object.__getattribute__(self, name)
|
||||
... except AttributeError as e:
|
||||
... try:
|
||||
... return getattr(self._fwdm, name)
|
||||
... except AttributeError:
|
||||
... raise e
|
||||
... return getattr(self._fwdm, name)
|
||||
|
||||
>>> KeySortedBidict.__getattribute__ = __getattribute__
|
||||
|
||||
|
|
|
@ -243,6 +243,36 @@ Python 2 dict "view*" APIs are supported::
|
|||
>>> sorted(ob.viewitems()) == sorted(ob.items()) if PY2 else True
|
||||
True
|
||||
|
||||
On Python 2, bidict.compat.{iter,view}* functions should still work
|
||||
even if passed a Mapping that does not provide corresponding methods
|
||||
(e.g. a sortedcontainers.SortedDict, see
|
||||
https://github.com/grantjenks/python-sortedcontainers/pull/106#issuecomment-435631649)::
|
||||
|
||||
>>> class MissingViewMethodsDict(dict):
|
||||
... def __getattribute__(self, attr):
|
||||
... if attr in (
|
||||
... 'iterkeys', 'itervalues', 'iteritems',
|
||||
... 'viewkeys', 'viewvalues', 'viewitems'):
|
||||
... raise AttributeError(attr)
|
||||
... return object.__getattribute__(self, attr)
|
||||
|
||||
>>> mvmdict = MissingViewMethodsDict({'a': 'A', 'b': 'B'})
|
||||
>>> from bidict.compat import (
|
||||
... iterkeys, itervalues, iteritems,
|
||||
... viewkeys, viewvalues, viewitems, KeysView, ValuesView, ItemsView)
|
||||
>>> set(iterkeys(mvmdict)) == set(mvmdict.keys())
|
||||
True
|
||||
>>> set(itervalues(mvmdict)) == set(mvmdict.values())
|
||||
True
|
||||
>>> set(iteritems(mvmdict)) == set(mvmdict.items())
|
||||
True
|
||||
>>> viewkeys(mvmdict) == KeysView(mvmdict)
|
||||
True
|
||||
>>> set(viewvalues(mvmdict)) == set(ValuesView(mvmdict)) # ValuesView is not a Set, must wrap
|
||||
True
|
||||
>>> viewitems(mvmdict) == ItemsView(mvmdict)
|
||||
True
|
||||
|
||||
Make sure copy.copy ends up calling BidictBase.__copy__
|
||||
(should show up in the coverage report)::
|
||||
|
||||
|
|
Loading…
Reference in New Issue