Skip to content

Commit

Permalink
Type check tests, fix inverted type hint, prepare for 0.22.1 release
Browse files Browse the repository at this point in the history
* Add type hints to tests and no longer exclude them from type checking.

* Fix the return type hint of bidict.inverted() to return an Iterator
  (aliased as ItemsIter), not an Iterable.

* Rename the internal type alias for Iterable[Tuple[KT, VT]] from
  IterItems to Items, and likewise MapOrIterItems to MapOrItems.
  • Loading branch information
jab committed Jan 1, 2023
1 parent aebc3d0 commit da526c4
Show file tree
Hide file tree
Showing 15 changed files with 217 additions and 185 deletions.
4 changes: 3 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ repos:
rev: v0.991
hooks:
- id: mypy
exclude: setup.py|docs/conf.py|tests
additional_dependencies:
- hypothesis
- pytest

- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.192
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,18 @@ please consider sponsoring bidict on GitHub.`
Click the "Watch" dropdown, choose "Custom", and then choose "Releases".


0.22.1 (2022-12-31)
-------------------

- Only include the source code in the source distribution.
This reduces the size of the source distribution
from ~200kB to ~30kB.

- Fix the return type hint of :func:`bidict.inverted`
to return an :class:`~collections.abc.Iterator`,
rather than an :class:`~collections.abc.Iterable`.


0.22.0 (2022-03-23)
-------------------

Expand Down
14 changes: 7 additions & 7 deletions bidict/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from ._dup import ON_DUP_DEFAULT, RAISE, DROP_OLD, DROP_NEW, OnDup
from ._exc import DuplicationError, KeyDuplicationError, ValueDuplicationError, KeyAndValueDuplicationError
from ._iter import iteritems, inverted
from ._typing import KT, VT, MISSING, OKT, OVT, IterItems, MapOrIterItems, TypeAlias
from ._typing import KT, VT, MISSING, OKT, OVT, Items, MapOrItems, TypeAlias


OldKV: TypeAlias = 'tuple[OKT[KT], OVT[VT]]'
Expand All @@ -44,7 +44,7 @@ class BidictKeysView(t.KeysView[KT], t.ValuesView[KT]):
"""


def get_arg(*args: MapOrIterItems[KT, VT]) -> MapOrIterItems[KT, VT]:
def get_arg(*args: MapOrItems[KT, VT]) -> MapOrItems[KT, VT]:
"""Ensure there's only a single arg in *args*, then return it."""
if len(args) > 1:
raise TypeError(f'Expected at most 1 positional argument, got {len(args)}')
Expand Down Expand Up @@ -142,9 +142,9 @@ def __init__(self, **kw: VT) -> None: ...
@t.overload
def __init__(self, __m: t.Mapping[KT, VT], **kw: VT) -> None: ...
@t.overload
def __init__(self, __i: IterItems[KT, VT], **kw: VT) -> None: ...
def __init__(self, __i: Items[KT, VT], **kw: VT) -> None: ...

def __init__(self, *args: MapOrIterItems[KT, VT], **kw: VT) -> None:
def __init__(self, *args: MapOrItems[KT, VT], **kw: VT) -> None:
"""Make a new bidirectional mapping.
The signature behaves like that of :class:`dict`.
Items passed in are added in the order they are passed,
Expand Down Expand Up @@ -405,7 +405,7 @@ def _prep_write(self, newkey: KT, newval: VT, oldkey: OKT[KT], oldval: OVT[VT],

def _update(
self,
arg: MapOrIterItems[KT, VT],
arg: MapOrItems[KT, VT],
kw: t.Mapping[str, VT] = MappingProxyType({}),
*,
rbof: bool | None = None,
Expand Down Expand Up @@ -470,7 +470,7 @@ def copy(self: BT) -> BT:
return self._from_other(self.__class__, self)

@staticmethod
def _from_other(bt: t.Type[BT], other: MapOrIterItems[KT, VT], inv: bool = False) -> BT:
def _from_other(bt: t.Type[BT], other: MapOrItems[KT, VT], inv: bool = False) -> BT:
"""Fast, private constructor based on :meth:`_init_from`.
If *inv* is true, return the inverse of the instance instead of the instance itself.
Expand All @@ -480,7 +480,7 @@ def _from_other(bt: t.Type[BT], other: MapOrIterItems[KT, VT], inv: bool = False
inst._init_from(other)
return t.cast(BT, inst.inverse) if inv else inst

def _init_from(self, other: MapOrIterItems[KT, VT]) -> None:
def _init_from(self, other: MapOrItems[KT, VT]) -> None:
"""Fast init from *other*, bypassing item-by-item duplication checking."""
self._fwdm.clear()
self._invm.clear()
Expand Down
14 changes: 7 additions & 7 deletions bidict/_bidict.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from ._abc import MutableBidirectionalMapping
from ._base import BidictBase, get_arg
from ._dup import OnDup, ON_DUP_RAISE, ON_DUP_DROP_OLD
from ._typing import KT, VT, DT, ODT, MISSING, IterItems, MapOrIterItems
from ._typing import KT, VT, DT, ODT, MISSING, Items, MapOrItems


class MutableBidict(BidictBase[KT, VT], MutableBidirectionalMapping[KT, VT]):
Expand Down Expand Up @@ -139,23 +139,23 @@ def popitem(self) -> tuple[KT, VT]:
@t.overload # type: ignore [override] # https://github.com/jab/bidict/pull/242#discussion_r825464731
def update(self, __m: t.Mapping[KT, VT], **kw: VT) -> None: ...
@t.overload
def update(self, __i: IterItems[KT, VT], **kw: VT) -> None: ...
def update(self, __i: Items[KT, VT], **kw: VT) -> None: ...
@t.overload
def update(self, **kw: VT) -> None: ...

def update(self, *args: MapOrIterItems[KT, VT], **kw: VT) -> None:
def update(self, *args: MapOrItems[KT, VT], **kw: VT) -> None:
"""Like calling :meth:`putall` with *self.on_dup* passed for *on_dup*."""
if args or kw:
self._update(get_arg(*args), kw)

@t.overload
def forceupdate(self, __m: t.Mapping[KT, VT], **kw: VT) -> None: ...
@t.overload
def forceupdate(self, __i: IterItems[KT, VT], **kw: VT) -> None: ...
def forceupdate(self, __i: Items[KT, VT], **kw: VT) -> None: ...
@t.overload
def forceupdate(self, **kw: VT) -> None: ...

def forceupdate(self, *args: MapOrIterItems[KT, VT], **kw: VT) -> None:
def forceupdate(self, *args: MapOrItems[KT, VT], **kw: VT) -> None:
"""Like a bulk :meth:`forceput`."""
if args or kw:
self._update(get_arg(*args), kw, on_dup=ON_DUP_DROP_OLD)
Expand All @@ -168,9 +168,9 @@ def __ior__(self, other: t.Mapping[KT, VT]) -> MutableBidict[KT, VT]:
@t.overload
def putall(self, items: t.Mapping[KT, VT], on_dup: OnDup) -> None: ...
@t.overload
def putall(self, items: IterItems[KT, VT], on_dup: OnDup = ...) -> None: ...
def putall(self, items: Items[KT, VT], on_dup: OnDup = ...) -> None: ...

def putall(self, items: MapOrIterItems[KT, VT], on_dup: OnDup = ON_DUP_RAISE) -> None:
def putall(self, items: MapOrItems[KT, VT], on_dup: OnDup = ON_DUP_RAISE) -> None:
"""Like a bulk :meth:`put`.
If one of the given items causes an exception to be raised,
Expand Down
10 changes: 5 additions & 5 deletions bidict/_iter.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@
from operator import itemgetter
import typing as t

from ._typing import KT, VT, IterItems, MapOrIterItems
from ._typing import KT, VT, ItemsIter, MapOrItems


def iteritems_mapping_or_iterable(arg: MapOrIterItems[KT, VT]) -> IterItems[KT, VT]:
def iteritems_mapping_or_iterable(arg: MapOrItems[KT, VT]) -> ItemsIter[KT, VT]:
"""Yield the items in *arg* based on whether it's a mapping."""
yield from arg.items() if isinstance(arg, t.Mapping) else arg


def iteritems(__arg: MapOrIterItems[KT, VT], **kw: VT) -> IterItems[KT, VT]:
def iteritems(__arg: MapOrItems[KT, VT], **kw: VT) -> ItemsIter[KT, VT]:
"""Yield the items from *arg* and then any from *kw* in the order given."""
yield from iteritems_mapping_or_iterable(__arg)
yield from kw.items() # type: ignore [misc]
Expand All @@ -28,7 +28,7 @@ def iteritems(__arg: MapOrIterItems[KT, VT], **kw: VT) -> IterItems[KT, VT]:
swap = itemgetter(1, 0)


def inverted(arg: MapOrIterItems[KT, VT]) -> IterItems[VT, KT]:
def inverted(arg: MapOrItems[KT, VT]) -> ItemsIter[VT, KT]:
"""Yield the inverse items of the provided object.
If *arg* has a :func:`callable` ``__inverted__`` attribute,
Expand All @@ -41,6 +41,6 @@ def inverted(arg: MapOrIterItems[KT, VT]) -> IterItems[VT, KT]:
"""
invattr = getattr(arg, '__inverted__', None)
if callable(invattr):
inv: IterItems[VT, KT] = invattr()
inv: ItemsIter[VT, KT] = invattr()
return inv
return map(swap, iteritems_mapping_or_iterable(arg))
8 changes: 4 additions & 4 deletions bidict/_orderedbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from ._base import BidictBase, PreparedWrite
from ._bidict import bidict
from ._iter import iteritems
from ._typing import KT, VT, OKT, OVT, MISSING, IterItems, MapOrIterItems
from ._typing import KT, VT, OKT, OVT, MISSING, Items, MapOrItems


IT = t.TypeVar('IT') # instance type
Expand Down Expand Up @@ -114,11 +114,11 @@ class OrderedBidictBase(BidictBase[KT, VT]):
@t.overload
def __init__(self, __m: t.Mapping[KT, VT], **kw: VT) -> None: ...
@t.overload
def __init__(self, __i: IterItems[KT, VT], **kw: VT) -> None: ...
def __init__(self, __i: Items[KT, VT], **kw: VT) -> None: ...
@t.overload
def __init__(self, **kw: VT) -> None: ...

def __init__(self, *args: MapOrIterItems[KT, VT], **kw: VT) -> None:
def __init__(self, *args: MapOrItems[KT, VT], **kw: VT) -> None:
"""Make a new ordered bidirectional mapping.
The signature behaves like that of :class:`dict`.
Items passed in are added in the order they are passed,
Expand Down Expand Up @@ -151,7 +151,7 @@ def _dissoc_node(self, node: Node) -> None:
del self._node_by_korv.inverse[node]
node.unlink()

def _init_from(self, other: MapOrIterItems[KT, VT]) -> None:
def _init_from(self, other: MapOrItems[KT, VT]) -> None:
"""See :meth:`BidictBase._init_from`."""
super()._init_from(other)
bykey = self._bykey
Expand Down
5 changes: 3 additions & 2 deletions bidict/_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@
VT = t.TypeVar('VT')


IterItems: TypeAlias = 't.Iterable[tuple[KT, VT]]'
MapOrIterItems: TypeAlias = 't.Mapping[KT, VT] | IterItems[KT, VT]'
Items: TypeAlias = 't.Iterable[tuple[KT, VT]]'
MapOrItems: TypeAlias = 't.Mapping[KT, VT] | Items[KT, VT]'
ItemsIter: TypeAlias = 't.Iterator[tuple[KT, VT]]'


class MissingT(Enum):
Expand Down
2 changes: 1 addition & 1 deletion bidict/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"""Define bidict package metadata."""


__version__ = '0.22.1.dev0'
__version__ = '0.22.1'
__author__ = {'name': 'Joshua Bronson', 'email': 'jabronson@gmail.com'}
__copyright__ = '© 2009-2022 Joshua Bronson'
__description__ = 'The bidirectional mapping library for Python.'
Expand Down
2 changes: 1 addition & 1 deletion run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@
partial(sphinx_main, '-b doctest -d docs/_build/doctrees docs docs/_build/doctest'.split()),
)

raise SystemExit(sum(bool(f()) for f in TEST_FUNCS))
raise SystemExit(sum(bool(f()) for f in TEST_FUNCS)) # type: ignore
38 changes: 20 additions & 18 deletions tests/property_tests/_strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,33 @@

"""Strategies for Hypothesis tests."""

from __future__ import annotations
from collections import OrderedDict
from operator import attrgetter, itemgetter, methodcaller
import typing as t

import hypothesis.strategies as st
from bidict import DROP_NEW, DROP_OLD, RAISE, OnDup, OrderedBidictBase, namedbidict

from . import _types as t
from . import _types as _t


def one_of(items):
def one_of(items: t.Any) -> t.Any:
"""Create a one_of strategy using the given items."""
return st.one_of((st.just(i) for i in items))
return st.one_of((st.just(i) for i in items)) # type: ignore [call-overload]


DATA = st.data()
BIDICT_TYPES = one_of(t.BIDICT_TYPES)
MUTABLE_BIDICT_TYPES = one_of(t.MUTABLE_BIDICT_TYPES)
FROZEN_BIDICT_TYPES = one_of(t.FROZEN_BIDICT_TYPES)
ORDERED_BIDICT_TYPES = one_of(t.ORDERED_BIDICT_TYPES)
REVERSIBLE_BIDICT_TYPES = one_of(t.REVERSIBLE_BIDICT_TYPES)
MAPPING_TYPES = one_of(t.MAPPING_TYPES)
NON_BI_MAPPING_TYPES = one_of(t.NON_BI_MAPPING_TYPES)
NON_NAMED_BIDICT_TYPES = one_of(t.NON_NAMED_BIDICT_TYPES)
ORDERED_MAPPING_TYPES = one_of(t.ORDERED_MAPPING_TYPES)
HASHABLE_MAPPING_TYPES = one_of(t.HASHABLE_MAPPING_TYPES)
BIDICT_TYPES = one_of(_t.BIDICT_TYPES)
MUTABLE_BIDICT_TYPES = one_of(_t.MUTABLE_BIDICT_TYPES)
FROZEN_BIDICT_TYPES = one_of(_t.FROZEN_BIDICT_TYPES)
ORDERED_BIDICT_TYPES = one_of(_t.ORDERED_BIDICT_TYPES)
REVERSIBLE_BIDICT_TYPES = one_of(_t.REVERSIBLE_BIDICT_TYPES)
MAPPING_TYPES = one_of(_t.MAPPING_TYPES)
NON_BI_MAPPING_TYPES = one_of(_t.NON_BI_MAPPING_TYPES)
NON_NAMED_BIDICT_TYPES = one_of(_t.NON_NAMED_BIDICT_TYPES)
ORDERED_MAPPING_TYPES = one_of(_t.ORDERED_MAPPING_TYPES)
HASHABLE_MAPPING_TYPES = one_of(_t.HASHABLE_MAPPING_TYPES)
ON_DUP_ACTIONS = one_of((DROP_NEW, DROP_OLD, RAISE))
ON_DUP = st.tuples(ON_DUP_ACTIONS, ON_DUP_ACTIONS, ON_DUP_ACTIONS).map(OnDup._make)

Expand Down Expand Up @@ -63,8 +65,8 @@ def one_of(items):
).filter(lambda i: i[0] != i[1])


def _bidict_strat(bi_types, init_items=I_PAIRS_NODUP, _inv=attrgetter('inverse')):
fwd_bidicts = st.tuples(bi_types, init_items).map(lambda i: i[0](i[1]))
def _bidict_strat(bi_types: t.Any, init_items: t.Any = I_PAIRS_NODUP, _inv: t.Any = attrgetter('inverse')) -> t.Any:
fwd_bidicts = st.tuples(bi_types, init_items).map(lambda i: i[0](i[1])) # type: ignore
inv_bidicts = fwd_bidicts.map(_inv)
return fwd_bidicts | inv_bidicts

Expand All @@ -78,7 +80,7 @@ def _bidict_strat(bi_types, init_items=I_PAIRS_NODUP, _inv=attrgetter('inverse')
KEYSVIEW_SET_OP_ARGS = st.sets(ATOMS) | st.dictionaries(ATOMS, ATOMS).map(callkeys) | BIDICTS.map(callkeys)
ITEMSVIEW_SET_OP_ARGS = st.sets(PAIRS) | st.dictionaries(ATOMS, ATOMS).map(callitems) | BIDICTS.map(callitems)

NON_BI_MAPPINGS = st.tuples(NON_BI_MAPPING_TYPES, L_PAIRS).map(lambda i: i[0](i[1]))
NON_BI_MAPPINGS = st.tuples(NON_BI_MAPPING_TYPES, L_PAIRS).map(lambda i: i[0](i[1])) # type: ignore


NAMEDBIDICT_NAMES_ALL_VALID = st.lists(VALID_NAMES, min_size=3, max_size=3, unique=True)
Expand All @@ -91,7 +93,7 @@ def _bidict_strat(bi_types, init_items=I_PAIRS_NODUP, _inv=attrgetter('inverse')
NAMEDBIDICTS = _bidict_strat(NAMEDBIDICT_TYPES)


def _bi_and_map(bi_types, map_types=MAPPING_TYPES, init_items=L_PAIRS_NODUP):
def _bi_and_map(bi_types: t.Any, map_types: t.Any = MAPPING_TYPES, init_items: t.Any = L_PAIRS_NODUP) -> t.Any:
"""Given bidict types and mapping types, return a pair of each type created from init_items."""
return st.tuples(bi_types, map_types, init_items).map(
lambda i: (i[0](i[2]), i[1](i[2]))
Expand All @@ -110,7 +112,7 @@ def _bi_and_map(bi_types, map_types=MAPPING_TYPES, init_items=L_PAIRS_NODUP):
ORDERED_BIDICT_TYPES, ORDERED_MAPPING_TYPES, SAME_ITEMS_DIFF_ORDER
).map(_unpack)

_cmpdict = lambda i: (OrderedDict if isinstance(i, OrderedBidictBase) else dict) # noqa: E731
_cmpdict: t.Any = lambda i: (OrderedDict if isinstance(i, OrderedBidictBase) else dict) # noqa: E731
BI_AND_CMPDICT_FROM_SAME_ITEMS = L_PAIRS_NODUP.map(
lambda items: (lambda b: (b, _cmpdict(b)(items)))(_bidict_strat(BIDICT_TYPES, items))
)
Expand Down
Loading

0 comments on commit da526c4

Please sign in to comment.