Skip to content

Commit

Permalink
resurrect delegating mixins instead of __delegate__ + other refinements
Browse files Browse the repository at this point in the history
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
  • Loading branch information
jab committed Nov 19, 2018
1 parent e70816c commit d13fd9b
Show file tree
Hide file tree
Showing 35 changed files with 422 additions and 307 deletions.
32 changes: 26 additions & 6 deletions CHANGELOG.rst
Expand Up @@ -22,6 +22,26 @@ Tip: `Subscribe to bidict releases <https://libraries.io/pypi/bidict>`__
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)
-------------------

Expand All @@ -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`)
Expand All @@ -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 <https://github.com/grantjenks/python-sortedcontainers>`__
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions README.rst
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion 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
Expand Down
2 changes: 1 addition & 1 deletion 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
Expand Down
4 changes: 3 additions & 1 deletion 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
Expand Down Expand Up @@ -44,6 +44,7 @@

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 (
Expand Down Expand Up @@ -84,6 +85,7 @@
'ValueDuplicationError',
'KeyAndValueDuplicationError',
'BidictBase',
'MutableBidict',
'frozenbidict',
'bidict',
'namedbidict',
Expand Down
2 changes: 1 addition & 1 deletion 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
Expand Down
73 changes: 19 additions & 54 deletions 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
Expand All @@ -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 →
#==============================================================================


Expand All @@ -38,7 +38,7 @@
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
Expand All @@ -52,10 +52,7 @@
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)
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -194,7 +191,7 @@ def __repr__(self):
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
Expand Down Expand Up @@ -400,28 +397,16 @@ def __len__(self):
"""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.
Expand All @@ -434,18 +419,16 @@ def values(self):
"""
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()
Expand All @@ -454,25 +437,7 @@ def viewvalues(self): # noqa: D102; pylint: disable=missing-docstring
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
Expand All @@ -487,5 +452,5 @@ def __ne__(self, other): # noqa: N802

# * Code review nav *
#==============================================================================
# ← Prev: _abc.py Current: _base.py Next: _frozenbidict.py →
# ← Prev: _abc.py Current: _base.py Next: _delegating_mixins.py →
#==============================================================================

0 comments on commit d13fd9b

Please sign in to comment.