From a2bc2d7b873a3dd32296b18a37d107c24794359e Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Wed, 30 Apr 2025 18:38:43 -0400 Subject: [PATCH 1/3] feat(mypy): use typing_extensions.ParamSpec for decorator typing --- eth_utils/decorators.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/eth_utils/decorators.py b/eth_utils/decorators.py index 0268be25..0988b8b4 100644 --- a/eth_utils/decorators.py +++ b/eth_utils/decorators.py @@ -9,10 +9,13 @@ TypeVar, ) +from typing_extensions import ParamSpec + from .types import ( is_text, ) +P = ParamSpec("P") T = TypeVar("T") @@ -68,7 +71,7 @@ def _validate_supported_kwarg(kwargs: Any) -> None: ) -def validate_conversion_arguments(to_wrap: Callable[..., T]) -> Callable[..., T]: +def validate_conversion_arguments(to_wrap: Callable[P, T]) -> Callable[P, T]: """ Validates arguments for conversion functions. - Only a single argument is present @@ -77,7 +80,7 @@ def validate_conversion_arguments(to_wrap: Callable[..., T]) -> Callable[..., T] """ @functools.wraps(to_wrap) - def wrapper(*args: Any, **kwargs: Any) -> T: + def wrapper(*args: P.args, **kwargs: P.kwargs) -> T: _assert_one_val(*args, **kwargs) if kwargs: _validate_supported_kwarg(kwargs) @@ -108,15 +111,15 @@ def wrapper(*args: Any, **kwargs: Any) -> T: # type: ignore def replace_exceptions( old_to_new_exceptions: Dict[Type[BaseException], Type[BaseException]] -) -> Callable[[Callable[..., T]], Callable[..., T]]: +) -> Callable[[Callable[P, T]], Callable[P, T]]: """ Replaces old exceptions with new exceptions to be raised in their place. """ old_exceptions = tuple(old_to_new_exceptions.keys()) - def decorator(to_wrap: Callable[..., T]) -> Callable[..., T]: + def decorator(to_wrap: Callable[P, T]) -> Callable[P, T]: @functools.wraps(to_wrap) - def wrapped(*args: Any, **kwargs: Any) -> T: + def wrapped(*args: P.args, **kwargs: P.kwargs) -> T: try: return to_wrap(*args, **kwargs) except old_exceptions as err: From 0f8aec8f8454cb8b2bc0d4cf12738d9b7ff57ce9 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Wed, 30 Apr 2025 18:51:30 -0400 Subject: [PATCH 2/3] Update decorators.py --- eth_utils/decorators.py | 173 ++++++++++++++++++++++++++-------------- 1 file changed, 114 insertions(+), 59 deletions(-) diff --git a/eth_utils/decorators.py b/eth_utils/decorators.py index 0988b8b4..f523d934 100644 --- a/eth_utils/decorators.py +++ b/eth_utils/decorators.py @@ -9,13 +9,11 @@ TypeVar, ) -from typing_extensions import ParamSpec - from .types import ( is_text, ) -P = ParamSpec("P") + T = TypeVar("T") @@ -36,6 +34,23 @@ def _wrapper(*args: Any, **kwargs: Any) -> Any: return _wrapper +def return_arg_type(at_position: int) -> Callable[..., Callable[..., T]]: + """ + Wrap the return value with the result of `type(args[at_position])`. + """ + + def decorator(to_wrap: Callable[..., Any]) -> Callable[..., T]: + @functools.wraps(to_wrap) + def wrapper(*args: Any, **kwargs: Any) -> T: # type: ignore + result = to_wrap(*args, **kwargs) + ReturnType = type(args[at_position]) + return ReturnType(result) # type: ignore + + return wrapper + + return decorator + + def _has_one_val(*args: T, **kwargs: T) -> bool: vals = itertools.chain(args, kwargs.values()) not_nones = list(filter(lambda val: val is not None, vals)) @@ -71,65 +86,105 @@ def _validate_supported_kwarg(kwargs: Any) -> None: ) -def validate_conversion_arguments(to_wrap: Callable[P, T]) -> Callable[P, T]: - """ - Validates arguments for conversion functions. - - Only a single argument is present - - Kwarg must be 'primitive' 'hexstr' or 'text' - - If it is 'hexstr' or 'text' that it is a text type - """ - - @functools.wraps(to_wrap) - def wrapper(*args: P.args, **kwargs: P.kwargs) -> T: - _assert_one_val(*args, **kwargs) - if kwargs: - _validate_supported_kwarg(kwargs) - - if len(args) == 0 and "primitive" not in kwargs: - _assert_hexstr_or_text_kwarg_is_text_type(**kwargs) - return to_wrap(*args, **kwargs) - - return wrapper +try: # If you're using a recent enough version of python, we can enhance decorator typing with ParamSpec + from typing_extensions import ParamSpec + + P = ParamSpec("P") -def return_arg_type(at_position: int) -> Callable[..., Callable[..., T]]: - """ - Wrap the return value with the result of `type(args[at_position])`. - """ - - def decorator(to_wrap: Callable[..., Any]) -> Callable[..., T]: + + def validate_conversion_arguments(to_wrap: Callable[P, T]) -> Callable[P, T]: + """ + Validates arguments for conversion functions. + - Only a single argument is present + - Kwarg must be 'primitive' 'hexstr' or 'text' + - If it is 'hexstr' or 'text' that it is a text type + """ + @functools.wraps(to_wrap) - def wrapper(*args: Any, **kwargs: Any) -> T: # type: ignore - result = to_wrap(*args, **kwargs) - ReturnType = type(args[at_position]) - return ReturnType(result) # type: ignore - + def wrapper(*args: P.args, **kwargs: P.kwargs) -> T: + _assert_one_val(*args, **kwargs) + if kwargs: + _validate_supported_kwarg(kwargs) + + if len(args) == 0 and "primitive" not in kwargs: + _assert_hexstr_or_text_kwarg_is_text_type(**kwargs) + return to_wrap(*args, **kwargs) + return wrapper - - return decorator - - -def replace_exceptions( - old_to_new_exceptions: Dict[Type[BaseException], Type[BaseException]] -) -> Callable[[Callable[P, T]], Callable[P, T]]: - """ - Replaces old exceptions with new exceptions to be raised in their place. - """ - old_exceptions = tuple(old_to_new_exceptions.keys()) - - def decorator(to_wrap: Callable[P, T]) -> Callable[P, T]: + + + def replace_exceptions( + old_to_new_exceptions: Dict[Type[BaseException], Type[BaseException]] + ) -> Callable[[Callable[P, T]], Callable[P, T]]: + """ + Replaces old exceptions with new exceptions to be raised in their place. + """ + old_exceptions = tuple(old_to_new_exceptions.keys()) + + def decorator(to_wrap: Callable[P, T]) -> Callable[P, T]: + @functools.wraps(to_wrap) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> T: + try: + return to_wrap(*args, **kwargs) + except old_exceptions as err: + try: + raise old_to_new_exceptions[type(err)](err) from err + except KeyError: + raise TypeError( + f"could not look up new exception to use for {repr(err)}" + ) from err + + return wrapped + + return decorator + +except ImportError: # Your python version is too low to use ParamSpec + + + def validate_conversion_arguments(to_wrap: Callable[..., T]) -> Callable[..., T]: + """ + Validates arguments for conversion functions. + - Only a single argument is present + - Kwarg must be 'primitive' 'hexstr' or 'text' + - If it is 'hexstr' or 'text' that it is a text type + """ + @functools.wraps(to_wrap) - def wrapped(*args: P.args, **kwargs: P.kwargs) -> T: - try: - return to_wrap(*args, **kwargs) - except old_exceptions as err: + def wrapper(*args: Any, **kwargs: Any) -> T: + _assert_one_val(*args, **kwargs) + if kwargs: + _validate_supported_kwarg(kwargs) + + if len(args) == 0 and "primitive" not in kwargs: + _assert_hexstr_or_text_kwarg_is_text_type(**kwargs) + return to_wrap(*args, **kwargs) + + return wrapper + + + def replace_exceptions( + old_to_new_exceptions: Dict[Type[BaseException], Type[BaseException]] + ) -> Callable[[Callable[..., T]], Callable[..., T]]: + """ + Replaces old exceptions with new exceptions to be raised in their place. + """ + old_exceptions = tuple(old_to_new_exceptions.keys()) + + def decorator(to_wrap: Callable[..., T]) -> Callable[..., T]: + @functools.wraps(to_wrap) + def wrapped(*args: Any, **kwargs: Any) -> T: try: - raise old_to_new_exceptions[type(err)](err) from err - except KeyError: - raise TypeError( - f"could not look up new exception to use for {repr(err)}" - ) from err - - return wrapped - - return decorator + return to_wrap(*args, **kwargs) + except old_exceptions as err: + try: + raise old_to_new_exceptions[type(err)](err) from err + except KeyError: + raise TypeError( + f"could not look up new exception to use for {repr(err)}" + ) from err + + return wrapped + + return decorator + From e4e96249e196866a7683f348fe37ca38b66e8240 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Wed, 30 Apr 2025 18:51:42 -0400 Subject: [PATCH 3/3] Update decorators.py --- eth_utils/decorators.py | 1 - 1 file changed, 1 deletion(-) diff --git a/eth_utils/decorators.py b/eth_utils/decorators.py index f523d934..94988399 100644 --- a/eth_utils/decorators.py +++ b/eth_utils/decorators.py @@ -13,7 +13,6 @@ is_text, ) - T = TypeVar("T")