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 8, 2020
1 parent 8a8e58b commit 1695def
Show file tree
Hide file tree
Showing 31 changed files with 598 additions and 426 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,
69 changes: 60 additions & 9 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,67 @@ Tip: Subscribe to releases
to be notified when new versions of ``bidict`` are released.


0.21.0 not yet released
-----------------------

- Add :class:`bidict.MutableBidirectionalMapping`.

- Add type hints.

- Drop support for Python 3.5.
0.21.0 (not yet released)
-------------------------

- Remove the ``bidict.compat`` module.
- :mod:`bidict` now provides
`type hints <https://www.python.org/dev/peps/pep-0484/>`__! ⌨️ ✅

Adding type hints to :mod:`bidict` poses particularly interesting challenges
due to the combination of generic types,
dynamically-generated types
(such as :ref:`inverse bidict classes <extending:Dynamic Inverse Class Generation>`
and :func:`namedbidicts <bidict.namedbidict>`),
and complicating optimizations
such as the use of slots and weakrefs.

It didn't take long to hit bugs and missing features
in the state of the art for type hinting in Python today,
e.g. no support for
`higher-kinded types <https://github.com/python/typing/issues/548#issuecomment-621195693>`__,
a `typing.Generic bug in Python 3.6 <https://bugs.python.org/issue41451>`__, etc.

That said, this release should provide a solid foundation
for code using :mod:`bidict` that enables static type checking.

As always, if you spot any opportunities to improve :mod:`bidict`
(including its new type hints),
please don't hesitate to 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 reaches end of life on 2020-09-13,
represents a tiny percentage of bidict downloads on
`PyPI Stats <https://pypistats.org/packages/bidict>`__,
and lacks support for
`variable type hint syntax <https://www.python.org/dev/peps/pep-0526/>`__,
`ordered dicts <https://stackoverflow.com/a/39980744>`__,
and :attr:`object.__init_subclass__`.

- Remove the no-longer-needed ``bidict.compat`` module.

- Move :ref:`inverse bidict class access <extending:Dynamic Inverse Class Generation>`
from a property to an attribute set in
:attr:`~bidict.BidictBase.__init_subclass__`,
to save function call overhead on repeated access.

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

- Set the ``__module__`` attribute of various :mod:`bidict` types
(using :func:`sys._getframe` when necessary)
so that private, internal modules are not exposed
e.g. in classes' repr strings.

- :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
26 changes: 12 additions & 14 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,6 @@ Status
.. image:: https://img.shields.io/lgtm/alerts/github/jab/bidict.svg
:target: https://lgtm.com/projects/g/jab/bidict/
:alt: LGTM alerts
.. image:: https://api.codacy.com/project/badge/Grade/6628756a73254cd895656348236833b8
: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
Expand All @@ -62,24 +59,25 @@ Status
:target: https://raw.githubusercontent.com/jab/bidict/master/LICENSE
:alt: License

.. image:: https://img.shields.io/badge/dynamic/json.svg?label=downloads&url=https%3A%2F%2Fpypistats.org%2Fapi%2Fpackages%2Fbidict%2Frecent%3Fperiod%3Dmonth&query=%24.data.last_month&colorB=blue&suffix=%2fmonth
:target: https://pypistats.org/packages/bidict
:alt: Downloads past month
.. image:: https://static.pepy.tech/badge/bidict
:target: https://pepy.tech/project/bidict
:alt: PyPI Downloads


``bidict``:
^^^^^^^^^^^

- has been used for many years by several teams at
Google, Venmo, CERN, Bank of America Merrill Lynch, Bloomberg, Two Sigma, and others
**Google, Venmo, CERN, Bank of America Merrill Lynch, Bloomberg, Two Sigma,** and many others
- has carefully designed APIs for
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
- 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
- has extensive docs and test coverage
**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.abc`` interfaces
- provides **type hints** for all public APIs
- is implemented in **concise, well-factored, pure (PyPy-compatible) Python code**
that is **optimized for running efficiently**
as well as for **reading and learning** [#fn-learning]_
- has **extensive docs and test coverage**
(including property-based tests and benchmarks)
run continuously on all supported Python versions and OSes

Expand Down
22 changes: 13 additions & 9 deletions bidict/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@

# 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 ._abc import BidirectionalMapping, MutableBidirectionalMapping
from ._base import BidictBase
Expand All @@ -69,20 +68,25 @@
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 1695def

Please sign in to comment.