Skip to content

Commit

Permalink
rm OnDup.kv
Browse files Browse the repository at this point in the history
  • Loading branch information
jab committed Jan 9, 2024
1 parent 678c007 commit 1b92df4
Show file tree
Hide file tree
Showing 9 changed files with 89 additions and 82 deletions.
55 changes: 34 additions & 21 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,35 @@ please consider sponsoring bidict on GitHub.`
0.23.0 (not yet released)
-------------------------

The changes in this release are expected to affect few users.
Primarily, this release simplifies bidict by removing features
that few if any users depend on.

It also contains a few other minor improvements.

- Drop support for Python 3.7,
which reached end of life on 2023-06-27,
and take advantage of features available in Python 3.8+.

- Test with Python 3.12 in CI.
- Remove ``FrozenOrderedBidict`` now that Python 3.7 is no longer supported.
:class:`~bidict.frozenbidict` now provides everything
that ``FrozenOrderedBidict`` provided
(including :class:`reversibility <collections.abc.Reversible>`)
on all supported Python versions,
but with less space overhead.

Note: Older versions of bidict also support Python 3.12,
even though they don't explicitly declare support for it.
- Remove ``namedbidict`` due to low usage.

- Drop use of `Trove classifiers <https://github.com/pypa/trove-classifiers>`__
to explicitly declare support for specific Python versions in package metadata.
- Remove the ``kv`` field of :class:`~bidict.OnDup`
which specified the :class:`~bidict.OnDupAction` to take
in the case of :ref:`basic-usage:key and value duplication`.
The :attr:`~bidict.OnDup.val` field now specifies the action to take
in the case of
:ref:`basic-usage:key and value duplication`
as well as
:ref:`just value duplication <basic-usage:values must be unique>`.

- Fix a bug where e.g. ``bidict(None)`` would incorrectly return an empty bidict
rather than raising :class:`TypeError`.

- All :meth:`~bidict.bidict.__init__`,
:meth:`~bidict.bidict.update`,
Expand All @@ -50,26 +66,23 @@ The changes in this release are expected to affect few users.
<https://github.com/python/cpython/blob/v3.11.5/Lib/_collections_abc.py#L943>`__ does,
before falling back to handling the provided object as an iterable of pairs.

- Remove ``FrozenOrderedBidict`` now that Python 3.7 is no longer supported.
:class:`~bidict.frozenbidict` now provides everything
that ``FrozenOrderedBidict`` provided
(including :class:`reversibility <collections.abc.Reversible>`)
on all supported Python versions,
but with less space overhead.

- Remove ``namedbidict`` due to low usage.

- Fix a bug where e.g. ``bidict(None)`` would incorrectly return an empty bidict
rather than raising :class:`TypeError`.

