Skip to content

Commit

Permalink
PEP 561 x 1.
Browse files Browse the repository at this point in the history
This commit is the first in a commit chain rendering `beartype`
compliant with PEP 561 by annotating the codebase in a manner
specifically compatible with the most popular third-party static type
checker, mypy, en-route to eventually resolving issue #25 kindly
submitted by best macOS package manager ever @harens. Specifically, this
commit:

* Resolves a variety of mypy errors by annotating various callables and
  classes in a mypy-compliant manner. Nonetheless, a variety of mypy
  errors remain. *Thus begins the eternal battle for quality assurance.*
* Adds a functional test exercising compatibility across the entire
  codebase with mypy.
* Adds an optional test-time developer dependency on a fairly recent
  version of mypy.

(*Flippant eternity's slipshod paternity!*)
  • Loading branch information
leycec committed Feb 17, 2021
1 parent 28b9e98 commit e2144fe
Show file tree
Hide file tree
Showing 33 changed files with 541 additions and 218 deletions.
23 changes: 23 additions & 0 deletions .mypy.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# --------------------( LICENSE )--------------------
# Copyright (c) 2014-2021 Cecil Curry.
# See "LICENSE" for further details.
#
# --------------------( SYNOPSIS )--------------------
# Project-wide mypy configuration, applied to all invocations of the mypy
# static type-checker within this project.
#
# --------------------( SEE ALSO )--------------------
# * https://mypy.readthedocs.io/en/stable/config_file.html
# Official documentation on this file format.

# ....................{ GLOBAL }...................
# The following mypy-specific section specifier is mandatory, despite this
# file's unambiguous basename of ".mypy.ini". One is enraged by bureaucracy!
[mypy]

# Display machine-readable "["- and "]"-bracketed error codes in *ALL*
# mypy-specific error messages. This option is disabled by default, which is
# awful, because these codes are the *ONLY* means of explicitly ignoring
# specific mypy errors with "# type: ignore[{error_code}]" comments littered
# throughout this project's codebase. Type-checked serenity now!
show_error_codes = True
5 changes: 5 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1043,6 +1043,8 @@ Let's chart current and future compliance with Python's `typing`_ landscape:
+------------------+-----------------------------------------+-------------------------------+---------------------------+
| | `560 <PEP 560_>`__ | **0.4.0**\ \ *current* | **0.4.0**\ \ *current* |
+------------------+-----------------------------------------+-------------------------------+---------------------------+
| | `561 <PEP 561_>`__ | **0.5.2**\ \ *current* | **0.5.2**\ \ *current* |
+------------------+-----------------------------------------+-------------------------------+---------------------------+
| | `563 <PEP 563_>`__ | **0.1.1**\ \ *current* | **0.1.1**\ \ *current* |
+------------------+-----------------------------------------+-------------------------------+---------------------------+
| | `572 <PEP 572_>`__ | **0.3.0**\ \ *current* | **0.4.0**\ \ *current* |
Expand Down Expand Up @@ -1389,6 +1391,7 @@ Compliance
* `PEP 544 -- Protocols: Structural subtyping (static duck typing) <PEP
544_>`_.
* `PEP 560 -- Core support for typing module and generic types <PEP 560_>`_.
* `PEP 561 -- Distributing and Packaging Type Information <PEP 561_>`_.
* `PEP 563 -- Postponed Evaluation of Annotations <PEP 563_>`__.
* `PEP 572 -- Assignment Expressions <PEP 572_>`__.
* `PEP 585 -- Type Hinting Generics In Standard Collections <PEP 585_>`__.
Expand Down Expand Up @@ -3052,6 +3055,8 @@ application stack at tool rather than Python runtime) include:
https://www.python.org/dev/peps/pep-0526
.. _PEP 544:
https://www.python.org/dev/peps/pep-0544
.. _PEP 561:
https://www.python.org/dev/peps/pep-0561
.. _PEP 563:
https://www.python.org/dev/peps/pep-0563
.. _PEP 570:
Expand Down
4 changes: 2 additions & 2 deletions beartype/_decor/_code/_pep/_error/_peperrorgeneric.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@
get_hint_pep484_generic_base_erased_from_unerased)
from beartype._util.hint.pep.proposal.utilhintpep585 import is_hint_pep585
from beartype._util.hint.pep.utilhintpeptest import is_hint_pep_typing
from typing import Generic
from typing import Generic, Optional

# See the "beartype.__init__" submodule for further commentary.
__all__ = ['STAR_IMPORTS_CONSIDERED_HARMFUL']

