Skip to content

Commit

Permalink
Reiterables x 11.
Browse files Browse the repository at this point in the history
This commit is the next in a commit chain deeply type-checking
**reiterables** (i.e., collections satisfying the
`collections.abc.Collection` protocol with guaranteed `O(1)` read-only
access to *only* the first collection item), en-route to *finally*
resolving feature request #167 kindly submitted by the perennial
brilliant @langfield (...*how I miss that awesome guy!*) several
lifetimes ago back when I was probably a wandering vagabond Buddhist
monk with a bad attitude, a begging bowl the size of my emaciated torso,
and an honestly pretty cool straw hat that glinted dangerously in the
firelight. Note that reiterables include *all* containers matched by one
or more of the following PEP 484- or 585-compliant type hints:

* `frozenset[...]`.
* `set[...]`.
* `collections.deque[...]`.
* `collections.abc.Collection[...]`.
* `collections.abc.KeysView[...]`.
* `collections.abc.MutableSet[...]`.
* `collections.abc.Set[...]`.
* `collections.abc.ValuesView[...]`.
* `typing.AbstractSet[...]`.
* `typing.Collection[...]`.
* `typing.Deque[...]`.
* `typing.FrozenSet[...]`.
* `typing.KeysView[...]`.
* `typing.MutableSet[...]`.
* `typing.Set[...]`.
* `typing.ValuesView[...]`.

Specifically, this commit *finally* finishes reducing problematic DRY
(Don't Repeat Yourself) violations in @beartype's internal type-checking
code generator. It took forever, peeps. It took a small chunk out of
@leycec's withered and weather-beaten soul. *But it was all worth it.*
(*Withered and weather-beaten beets beat staccato tacos!*)
  • Loading branch information
leycec committed Jun 21, 2024
1 parent c1bed40 commit a144f21
Show file tree
Hide file tree
Showing 19 changed files with 255 additions and 379 deletions.
4 changes: 2 additions & 2 deletions beartype/_check/code/codemake.py
Original file line number Diff line number Diff line change
Expand Up @@ -1449,8 +1449,8 @@ def _enqueue_hint_child(pith_child_expr: str) -> str:
hint_curr_sign)

# If *NO* hint sign logic type-checks this sign, raise
# an exception. Note that, though this logic should
# *ALWAYS* be non-"None", assumptions make a donkey.
# an exception. Note that this logic should *ALWAYS* be
# non-"None". Nonetheless, assumptions make a donkey.
if hint_sign_logic is None: # pragma: no cover
raise BeartypeDecorHintPepException(
f'{EXCEPTION_PREFIX} '
Expand Down
41 changes: 16 additions & 25 deletions beartype/_check/error/_errmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
Dict,
)
from beartype._data.hint.pep.sign.datapepsigncls import HintSign
from beartype._check.error._errcause import ViolationCause
from beartype._check.error.errcause import ViolationCause

