Skip to content

Commit

Permalink
Finish first pass at adding type hints.
Browse files Browse the repository at this point in the history
Fixes #93.
  • Loading branch information
jab committed Aug 7, 2020
1 parent 8a8e58b commit 412de4a
Show file tree
Hide file tree
Showing 28 changed files with 445 additions and 319 deletions.
12 changes: 4 additions & 8 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ repos:
# - id: fix-encoding-pragma
- id: check-yaml

#- repo: https://github.com/pre-commit/mirrors-mypy
# rev: '0.782'
# hooks:
# - id: mypy
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.782
hooks:
- id: mypy

- repo: https://github.com/pycqa/pydocstyle
rev: 5.0.2
Expand Down Expand Up @@ -53,10 +53,6 @@ repos:
- hypothesis < 6
- pytest < 7
- Sphinx < 4
args:
# http://pylint.pycqa.org/en/latest/user_guide/run.html#parallel-execution
# "If the provided number is 0, then the total number of CPUs will be used."
- --jobs=0

- repo: https://github.com/jumanjihouse/pre-commit-hooks
rev: 2.1.4
Expand Down
44 changes: 26 additions & 18 deletions .pylintrc
Original file line number Diff line number Diff line change
@@ -1,24 +1,32 @@
# https://docs.pylint.org/en/latest/technical_reference/features.html

[MASTER]
ignore=_version.py conf.py
jobs=0

[MESSAGES CONTROL]
disable =
disable=
abstract-method,
arguments-differ,
attribute-defined-outside-init,
bad-continuation,
broad-except,
invalid-name,
isinstance-second-argument-not-valid-type, # https://github.com/PyCQA/pylint/issues/3507
line-too-long,
missing-function-docstring, # prevents idiomatic type hints
multiple-statements, # prevents idiomatic type hints
no-init,
no-member,
too-few-public-methods,
no-self-use, # prevents idiomatic type hints
not-callable,
protected-access,
isinstance-second-argument-not-valid-type, # https://github.com/PyCQA/pylint/issues/3507

# bidict/_version.py is generated by setuptools_scm
ignore = _version.py

# Maximum number of parents for a class.
# The default of 7 results in "too-many-ancestors" for all bidict classes.
max-parents = 13

# Maximum number of arguments for a function.
# The default of 5 only leaves room for 4 args besides self for methods.
max-args=6


[FORMAT]
max-line-length=125
redefined-builtin,
signature-differs,
super-init-not-called,
too-few-public-methods,
too-many-ancestors,
too-many-branches,
too-many-locals,
wrong-import-order,
wrong-import-position,
51 changes: 45 additions & 6 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,55 @@ Tip: Subscribe to releases
to be notified when new versions of ``bidict`` are released.


0.21.0 not yet released
-----------------------
0.21.0 (not yet released)
-------------------------

- First release of ``bidict`` to provide
`type hints <https://www.python.org/dev/peps/pep-0484/>`__! ⌨️ ✅

Adding static type hints to ``bidict`` poses a particularly interesting challenge
due to the combination of generic types,
dynamically-generated inverse types and :func:`namedbidicts <bidict.namedbidict>`,
optimizations such as the use of slots and weakrefs,
and several other factors.

It didn't take long to hit up against current limitations
in the :mod:`typing` module
(e.g. `#548 <https://github.com/python/typing/issues/548#issuecomment-621195693>`__
and `BPO-4151 <https://bugs.python.org/issue41451>`__, among others).

That said, this release should provide a solid foundation
for type hinted programs that use ``bidict``.
But if you spot any opportunities to improve type hints in ``bidict``,
please submit a PR!

- Add :class:`bidict.MutableBidirectionalMapping` ABC.

The :ref:`other-bidict-types:Bidict Types Diagram` has been updated accordingly.

- Drop support for Python 3.5,
which lacks support for
`variable type hint syntax <https://www.python.org/dev/peps/pep-0526/>`__,
`ordered dicts <https://stackoverflow.com/a/39980744>`__,
and which the Python core developers no longer maintain
`as of 2020-09-13 <https://www.python.org/downloads/>`__.

- Add :class:`bidict.MutableBidirectionalMapping`.
- Remove the no-longer-needed ``bidict.compat`` module.

- Add type hints.
- :meth:`bidict.OrderedBidictBase.__iter__` no longer accepts
a ``reverse`` keyword argument so that it matches the signature of
:meth:`container.__iter__`.

- Drop support for Python 3.5.
- Set the ``__module__`` attribute of everything
that should be imported from the :mod:`bidict` module to ``'bidict'``,
so that private, internal submodules are not exposed to users
e.g. in repr strings.

- Remove the ``bidict.compat`` module.
- :func:`~bidict.namedbidict` now immediately raises :class:`TypeError`
if the provided ``base_type`` does not provide
``_isinv`` or :meth:`~object.__getstate__`,
rather than succeeding with a class whose instances may raise
:class:`AttributeError` when these attributes are accessed.