# ....................{ GETTERS }....................
def get_cause_or_none_generic(sleuth: CauseSleuth) -> 'Optional[str]':
def get_cause_or_none_generic(sleuth: CauseSleuth) -> Optional[str]:
'''
Human-readable string describing the failure of the passed arbitrary object
to satisfy the passed `PEP 484`_-compliant **generic** (i.e., type hint
Expand Down
2 changes: 1 addition & 1 deletion beartype/_decor/_code/_pep/_error/_peperrorreturn.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
__all__ = ['STAR_IMPORTS_CONSIDERED_HARMFUL']

# ....................{ GETTERS }....................
def get_cause_or_none_noreturn(sleuth: CauseSleuth) -> 'Optional[str]':
def get_cause_or_none_noreturn(sleuth: CauseSleuth) -> str:
'''
Human-readable string describing the failure of the decorated callable to
*not* return a value in violation of the `PEP 484`_-compliant
Expand Down
10 changes: 5 additions & 5 deletions beartype/_decor/_code/_pep/_error/_peperrorsequence.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@
from beartype._util.hint.pep.utilhintpeptest import is_hint_pep_tuple_empty
from beartype._util.hint.utilhinttest import is_hint_ignorable
from beartype._util.text.utiltextrepr import get_object_representation
from typing import Optional

# See the "beartype.__init__" submodule for further commentary.
__all__ = ['STAR_IMPORTS_CONSIDERED_HARMFUL']

# ....................{ GETTERS ~ sequence }....................
def get_cause_or_none_sequence_standard(
sleuth: CauseSleuth) -> 'Optional[str]':
def get_cause_or_none_sequence_standard(sleuth: CauseSleuth) -> Optional[str]:
'''
Human-readable string describing the failure of the passed arbitrary object
to satisfy the passed **PEP-compliant standard sequence type hint** (i.e.,
Expand Down Expand Up @@ -68,7 +68,7 @@ def get_cause_or_none_sequence_standard(
return _get_cause_or_none_sequence(sleuth)


def get_cause_or_none_tuple(sleuth: CauseSleuth) -> 'Optional[str]':
def get_cause_or_none_tuple(sleuth: CauseSleuth) -> Optional[str]:
'''
Human-readable string describing the failure of the passed arbitrary object
to satisfy the passed **PEP-compliant standard sequence type hint** (i.e.,
Expand Down Expand Up @@ -169,7 +169,7 @@ def get_cause_or_none_tuple(sleuth: CauseSleuth) -> 'Optional[str]':
return None

# ....................{ GETTERS ~ private }....................
def _get_cause_or_none_sequence(sleuth: CauseSleuth) -> 'Optional[str]':
def _get_cause_or_none_sequence(sleuth: CauseSleuth) -> Optional[str]:
'''
Human-readable string describing the failure of the passed arbitrary object
to satisfy the passed **PEP-compliant variadic sequence type hint** (i.e.,
Expand Down Expand Up @@ -219,7 +219,7 @@ def _get_cause_or_none_sequence(sleuth: CauseSleuth) -> 'Optional[str]':
# * "item" is an arbitrary item of this sequence.
# * "item_index" is the 0-based index of this item.
pith_enumerator = None

# If this sequence was indexed by the parent @beartype-generated
# wrapper function by a pseudo-random integer in O(1) time,
# type-check *ONLY* the same index of this sequence also in O(1)
Expand Down
24 changes: 12 additions & 12 deletions beartype/_decor/_code/_pep/_error/_peperrorsleuth.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
is_hint_forwardref,
is_hint_ignorable,
)
from typing import NoReturn
from typing import Any, Callable, NoReturn, Optional, Tuple

# See the "beartype.__init__" submodule for further commentary.
__all__ = ['STAR_IMPORTS_CONSIDERED_HARMFUL']
Expand All @@ -66,14 +66,14 @@ class CauseSleuth(object):
Human-readable label describing the parameter or return value from
which this object originates, typically embedded in exceptions raised
from this getter in the event of unexpected runtime failure.
func : object
func : Callable
Decorated callable generating this type-checking error.
hint : object
hint : Any
Type hint to validate this object against.
hint_sign : object
hint_sign : Any
Unsubscripted :mod:`typing` attribute identifying this hint if this hint
is PEP-compliant *or* ``None`` otherwise.
hint_childs : Optional[tuple]
hint_childs : Optional[Tuple]
Either:
* If this hint is PEP-compliant:
Expand All @@ -86,7 +86,7 @@ class CauseSleuth(object):
hint if this
* Else, ``None``.
pith : object
pith : Any
Arbitrary object to be validated.
random_int: Optional[int]
**Pseudo-random integer** (i.e., unsigned 32-bit integer
Expand Down Expand Up @@ -142,9 +142,9 @@ class CauseSleuth(object):
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
def __init__(
self,
func: object,
pith: object,
hint: object,
func: Callable,
pith: Any,
hint: Any,
cause_indent: str,
exception_label: str,
random_int: int,
Expand All @@ -169,8 +169,8 @@ def __init__(
self.random_int = random_int

# Nullify all remaining parameters for safety.
self.hint_sign = None
self.hint_childs = None
self.hint_sign: Any = None
self.hint_childs: Tuple = None # type: ignore[assignment]

# ................{ REDUCTION }................
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Expand Down Expand Up @@ -233,7 +233,7 @@ def __init__(
)

# ..................{ GETTERS }..................
def get_cause_or_none(self) -> 'Optional[str]':
def get_cause_or_none(self) -> Optional[str]:
'''
Human-readable string describing the failure of this pith to satisfy
this PEP-compliant type hint if this pith fails to satisfy this pith
Expand Down
7 changes: 4 additions & 3 deletions beartype/_decor/_code/_pep/_error/_peperrortype.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@
get_hint_pep_type_origin_or_none)
from beartype._util.py.utilpymodule import import_module_attr
from beartype._util.text.utiltextcause import get_cause_object_not_type
from typing import Optional