- The :func:`repr` of ordered bidicts now matches that of regular bidicts,
e.g. ``OrderedBidict({1: 1})`` rather than ``OrderedBidict([(1, 1)])``
(and accordingly, ``__repr_delegate__`` has been removed
as it's no longer needed).
e.g. ``OrderedBidict({1: 1})`` rather than ``OrderedBidict([(1, 1)])``.

(Accordingly, the ``bidict.__repr_delegate__`` field has been removed
now that it's no longer needed.)

This tracks with the change to :class:`collections.OrderedDict`\'s :func:`repr`
`in Python 3.12 <https://github.com/python/cpython/pull/101661>`__.

- Test with Python 3.12 in CI.

Note: Older versions of bidict also support Python 3.12,
even though they don't explicitly declare support for it.

- Drop use of `Trove classifiers <https://github.com/pypa/trove-classifiers>`__
that declare support for specific Python versions in package metadata.


0.22.1 (2022-12-31)
-------------------
Expand Down
6 changes: 3 additions & 3 deletions bidict/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,11 +339,11 @@ def _dedup(self, key: KT, val: VT, on_dup: OnDup) -> DedupResult[KT, VT]:
# (key, val) duplicates an existing item -> no-op.
return None
# key and val each duplicate a different existing item.
if on_dup.kv is RAISE:
if on_dup.val is RAISE:
raise KeyAndValueDuplicationError(key, val)
if on_dup.kv is DROP_NEW:
if on_dup.val is DROP_NEW:
return None
assert on_dup.kv is DROP_OLD
assert on_dup.val is DROP_OLD
# Fall through to the return statement on the last line.
elif isdupkey:
if on_dup.key is RAISE:
Expand Down
2 changes: 1 addition & 1 deletion bidict/_bidict.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def put(self, key: KT, val: VT, on_dup: OnDup = ON_DUP_RAISE) -> None:
: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
duplicates another existing item's, and *on_dup.val* is
:attr:`~bidict.RAISE`.
"""
self._update([(key, val)], on_dup=on_dup)
Expand Down
26 changes: 14 additions & 12 deletions bidict/_dup.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,28 +33,30 @@ def __repr__(self) -> str:
DROP_NEW: t.Final[OD] = OD.DROP_NEW


class OnDup(t.NamedTuple('_OnDup', [('key', OD), ('val', OD), ('kv', OD)])):
r"""A 3-tuple of :class:`OD`\s specifying how to handle the 3 kinds of duplication.
class OnDup(t.NamedTuple):
r"""A combination of :class:`~bidict.OnDupAction`\s specifying how to handle various types of duplication.
The :attr:`key` field specifies what action to take when a duplicate key is encountered.
The :attr:`val` field specifies what action to take when a duplicate value is encountered.
In the case of both key and value duplication across two different items,
only :attr:`val` is used.
*See also* :ref:`basic-usage:Values Must Be Unique`
(https://bidict.rtfd.io/basic-usage.html#values-must-be-unique)
If *kv* is not specified, *val* will be used for *kv*.
"""

__slots__ = ()

def __new__(cls, key: OD = DROP_OLD, val: OD = RAISE, kv: OD | None = None) -> OnDup:
"""Override to provide user-friendly default values."""
return super().__new__(cls, key, val, kv or val)
key: OD = DROP_OLD
val: OD = RAISE


#: Default :class:`OnDup` used for the
#: :meth:`~bidict.bidict.__init__`,
#: :meth:`~bidict.bidict.__setitem__`, and
#: :meth:`~bidict.bidict.update` methods.
ON_DUP_DEFAULT: t.Final[OnDup] = OnDup(key=DROP_OLD, val=RAISE, kv=RAISE)
ON_DUP_DEFAULT: t.Final[OnDup] = OnDup(key=DROP_OLD, val=RAISE)
#: An :class:`OnDup` whose members are all :obj:`RAISE`.
ON_DUP_RAISE: t.Final[OnDup] = OnDup(key=RAISE, val=RAISE, kv=RAISE)
ON_DUP_RAISE: t.Final[OnDup] = OnDup(key=RAISE, val=RAISE)
#: An :class:`OnDup` whose members are all :obj:`DROP_OLD`.
ON_DUP_DROP_OLD: t.Final[OnDup] = OnDup(key=DROP_OLD, val=DROP_OLD, kv=DROP_OLD)
ON_DUP_DROP_OLD: t.Final[OnDup] = OnDup(key=DROP_OLD, val=DROP_OLD)
2 changes: 1 addition & 1 deletion bidict/_exc.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class BidictException(Exception):

class DuplicationError(BidictException):
"""Base class for exceptions raised when uniqueness is violated
as per the :attr:~bidict.RAISE` :class:`~bidict.OnDupAction`.
as per the :attr:`~bidict.RAISE` :class:`~bidict.OnDupAction`.
"""


Expand Down
35 changes: 15 additions & 20 deletions docs/basic-usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -242,44 +242,39 @@ Key and Value Duplication
Note that it's possible for a given item to duplicate
the key of one existing item,
and the value of another existing item.
In the following example,
the third item we're trying to insert
duplicates the first item's key
and the second item's value:

For example:

.. code-block:: python
b.putall({1: 2, 3: 4, 1: 4}, on_dup=OnDup(...))
b.putall([(1, -1), (2, -2), (1, -2)], on_dup=OnDup(...))
What should happen next?
Here, the third item we're trying to insert, (1, -2),
duplicates the key of the first item we're passing, (1, -1),
and the value of the second item we're passing, (2, -2).

Keep in mind, the active :class:`~bidict.OnDup`
Keep in mind, the :class:`~bidict.OnDup`
may specify one :class:`~bidict.OnDupAction`
for :attr:`key duplication <bidict.OnDup.key>`
and a different :class:`~bidict.OnDupAction`
for :attr:`value duplication <bidict.OnDup.val>`.

To account for this,
:class:`~bidict.OnDup`
allows you to use its
:attr:`~bidict.OnDup.kv` field
to indicate how you want to handle this case
without ambiguity:
In the case of a key and value duplication,
the :class:`~bidict.OnDupAction`
for :attr:`value duplication <bidict.OnDup.val>`
takes precedence:

.. doctest::

>>> from bidict import DROP_OLD
>>> on_dup = OnDup(key=DROP_OLD, val=RAISE, kv=RAISE)
>>> b.putall([(1, 2), (3, 4), (1, 4)], on_dup)
>>> on_dup = OnDup(key=DROP_OLD, val=RAISE)
>>> b.putall([(1, -1), (2, -2), (1, -2)], on_dup=on_dup)
Traceback (most recent call last):
...
bidict.KeyAndValueDuplicationError: (1, 4)

If not specified, *kv* defaults to whatever was provided for *val*.
bidict.KeyAndValueDuplicationError: (1, -2)

Note that repeated insertions of the same item
are construed as a no-op and will not raise,
no matter what the active :class:`~bidict.OnDup` is:
no matter what :class:`~bidict.OnDup` is:

.. doctest::

Expand Down
6 changes: 3 additions & 3 deletions tests/bidict_test_fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,11 @@ def put(self, key: KT, val: VT, on_dup: OnDup = DEFAULT_ON_DUP) -> None:
assert val == oldval
return
# key and val each duplicate a different existing item.
if on_dup.kv is RAISE:
if on_dup.val is RAISE:
raise KeyAndValueDuplicationError(key, val)
if on_dup.kv is DROP_NEW:
if on_dup.val is DROP_NEW:
return
assert on_dup.kv is DROP_OLD
assert on_dup.val is DROP_OLD
elif isdupkey:
if on_dup.key is RAISE:
raise KeyDuplicationError(key)
Expand Down
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
from hypothesis import settings


settings.register_profile('more-examples', max_examples=1_000, deadline=None)
settings.register_profile('more-examples', max_examples=200, deadline=None)
37 changes: 17 additions & 20 deletions tests/test_bidict.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,12 @@
init_items = dictionaries(vals, keys, max_size=MAX_SIZE).map(lambda d: {v: k for (k, v) in d.items()}) # no dup vals
bidict_t = sampled_from(bidict_types)
mut_bidict_t = sampled_from(mutable_bidict_types)
pairs = tuples(keys, vals)
updates = lists(pairs, max_size=MAX_SIZE) # "lists" to allow testing updates with dup k and/or v
items = tuples(keys, vals)
ItemLists: t.TypeAlias = t.List[t.Tuple[int, int]]
itemlists = lists(items, max_size=MAX_SIZE) # "lists" to allow testing updates with dup k and/or v
updates_t = sampled_from(update_arg_types)
itemsets = frozensets(pairs, max_size=MAX_SIZE)
on_dups = tuple(starmap(OnDup, product(OD, repeat=3)))
itemsets = frozensets(items, max_size=MAX_SIZE)
on_dups = tuple(starmap(OnDup, product(OD, repeat=2)))
on_dup = sampled_from(on_dups)


Expand Down Expand Up @@ -177,7 +178,7 @@ def put(self, key: int, val: complex, on_dup: OnDup) -> None:
partial(self.oracle.put, key, val, on_dup),
)

@rule(updates=updates, updates_t=updates_t, on_dup=on_dup)
@rule(updates=itemlists, updates_t=updates_t, on_dup=on_dup)
def putall(self, updates: MapOrItems[int, int], updates_t: t.Any, on_dup: OnDup) -> None:
# Don't let the updates_t(updates) calls below raise a DuplicationError.
if isinstance(updates_t, type) and issubclass(updates_t, BidirectionalMapping):
Expand Down Expand Up @@ -356,18 +357,14 @@ def test_ne_ordsens_to_equal_map_with_diff_order(init_items: InitItems, bidict_t
assert not bi.equals_order_sensitive(map_shuf)


lists_pairs = lists(tuples(integers(), integers()))
LP: t.TypeAlias = t.List[t.Tuple[int, int]]


@given(lp=lists_pairs, bidict_t=bidict_t)
def test_inverted(lp: LP, bidict_t: BT[int, int]) -> None:
check_list = list(inverted(inverted(lp)))
expect_list = lp
@given(items=itemlists, bidict_t=bidict_t)
def test_inverted(items: ItemLists, bidict_t: BT[int, int]) -> None:
check_list = list(inverted(inverted(items)))
expect_list = items
assert check_list == expect_list
lp_nodup = dedup(lp)
check_bi = bidict_t(inverted(bidict_t(lp_nodup)))
expect_bi = bidict_t({v: k for (k, v) in lp_nodup.items()})
items_nodup = dedup(items)
check_bi = bidict_t(inverted(bidict_t(items_nodup)))
expect_bi = bidict_t({v: k for (k, v) in items_nodup.items()})
assert_bidicts_equal(check_bi, expect_bi)


Expand Down Expand Up @@ -396,21 +393,21 @@ def test_putall_matches_bulk_put(bi_t: type[MutableBidict[int, int]], on_dup: On
bi = bi_t(init_items)
for k1, v1, k2, v2 in product(range(4), repeat=4):
for b in bi, bi.inv:
assert_putall_matches_bulk_put(b, {k1: v1, k2: v2}, on_dup)
assert_putall_matches_bulk_put(b, [(k1, v1), (k2, v2)], on_dup)


def assert_putall_matches_bulk_put(bi: MutableBidict[KT, VT], init_items: InitItems, on_dup: OnDup) -> None:
def assert_putall_matches_bulk_put(bi: MutableBidict[int, int], new_items: ItemLists, on_dup: OnDup) -> None:
tmp = bi.copy()
checkexc = None
expectexc = None
try:
for key, val in init_items.items():
for key, val in new_items:
tmp.put(key, val, on_dup)
except DuplicationError as exc:
expectexc = type(exc)
tmp = bi # Since bulk updates fail clean, expect no changes (i.e. revert to bi).
try:
bi.putall(init_items, on_dup)
bi.putall(new_items, on_dup)
except DuplicationError as exc:
checkexc = type(exc)
assert checkexc == expectexc
Expand Down

0 comments on commit 1b92df4

Please sign in to comment.