0.20.0 (2020-07-23)
Expand Down
1 change: 1 addition & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ Status
safety, simplicity, flexibility, and ergonomics
- is fast, lightweight, and has no runtime dependencies other than Python's standard library
- integrates natively with Python’s collections interfaces
- provides type hints for all public APIs
- is implemented in concise, well-factored, pure (PyPy-compatible) Python code
optimized both for reading and learning from [#fn-learning]_
as well as for running efficiently
Expand Down
25 changes: 15 additions & 10 deletions bidict/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,31 +58,36 @@

# The rest of this file only collects functionality implemented in the rest of the
# source for the purposes of exporting it under the `bidict` module namespace.
# pylint: disable=wrong-import-position
# flake8: noqa: F401 (imported but unused)
from ._typing import KT, VT, DT, OKT, OVT, ODT, _NONE, IterItems, MapOrIterItems
from ._abc import BidirectionalMapping, MutableBidirectionalMapping
from ._base import BidictBase
from ._base import BidictBase, BT
from ._mut import MutableBidict
from ._bidict import bidict
from ._frozenbidict import frozenbidict
from ._frozenordered import FrozenOrderedBidict
from ._named import namedbidict
from ._orderedbase import OrderedBidictBase
from ._orderedbidict import OrderedBidict
from ._dup import (
ON_DUP_DEFAULT, ON_DUP_RAISE, ON_DUP_DROP_OLD,
RAISE, DROP_OLD, DROP_NEW, OnDup, OnDupAction,
)
from ._exc import (
BidictException,
DuplicationError, KeyDuplicationError, ValueDuplicationError, KeyAndValueDuplicationError,
)
from ._dup import ON_DUP_DEFAULT, ON_DUP_RAISE, ON_DUP_DROP_OLD, RAISE, DROP_OLD, DROP_NEW, OnDup, OnDupAction
from ._exc import BidictException, DuplicationError, KeyDuplicationError, ValueDuplicationError, KeyAndValueDuplicationError
from ._util import inverted
from .metadata import (
__author__, __maintainer__, __copyright__, __email__, __credits__, __url__,
__license__, __status__, __description__, __keywords__, __version__, __version_info__,
)

# Set __module__ of re-exported classes to the 'bidict' top-level module name
# so that private/internal submodules are not exposed to users e.g. in repr strings.
_locals = tuple(locals().items())
for _name, _obj in _locals: # pragma: no cover
if not getattr(_obj, '__module__', '').startswith('bidict.'):
continue
try:
_obj.__module__ = 'bidict'
except AttributeError as exc: # raised when __module__ is read-only (as in OnDup)
pass


# * Code review nav *
#==============================================================================
Expand Down
18 changes: 7 additions & 11 deletions bidict/_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,13 @@

"""Provide the :class:`BidirectionalMapping` abstract base class."""

import typing as _t
from abc import abstractmethod
from typing import AbstractSet, Iterator, Mapping, MutableMapping, Tuple, TypeVar

from ._typing import KT, VT

KT = TypeVar('KT')
VT = TypeVar('VT')


# pylint: disable=abstract-method,no-init

class BidirectionalMapping(Mapping[KT, VT]):
class BidirectionalMapping(_t.Mapping[KT, VT]):
"""Abstract base class (ABC) for bidirectional mapping types.
Extends :class:`collections.abc.Mapping` primarily by adding the
Expand Down Expand Up @@ -66,7 +62,7 @@ def inverse(self) -> 'BidirectionalMapping[VT, KT]':
# clear there's no reason to call this implementation (e.g. via super() after overriding).
raise NotImplementedError

def __inverted__(self) -> Iterator[Tuple[VT, KT]]:
def __inverted__(self) -> _t.Iterator[_t.Tuple[VT, KT]]:
"""Get an iterator over the items in :attr:`inverse`.
This is functionally equivalent to iterating over the items in the
Expand All @@ -82,7 +78,7 @@ def __inverted__(self) -> Iterator[Tuple[VT, KT]]:
"""
return iter(self.inverse.items())

def values(self) -> AbstractSet[VT]: # type: ignore[override]
def values(self) -> _t.KeysView[VT]: # type: ignore
"""A set-like object providing a view on the contained values.
Override the implementation inherited from
Expand All @@ -94,10 +90,10 @@ def values(self) -> AbstractSet[VT]: # type: ignore[override]
which has the advantages of constant-time containment checks
and supporting set operations.
"""
return self.inverse.keys()
return self.inverse.keys() # type: ignore


class MutableBidirectionalMapping(BidirectionalMapping[KT, VT], MutableMapping[KT, VT]):
class MutableBidirectionalMapping(BidirectionalMapping[KT, VT], _t.MutableMapping[KT, VT]):
"""Abstract base class (ABC) for mutable bidirectional mapping types."""

__slots__ = ()
Expand Down
Loading

0 comments on commit 412de4a

Please sign in to comment.