Skip to content

Commit

Permalink
Parametrized generic detection x 3.
Browse files Browse the repository at this point in the history
This commit is the next in a commit chain detecting parametrized
generics (i.e., user-defined generics subscripted by one or more type
variables) en-route to resolving issue #29, kindly submitted by
indefatigable test engineer and anthropomorphic Siberian Husky @eehusky.
Specifically, this commit successfully resolves this issue for both
Python 3.6 and 3.7 but has yet to resolve this issue for either Python
3.8 or 3.9, where tests still regrettably fail. (*Outlandish lands!*)
  • Loading branch information
leycec committed Mar 2, 2021
1 parent de8f6f2 commit 5a03fbc
Show file tree
Hide file tree
Showing 22 changed files with 1,154 additions and 726 deletions.
12 changes: 9 additions & 3 deletions beartype/_decor/_code/_pep/_error/_peperrorgeneric.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
from beartype._util.hint.utilhinttest import is_hint_ignorable
from beartype._util.hint.pep.proposal.utilhintpep484 import (
get_hint_pep484_generic_base_erased_from_unerased)
from beartype._util.hint.pep.proposal.utilhintpep585 import is_hint_pep585
from beartype._util.hint.pep.proposal.utilhintpep585 import is_hint_pep585_builtin
from beartype._util.hint.pep.utilhintpepget import (
get_hint_pep_origin_type_generic_or_none)
from beartype._util.hint.pep.utilhintpeptest import is_hint_pep_typing
from typing import Generic, Optional

Expand All @@ -42,10 +44,14 @@ def get_cause_or_none_generic(sleuth: CauseSleuth) -> Optional[str]:
Type-checking error cause sleuth.
'''
assert isinstance(sleuth, CauseSleuth), f'{repr(sleuth)} not cause sleuth.'
assert isinstance(sleuth.hint, type), f'{repr(sleuth.hint)} not class.'
assert sleuth.hint_sign is Generic, (
f'{repr(sleuth.hint_sign)} not generic.')

# If this hint is *NOT* a class, reduce this hint to the object originating
# this hint if any. See the is_hint_pep484_generic() tester for details.
sleuth.hint = get_hint_pep_origin_type_generic_or_none(sleuth.hint)
assert isinstance(sleuth.hint, type), f'{repr(sleuth.hint)} not class.'

# If this pith is *NOT* an instance of this generic, defer to the getter
# function handling non-"typing" classes.
if not isinstance(sleuth.pith, sleuth.hint):
Expand All @@ -67,7 +73,7 @@ def get_cause_or_none_generic(sleuth: CauseSleuth) -> Optional[str]:
# *NOR* a PEP-compliant type hint defined by the "typing" module,
# reduce this pseudo-superclass to a real superclass originating this
# pseudo-superclass. See commentary in the "_pephint" submodule.
elif not (is_hint_pep585(hint_base) and is_hint_pep_typing(hint_base)):
elif not (is_hint_pep585_builtin(hint_base) and is_hint_pep_typing(hint_base)):
hint_base = get_hint_pep484_generic_base_erased_from_unerased(
hint_base)
# Else, this pseudo-superclass is defined by the "typing" module.
Expand Down
4 changes: 2 additions & 2 deletions beartype/_decor/_code/_pep/_error/_peperrorsequence.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
HINT_PEP_SIGNS_SEQUENCE_STANDARD,
HINT_PEP_SIGNS_TUPLE,
)
from beartype._util.hint.pep.utilhintpepget import get_hint_pep_origin_type
from beartype._util.hint.pep.utilhintpepget import get_hint_pep_origin_type_stdlib
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
Expand Down Expand Up @@ -56,7 +56,7 @@ def get_cause_or_none_sequence_standard(sleuth: CauseSleuth) -> Optional[str]:
f'multiple arguments.')

# Non-"typing" class originating this attribute (e.g., "list" for "List").
hint_type_origin = get_hint_pep_origin_type(sleuth.hint)
hint_type_origin = get_hint_pep_origin_type_stdlib(sleuth.hint)

# If this pith is *NOT* an instance of this class, defer to the getter
# function handling non-"typing" classes.
Expand Down
81 changes: 49 additions & 32 deletions beartype/_decor/_code/_pep/_error/_peperrorsleuth.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,6 @@ class CauseSleuth(object):
from this getter in the event of unexpected runtime failure.
func : Callable
Decorated callable generating this type-checking error.
hint : Any
Type hint to validate this object against.
hint_sign : Any
Unsubscripted :mod:`typing` attribute identifying this hint if this hint
is PEP-compliant *or* ``None`` otherwise.
Expand Down Expand Up @@ -97,6 +95,11 @@ class CauseSleuth(object):
such integer). See the same parameter accepted by the higher-level
:func:`beartype._decor._code._pep._error.peperror.raise_pep_call_exception`
function for further details.
Attributes (Private)
----------
_hint : Any
Type hint to validate this object against.
'''

