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:
jab 2018-11-14 02:38:59 +00:00
parent 3a8339d29c
commit ba6e1fff13
5 changed files with 61 additions and 66 deletions

View File

@ -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,

View File

@ -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 #:

View File

@ -38,3 +38,5 @@ bidict.compat
.. automodule:: bidict.compat
:members:
:member-order: bysource
:undoc-members:

View File

@ -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__

View File

@ -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)::