# ....................{ GLOBALS }....................
# Initialized with automated inspection below in the _init() function.
Expand Down Expand Up @@ -49,8 +49,7 @@ def _init() -> None:
from beartype._data.hint.pep.sign.datapepsignset import (
HINT_SIGNS_MAPPING,
HINT_SIGNS_ORIGIN_ISINSTANCEABLE,
HINT_SIGNS_REITERABLE_ARGS_1,
HINT_SIGNS_SEQUENCE_ARGS_1,
HINT_SIGNS_CONTAINER_ARGS_1,
HINT_SIGNS_UNION,
)
from beartype._check.error._errtype import (
Expand All @@ -62,40 +61,32 @@ def _init() -> None:
from beartype._check.error._pep.errpep484604 import find_cause_union
from beartype._check.error._pep.errpep586 import find_cause_literal
from beartype._check.error._pep.errpep593 import find_cause_annotated
from beartype._check.error._pep.pep484585.errpep484585container import (
find_cause_container_args_1,
find_cause_tuple_fixed,
)
from beartype._check.error._pep.pep484585.errpep484585generic import (
find_cause_generic)
from beartype._check.error._pep.pep484585.errpep484585mapping import (
find_cause_mapping)
from beartype._check.error._pep.pep484585.errpep484585reiterable import (
find_cause_reiterable)
from beartype._check.error._pep.pep484585.errpep484585sequence import (
find_cause_sequence_args_1,
find_cause_tuple_fixed,
)

# Map each originative sign to the appropriate finder *BEFORE* any other
# mappings. This is merely a generalized fallback subsequently replaced by
# sign-specific finders below.
for pep_sign_origin_isinstanceable in HINT_SIGNS_ORIGIN_ISINSTANCEABLE:
HINT_SIGN_TO_GET_CAUSE_FUNC[pep_sign_origin_isinstanceable] = (
find_cause_type_instance_origin)
for hint_sign in HINT_SIGNS_ORIGIN_ISINSTANCEABLE:
HINT_SIGN_TO_GET_CAUSE_FUNC[hint_sign] = find_cause_type_instance_origin

# Map each 2-argument mapping sign to its corresponding finder.
for pep_sign_mapping in HINT_SIGNS_MAPPING:
HINT_SIGN_TO_GET_CAUSE_FUNC[pep_sign_mapping] = find_cause_mapping

# Map each 1-argument reiterable sign to its corresponding finder.
for pep_sign_reiterable in HINT_SIGNS_REITERABLE_ARGS_1:
HINT_SIGN_TO_GET_CAUSE_FUNC[pep_sign_reiterable] = find_cause_reiterable
# Map each 1-argument container sign to its corresponding finder.
for hint_sign in HINT_SIGNS_CONTAINER_ARGS_1:
HINT_SIGN_TO_GET_CAUSE_FUNC[hint_sign] = find_cause_container_args_1

# Map each 1-argument sequence sign to its corresponding finder.
for pep_sign_sequence_args_1 in HINT_SIGNS_SEQUENCE_ARGS_1:
HINT_SIGN_TO_GET_CAUSE_FUNC[pep_sign_sequence_args_1] = (
find_cause_sequence_args_1)
# Map each 2-argument mapping sign to its corresponding finder.
for hint_sign in HINT_SIGNS_MAPPING:
HINT_SIGN_TO_GET_CAUSE_FUNC[hint_sign] = find_cause_mapping

# Map each union-specific sign to its corresponding finder.
for pep_sign_type_union in HINT_SIGNS_UNION:
HINT_SIGN_TO_GET_CAUSE_FUNC[pep_sign_type_union] = find_cause_union
for hint_sign in HINT_SIGNS_UNION:
HINT_SIGN_TO_GET_CAUSE_FUNC[hint_sign] = find_cause_union

# Map each sign validated by a unique finder to that finder *AFTER* all
# other mappings. These sign-specific finders are intended to replace all
Expand Down
2 changes: 1 addition & 1 deletion beartype/_check/error/_errtype.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
HintSignType,
HintSignUnion,
)
from beartype._check.error._errcause import ViolationCause
from beartype._check.error.errcause import ViolationCause
from beartype._util.cls.utilclstest import is_type_subclass
from beartype._util.cls.pep.utilpep3119 import (
die_unless_object_issubclassable,
Expand Down
2 changes: 1 addition & 1 deletion beartype/_check/error/_pep/errpep484.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
# ....................{ IMPORTS }....................
from beartype.typing import Callable
from beartype._data.hint.pep.sign.datapepsigns import HintSignNoReturn
from beartype._check.error._errcause import ViolationCause
from beartype._check.error.errcause import ViolationCause
from beartype._util.text.utiltextlabel import label_callable
from beartype._util.text.utiltextrepr import represent_pith

Expand Down
2 changes: 1 addition & 1 deletion beartype/_check/error/_pep/errpep484604.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
# ....................{ IMPORTS }....................
from beartype.roar._roarexc import _BeartypeCallHintPepRaiseException
from beartype._data.hint.pep.sign.datapepsignset import HINT_SIGNS_UNION
from beartype._check.error._errcause import ViolationCause
from beartype._check.error.errcause import ViolationCause
from beartype._util.hint.pep.utilpepget import (
get_hint_pep_origin_type_isinstanceable_or_none)
from beartype._util.hint.pep.utilpeptest import is_hint_pep
Expand Down
2 changes: 1 addition & 1 deletion beartype/_check/error/_pep/errpep586.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
'''

# ....................{ IMPORTS }....................
from beartype._check.error._errcause import ViolationCause
from beartype._check.error.errcause import ViolationCause
from beartype._data.hint.pep.sign.datapepsigns import HintSignLiteral
from beartype._util.hint.pep.proposal.utilpep586 import (
get_hint_pep586_literals)
Expand Down
2 changes: 1 addition & 1 deletion beartype/_check/error/_pep/errpep593.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

# ....................{ IMPORTS }....................
from beartype.roar._roarexc import _BeartypeCallHintPepRaiseException
from beartype._check.error._errcause import ViolationCause
from beartype._check.error.errcause import ViolationCause
from beartype._data.hint.pep.sign.datapepsigns import HintSignAnnotated
from beartype._util.hint.pep.proposal.utilpep593 import (
get_hint_pep593_metadata,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,52 +4,54 @@
# See "LICENSE" for further details.

'''
Beartype :pep:`484`- and :pep:`585`-compliant **sequence type hint violation
describers** (i.e., functions returning human-readable strings explaining
violations of :pep:`484`- and :pep:`585`-compliant sequence type hints).
Beartype :pep:`484`- and :pep:`585`-compliant **single-argument sequence type
hint violation finders** (i.e., functions returning human-readable strings
explaining violations of :pep:`484`- and :pep:`585`-compliant type hints
subscripted by one child type hint constraining *all* items contained in that
container satisfying the :class:`collections.abc.Container` protocol).
This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS }....................
from beartype.roar._roarexc import _BeartypeCallHintPepRaiseException
from beartype._check.logic.logmap import (
HINT_SIGN_PEP484585_CONTAINER_ARGS_1_TO_LOGIC)
from beartype._check.error.errcause import ViolationCause
from beartype._check.error._errtype import find_cause_type_instance_origin
from beartype._data.hint.pep.sign.datapepsigns import HintSignTupleFixed
from beartype._data.hint.pep.sign.datapepsignmap import (
HINT_SIGN_ORIGIN_ISINSTANCEABLE_TO_ARGS_LEN_RANGE)
from beartype._data.hint.pep.sign.datapepsignset import (
HINT_SIGNS_SEQUENCE_ARGS_1)
from beartype._check.error._errcause import ViolationCause
from beartype._check.error._errtype import find_cause_type_instance_origin
HINT_SIGNS_CONTAINER_ARGS_1)
from beartype._util.hint.pep.proposal.pep484585.utilpep484585tuple import (
is_hint_pep484585_tuple_empty)
from beartype._util.text.utiltextansi import color_type
from beartype._util.text.utiltextprefix import prefix_pith_type
from beartype._util.text.utiltextrepr import represent_pith