# See the "beartype.__init__" submodule for further commentary.
__all__ = ['STAR_IMPORTS_CONSIDERED_HARMFUL']

# ....................{ GETTERS ~ forwardref }....................
def get_cause_or_none_forwardref(sleuth: CauseSleuth) -> 'Optional[str]':
def get_cause_or_none_forwardref(sleuth: CauseSleuth) -> Optional[str]:
'''
Human-readable string describing the failure of the passed arbitrary object
to satisfy the passed **forward reference type hint** (i.e., string whose
Expand Down Expand Up @@ -61,7 +62,7 @@ def get_cause_or_none_forwardref(sleuth: CauseSleuth) -> 'Optional[str]':
return get_cause_or_none_type(sleuth.permute(hint=hint_forwardref_class))

# ....................{ GETTERS ~ type }....................
def get_cause_or_none_type(sleuth: CauseSleuth) -> 'Optional[str]':
def get_cause_or_none_type(sleuth: CauseSleuth) -> Optional[str]:
'''
Human-readable string describing the failure of the passed arbitrary object
to be an instance of the passed non-:mod:`typing` class if this object is
Expand Down Expand Up @@ -94,7 +95,7 @@ def get_cause_or_none_type(sleuth: CauseSleuth) -> 'Optional[str]':
return get_cause_object_not_type(pith=sleuth.pith, hint=sleuth.hint)


def get_cause_or_none_type_origin(sleuth: CauseSleuth) -> 'Optional[str]':
def get_cause_or_none_type_origin(sleuth: CauseSleuth) -> Optional[str]:
'''
Human-readable string describing the failure of the passed arbitrary object
to satisfy the passed **PEP-compliant originative type hint** (i.e.,
Expand Down
3 changes: 2 additions & 1 deletion beartype/_decor/_code/_pep/_error/_peperrorunion.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,13 @@
from beartype._util.text.utiltextmunge import (
suffix_unless_suffixed, uppercase_char_first)
from beartype._util.text.utiltextrepr import get_object_representation
from typing import Optional

# See the "beartype.__init__" submodule for further commentary.
__all__ = ['STAR_IMPORTS_CONSIDERED_HARMFUL']

# ....................{ GETTERS }....................
def get_cause_or_none_union(sleuth: CauseSleuth) -> 'Optional[str]':
def get_cause_or_none_union(sleuth: CauseSleuth) -> Optional[str]:
'''
Human-readable string describing the failure of the passed arbitrary object
to satisfy the passed PEP-compliant union type hint if this object actually
Expand Down
13 changes: 7 additions & 6 deletions beartype/_decor/_code/_pep/_error/peperror.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@
)
from beartype._util.text.utiltextmunge import suffix_unless_suffixed
from beartype._util.text.utiltextrepr import get_object_representation
from typing import Generic
from collections.abc import Callable
from typing import Generic, Optional

# See the "beartype.__init__" submodule for further commentary.
__all__ = ['STAR_IMPORTS_CONSIDERED_HARMFUL']
Expand Down Expand Up @@ -133,12 +134,12 @@
# ....................{ RAISERS }....................
def raise_pep_call_exception(
# Mandatory parameters.
func: 'CallableTypes',
func: Callable,
pith_name: str,
pith_value: object,

# Optional parameters.
random_int: 'Optional[int]' = None,
random_int: Optional[int] = None,
) -> None:
'''
Raise a human-readable exception detailing the failure of the parameter
Expand Down Expand Up @@ -239,10 +240,10 @@ def raise_pep_call_exception(
# )'''.format(func, pith_name, pith_value))

# Type of exception to be raised.
exception_cls = None
exception_cls: type = None # type: ignore[assignment]

# Human-readable label describing this parameter or return value.
pith_label = None
pith_label: str = None # type: ignore[assignment]