# ..................{ CLASS VARIABLES }..................
Expand All @@ -107,11 +110,11 @@ class CauseSleuth(object):
'cause_indent',
'exception_label',
'func',
'hint',
'hint_sign',
'hint_childs',
'pith',
'random_int',
'_hint',
)


Expand All @@ -128,13 +131,6 @@ class CauseSleuth(object):
method, defined as a set to enable efficient membership testing.
'''


_VAR_NAMES = frozenset(__slots__)
'''
Frozen set of the names of all instance variables permitted on this object,
defined as a set to enable efficient membership testing.
'''

# ..................{ INITIALIZERS }..................
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# CAUTION: Whenever adding, deleting, or renaming any parameter accepted by
Expand Down Expand Up @@ -163,7 +159,6 @@ def __init__(
# Classify all passed parameters.
self.func = func
self.pith = pith
self.hint = hint
self.cause_indent = cause_indent
self.exception_label = exception_label
self.random_int = random_int
Expand All @@ -172,6 +167,25 @@ def __init__(
self.hint_sign: Any = None
self.hint_childs: Tuple = None # type: ignore[assignment]

# Classify this hint *AFTER* initializing all parameters above.
self.hint = hint

# ..................{ PROPERTIES }..................
@property
def hint(self) -> Any:
'''
Type hint to validate this object against.
'''

return self._hint


@hint.setter
def hint(self, hint: Any) -> None:
'''
Set the type hint to validate this object against.
'''

# ................{ REDUCTION }................
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# CAVEATS: Synchronize changes here with the corresponding block of the
Expand All @@ -188,13 +202,13 @@ def __init__(
# "typing" module, PEP 484 explicitly supports this singleton:
# When used in a type hint, the expression None is considered
# equivalent to type(None).
if self.hint is None:
self.hint = NoneType
if hint is None:
hint = NoneType
# If this is a PEP 484-compliant new type hint, reduce this hint to the
# user-defined class aliased by this hint. Although this logic could
# also be performed below, doing so here simplifies matters.
elif is_hint_pep484_newtype(self.hint):
self.hint = get_hint_pep484_newtype_class(self.hint)
elif is_hint_pep484_newtype(hint):
hint = get_hint_pep484_newtype_class(hint)
# ................{ REDUCTION ~ pep 544 }................
# If this is a PEP 484-compliant IO generic base class *AND* the active
# Python interpreter targets at least Python >= 3.8 and thus supports
Expand All @@ -207,31 +221,34 @@ def __init__(
# classes from third-party classes). Ergo, we can neither safely emit
# warnings nor raise exceptions on visiting these classes under *ANY*
# Python version.
elif is_hint_pep544_io_generic(self.hint):
self.hint = get_hint_pep544_io_protocol_from_generic(self.hint)
elif is_hint_pep544_io_generic(hint):
hint = get_hint_pep544_io_protocol_from_generic(hint)
# ................{ REDUCTION ~ pep 593 }................
# If this is a PEP 593-compliant type metahint, ignore all annotations
# on this hint (i.e., "hint_curr.__metadata__" tuple) by reducing this
# hint to its origin (e.g., "str" in "Annotated[str, 50, False]").
elif is_hint_pep593(self.hint):
self.hint = get_hint_pep593_hint(self.hint)
elif is_hint_pep593(hint):
hint = get_hint_pep593_hint(hint)
# ................{ REDUCTION ~ end }................

# If this hint is PEP-compliant...
if is_hint_pep(self.hint):
if is_hint_pep(hint):
# Arbitrary object uniquely identifying this hint.
self.hint_sign = get_hint_pep_sign(self.hint)
self.hint_sign = get_hint_pep_sign(hint)

# Tuple of either...
self.hint_childs = (
# If this hint is a generic, the one or more unerased
# pseudo-superclasses originally subclassed by this hint.
get_hint_pep_generic_bases_unerased(self.hint)
if is_hint_pep_generic(self.hint) else
get_hint_pep_generic_bases_unerased(hint)
if is_hint_pep_generic(hint) else
# Else, the zero or more arguments subscripting this hint.
get_hint_pep_args(self.hint)
get_hint_pep_args(hint)
)

# Classify this hint *AFTER* all other assignments above.
self._hint = hint

# ..................{ GETTERS }..................
def get_cause_or_none(self) -> Optional[str]:
'''
Expand Down Expand Up @@ -426,18 +443,18 @@ def permute(self, **kwargs) -> 'CauseSleuth':