# ....................{ FINDERS }....................
def find_cause_sequence_args_1(cause: ViolationCause) -> ViolationCause:
def find_cause_container_args_1(cause: ViolationCause) -> ViolationCause:
'''
Output cause describing whether the pith of the passed input cause either
satisfies or violates the **single-argument variadic sequence type hint**
(i.e., PEP-compliant type hint accepting exactly one subscripted argument
constraining *all* items of this pith, which necessarily satisfies the
:class:`collections.abc.Sequence` protocol with guaranteed :math:`O(1)`
indexation across all sequence items) of that cause.
satisfies or violates the **single-argument container type hint**
(i.e., :pep:`484`- or :pep:`585`-compliant type hint subscripted by one
child type hint constraining *all* items contained in that container
satisfying the :class:`collections.abc.Container` protocol) of that cause.
Parameters
----------
cause : ViolationCause
Input cause providing this data.
Input violation cause finder to be inspected.
Returns
-------
ViolationCause
Output cause type-checking this data.
Output violation cause finder type-checking this input.
'''
assert isinstance(cause, ViolationCause), f'{repr(cause)} not cause.'
assert cause.hint_sign in HINT_SIGNS_SEQUENCE_ARGS_1, (
f'{repr(cause.hint)} neither '
f'1-argument sequence nor variable-length tuple hint.'
)
assert cause.hint_sign in HINT_SIGNS_CONTAINER_ARGS_1, (
f'{repr(cause.hint)} not 1-argument container type hint.')

# Number of child type hints expected to be subscripting this hint.
hints_child_len_expected = (
Expand All @@ -62,9 +64,9 @@ def find_cause_sequence_args_1(cause: ViolationCause) -> ViolationCause:
f'{len(cause.hint_childs)} not in {hints_child_len_expected}.'
)

# First child hint subscripting this parent sequence hint. All remaining
# First child hint subscripting this parent container hint. All remaining
# child hints if any are ignorable. Specifically, if this hint is:
# * A standard sequence (e.g., "typing.List[str]"), this hint is subscripted
# * A standard container (e.g., "typing.List[str]"), this hint is subscripted
# by only one child hint.
# * A variadic tuple (e.g., "typing.Tuple[str, ...]"), this hint is
# subscripted by only two child hints -- the latter of which is guaranteed
Expand All @@ -84,54 +86,39 @@ def find_cause_sequence_args_1(cause: ViolationCause) -> ViolationCause:
#
# If either...
elif (
# This sequence is empty, all items of this sequence (of which there are
# This container is empty, all items of this container (of which there are
# none) are necessarily valid *OR*...
not cause.pith or
# This child hint is ignorable...
hint_child is None
):
# Then this sequence satisfies this hint. In this case, return the
# Then this container satisfies this hint. In this case, return the
# passed cause as is.
return cause
# Else, this sequence is non-empty *AND* this child hint is unignorable.