# If the name of this parameter is the magic string implying the passed
# object to be a return value, set the above local variables appropriately.
Expand Down Expand Up @@ -296,7 +297,7 @@ def raise_pep_call_exception(
text=exception_cause, suffix='.')

# Raise an exception of the desired class embedding this cause.
raise exception_cls(
raise exception_cls( # type: ignore[misc]
f'{pith_label} violates type hint '
f'{repr(hint)}, as {exception_cause_suffixed}'
)
Expand Down
7 changes: 7 additions & 0 deletions beartype/_decor/_code/_pep/_pephint.py
Original file line number Diff line number Diff line change
Expand Up @@ -1394,6 +1394,13 @@
#* Is annotated by the third-party optional "typeshed" dependency.
#
#Trivial, but worth noting.
#FIXME: Lastly, note that everywhere we say "typeshed" above, we *REALLY* mean
#a PEP 561-compliant search for stub files annotating that callable.
#Unsurprisingly, the search algorithm is non-trivial, which will impact the
#performance gains associated with type-checking annotations in the first
#place. Ergo, we might consider omitting aspects of this search that are both
#highly inefficient *AND* unlikely to yield positive hits. See also:
# https://www.python.org/dev/peps/pep-0561/

#FIXME: *IT'S CONFIGURATION TIME.* So, let's talk about how we efficiently
#handle @beartype configuration like the "is_proxying" boolean introduced
Expand Down
8 changes: 4 additions & 4 deletions beartype/_decor/_code/_pep/pepcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
from beartype._util.text.utiltextmunge import replace_str_substrs
from collections.abc import Callable, Iterable
from inspect import Parameter
from typing import NoReturn, Union
from typing import NoReturn, Tuple, Union

# See the "beartype.__init__" submodule for further commentary.
__all__ = ['STAR_IMPORTS_CONSIDERED_HARMFUL']
Expand Down Expand Up @@ -157,7 +157,7 @@ def pep_code_check_param(
hint: object,
param: Parameter,
param_index: int,
) -> 'Tuple[str, bool]':
) -> Tuple[str, bool]:
'''
Python code type-checking the parameter with the passed signature and index
annotated by a **PEP-compliant type hint** (e.g., :mod:`beartype`-agnostic
Expand Down Expand Up @@ -295,7 +295,7 @@ def pep_code_check_param(
def pep_code_check_return(
data: BeartypeData,
hint: object,
) -> 'Tuple[str, bool]':
) -> Tuple[str, bool]:
'''
Python code type-checking the return value annotated with a **PEP-compliant
type hint** (e.g., :mod:`beartype`-agnostic annotation compliant with
Expand Down Expand Up @@ -327,7 +327,7 @@ def pep_code_check_return(

# Memoized parameter-agnostic Python code type-checking either a parameter
# or return value with an arbitrary name.
func_code = None
func_code: str = None # type: ignore[assignment]

# True only if type-checking for this return value requires a higher-level
# caller to prefix the body of this wrapper function with code generating
Expand Down
7 changes: 4 additions & 3 deletions beartype/_decor/_code/codemain.py
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,7 @@
label_callable_decorated_return,
)
from inspect import Parameter, Signature
from typing import Tuple

# See the "beartype.__init__" submodule for further commentary.
__all__ = ['STAR_IMPORTS_CONSIDERED_HARMFUL']
Expand Down Expand Up @@ -619,7 +620,7 @@
'''

# ....................{ CODERS }....................
def generate_code(data: BeartypeData) -> None:
def generate_code(data: BeartypeData) -> Tuple[str, bool]:
'''
Set the :attr:`BeartypeData.func_code` instance variable of the passed data
object to a raw string of Python statements implementing the wrapper
Expand Down Expand Up @@ -746,7 +747,7 @@ def generate_code(data: BeartypeData) -> None:
return func_code, is_func_code_noop

# ....................{ CODERS ~ private }....................
def _code_check_params(data: BeartypeData) -> 'Tuple[str, bool]':
def _code_check_params(data: BeartypeData) -> Tuple[str, bool]:
'''
Python code type-checking all annotated parameters of the decorated
callable if any *or* the empty string otherwise (i.e., if these parameters
Expand Down Expand Up @@ -918,7 +919,7 @@ def _code_check_params(data: BeartypeData) -> 'Tuple[str, bool]':
)

# ....................{ CODERS }....................
def _code_check_return(data: BeartypeData) -> 'Tuple[str, bool]':
def _code_check_return(data: BeartypeData) -> Tuple[str, bool]:
'''
Python code snippet type-checking the annotated return value declared by
the decorated callable if any *or* the empty string otherwise (i.e., if
Expand Down

0 comments on commit e2144fe

Please sign in to comment.