# For the name of each passed keyword argument...
for param_name in kwargs.keys():
# If this copy does *NOT* already define an instance variable of
# the same name, raise an exception.
if param_name not in self._VAR_NAMES:
# If this name is *NOT* that of a parameter accepted by the
# __init__() method, raise an exception.
if param_name not in self._INIT_PARAM_NAMES:
raise _BeartypeCallHintPepRaiseException(
f'Unrecognized instance variable '
f'{self.__class__.__name__}.{param_name} not permutable.'
f'{self.__class__}.__init__() parameter '
f'{param_name} unrecognized.'
)

# For the name of each instance variable initializable by this class...
# For the name of each parameter accepted by the __init__() method...
for param_name in self._INIT_PARAM_NAMES:
# If this variable is not already defined by these arguments,
# cascade the current value of this variable into these arguments.
# If this parameter was *NOT* explicitly passed by the caller,
# default this parameter to its current value from this object.
if param_name not in kwargs:
kwargs[param_name] = getattr(self, param_name)

Expand Down
4 changes: 2 additions & 2 deletions beartype/_decor/_code/_pep/_error/_peperrortype.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from beartype._util.hint.utilhintget import (
get_hint_forwardref_classname_relative_to_obj)
from beartype._util.hint.pep.utilhintpepget import (
get_hint_pep_origin_type_or_none)
get_hint_pep_origin_type_stdlib_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
Expand Down Expand Up @@ -111,7 +111,7 @@ def get_cause_or_none_type_origin(sleuth: CauseSleuth) -> Optional[str]:
assert isinstance(sleuth, CauseSleuth), f'{repr(sleuth)} not cause sleuth.'

# Origin type originating this hint if any *OR* "None" otherwise.
hint_type_origin = get_hint_pep_origin_type_or_none(sleuth.hint)
hint_type_origin = get_hint_pep_origin_type_stdlib_or_none(sleuth.hint)

# If this hint does *NOT* originate from such a type, raise an exception.
if hint_type_origin is None:
Expand Down
4 changes: 2 additions & 2 deletions beartype/_decor/_code/_pep/_error/_peperrorunion.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from beartype._util.hint.data.pep.proposal.utilhintdatapep484 import (
HINT_PEP484_SIGNS_UNION)
from beartype._util.hint.pep.utilhintpepget import (
get_hint_pep_origin_type_or_none)
get_hint_pep_origin_type_stdlib_or_none)
from beartype._util.hint.pep.utilhintpeptest import is_hint_pep
from beartype._util.text.utiltextjoin import join_delimited_disjunction_classes
from beartype._util.text.utiltextmunge import (
Expand Down Expand Up @@ -72,7 +72,7 @@ def get_cause_or_none_union(sleuth: CauseSleuth) -> Optional[str]:
if is_hint_pep(hint_child):
# Non-"typing" class originating this child hint if any *OR* "None"
# otherwise.
hint_child_type_origin = get_hint_pep_origin_type_or_none(
hint_child_type_origin = get_hint_pep_origin_type_stdlib_or_none(
hint_child)

# If...
Expand Down
4 changes: 2 additions & 2 deletions beartype/_decor/_code/_pep/_error/peperror.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
from beartype._util.hint.data.pep.utilhintdatapep import (
HINT_PEP_SIGNS_SEQUENCE_STANDARD,
HINT_PEP_SIGNS_TUPLE,
HINT_PEP_SIGNS_TYPE_ORIGIN,
HINT_PEP_SIGNS_TYPE_ORIGIN_STDLIB,
)
from beartype._util.hint.data.pep.proposal.utilhintdatapep484 import (
HINT_PEP484_BASE_FORWARDREF,
Expand Down Expand Up @@ -329,7 +329,7 @@ def _init() -> None:
# Map each originative "typing" attribute to the appropriate getter
# *BEFORE* mapping any other attributes. This is merely a generalized
# fallback subsequently replaced by attribute-specific getters.
for pep_sign_type_origin in HINT_PEP_SIGNS_TYPE_ORIGIN:
for pep_sign_type_origin in HINT_PEP_SIGNS_TYPE_ORIGIN_STDLIB:
PEP_HINT_SIGN_TO_GET_CAUSE_FUNC[pep_sign_type_origin] = (
get_cause_or_none_type_origin)

Expand Down

0 comments on commit 5a03fbc

Please sign in to comment.