# Arbitrary iterator satisfying the enumerate() protocol, yielding zero or
# more 2-tuples of the form "(item_index, item)", where:
# * "item_index" is the 0-based index of this item.
# * "item" is an arbitrary item of this sequence.
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) time. Since the current call to
# that function failed a type-check, either this index is the index
# responsible for that failure *OR* this sequence is valid and another
# container is responsible for that failure. In either case, no other
# indices of this sequence need be checked.
if cause.random_int is not None:
# 0-based index of this item calculated from this random integer in the
# *SAME EXACT WAY* as in the parent @beartype-generated wrapper.
pith_item_index = cause.random_int % len(cause.pith)

# Pseudo-random item with this index in this sequence.
pith_item = cause.pith[pith_item_index]

# 2-tuple of this index and item in the same order as the 2-tuples
# returned by the enumerate() builtin.
pith_enumeratable = (pith_item_index, pith_item)

# Iterator yielding only this 2-tuple.
pith_enumerator = iter((pith_enumeratable,))
# print(f'Checking item {pith_item_index} in O(1) time!')
# Else, this sequence was iterated by the parent @beartype-generated wrapper
# function in O(n) time. In this case, type-check *ALL* indices of this
# sequence in O(n) time as well.
else:
# Iterator yielding all indices and items of this sequence.
pith_enumerator = enumerate(cause.pith)
# print('Checking sequence in O(n) time!')

# For each enumerated item of this sequence...
# Else, this container is non-empty *AND* this child hint is unignorable.

# Hint sign logic type-checking this sign if any *OR* "None" otherwise.
hint_sign_logic = HINT_SIGN_PEP484585_CONTAINER_ARGS_1_TO_LOGIC.get(
cause.hint_sign)

# If *NO* hint sign logic type-checks this sign, raise an exception. Note
# that this logic should *ALWAYS* be non-"None". Nonetheless, assumptions.
if hint_sign_logic is None: # pragma: no cover
raise _BeartypeCallHintPepRaiseException(
f'{cause.exception_prefix}1-argument container type hint '
f'{repr(cause.hint)} beartype sign {repr(cause.hint_sign)} '
f'code generation logic not found.'
)
# Else, some hint sign logic type-checks this sign.

# Arbitrary iterator over this container configured by this beartype
# configuration satisfying the enumerate() protocol. This iterator yields
# zero or more 2-tuples of the form "(item_index, item)", where:
# * "item_index" is the 0-based index of each item.
# * "item" is an arbitrary item of this container.
pith_enumerator = hint_sign_logic.enumerate_cause_items(cause)

# For each enumerated item of this container...
for pith_item_index, pith_item in pith_enumerator:
# Deep output cause describing the failure of this item to satisfy this
# child hint if this item violates this child hint *OR* "None" otherwise
Expand All @@ -153,8 +140,8 @@ def find_cause_sequence_args_1(cause: ViolationCause) -> ViolationCause:
# Else, this item is *NOT* the cause of this failure. Silently continue
# to the next item.

# Return this cause as is; all items of this sequence are valid, implying
# this sequence to deeply satisfy this hint.
# Return this cause as is; all items of this container are valid, implying
# this container to deeply satisfy this hint.
return cause


Expand All @@ -169,12 +156,12 @@ def find_cause_tuple_fixed(cause: ViolationCause) -> ViolationCause:
Parameters
----------
cause : ViolationCause
Input cause providing this data.
Input violation cause finder to be inspected.
Returns
-------
ViolationCause
Output cause type-checking this data.
Output violation cause finder type-checking this input.
'''
assert isinstance(cause, ViolationCause), f'{repr(cause)} not cause.'
assert cause.hint_sign is HintSignTupleFixed, (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

# ....................{ IMPORTS }....................
from beartype._data.hint.pep.sign.datapepsigns import HintSignGeneric
from beartype._check.error._errcause import ViolationCause
from beartype._check.error.errcause import ViolationCause
from beartype._check.error._errtype import find_cause_instance_type
from beartype._util.hint.pep.proposal.pep484585.utilpep484585generic import (
get_hint_pep484585_generic_type,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
Tuple,
)
from beartype._data.hint.pep.sign.datapepsignset import HINT_SIGNS_MAPPING
from beartype._check.error._errcause import ViolationCause
from beartype._check.error.errcause import ViolationCause
from beartype._check.error._errtype import find_cause_type_instance_origin
from beartype._util.text.utiltextprefix import prefix_pith_type
from beartype._util.text.utiltextrepr import represent_pith
Expand Down
Loading

0 comments on commit a144f21

Please sign in to comment.