-
-
Notifications
You must be signed in to change notification settings - Fork 119
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make @safe(exceptions=(TypeError, ValueError))
variant
#605
Comments
This also applies to |
Hey @sobolevn, I'm working on this issue but I got a problem with typesafety tests :( Here's the implementation: @overload
def safe(
arg: Callable[..., _ValueType],
) -> Callable[..., ResultE[_ValueType]]:
"""Decorator to convert exception-throwing for any kind of Exception."""
@overload
def safe(
arg: Tuple[Type[Exception], ...],
) -> Callable[
[Callable[..., _ValueType]],
Callable[..., ResultE[_ValueType]],
]:
"""Decorator to convert exception-throwing just for a set of Exceptions."""
def safe( # noqa: WPS234
arg: Union[
Tuple[Type[Exception], ...],
Callable[..., _ValueType],
],
) -> Union[
Callable[
[Callable[..., _ValueType]],
Callable[..., ResultE[_ValueType]],
],
Callable[..., ResultE[_ValueType]],
]:
...
It should work by the fact we have the Do you have any idea of why it's not working? |
I think that the reason is https://github.com/dry-python/returns/blob/master/returns/contrib/mypy/_features/decorators.py#L26-L27 Try to catch |
Just a question, should it work if we don't use our plugin?? |
It would work partially (except argument types). Because we don't have |
@sobolevn You were right, the problem is in that statement! I'll try to fix it! |
It's not treating tuples correctly 😢 @safe(args=(ValueError, EOFError))
def test() -> int:
return 1
|
And that error isn't our fault (I guess), 'cause it is not executing our decorator plugin function! |
Oh, my fault! I've used wrong parameter name |
Ok, now I think I have the same feelings you have about The problem is when decorator has parameters, like we trying to do here, see the example below: def decorator_argument(arg: int) -> Callable[[Callable[..., int]], Callable[..., int]]:
def decorator(function: Callable[..., int]) -> Callable[..., int]:
@wraps(function)
def decorated(*args, **kwargs):
pass
return decorated
return decorator Test case: @decorator_argument(42)
def test() -> int:
return 1
reveal_type(test) # N: Revealed type is 'def () -> builtins.int' Output:
|
Yeah, that's a known problem. We had solved it already in the previous versions. Take a look at our definitions: https://github.com/dry-python/returns/blob/0.14.0/returns/_generated/pointfree/map.pyi#L22 I hope, it helps. |
I'm thinking, it is worth it now? I'm saying this because I can implement this and maintain the backward compatibility. The guys who use In about 4 months we'll have |
Let's wait for it, I guess! |
No prob! |
Waiting for #823 |
Could you use a helper function, to composition, and then employ that as the base for the decorator with a bit or currying? ie from functools import wraps
from typing import Any, Callable, Tuple, Type, TypeVar, Union, overload
from returns.result import Result, Success, Failure
_ValueType = TypeVar("_ValueType")
_ErrorType = TypeVar("_ErrorType")
_Callee = Callable[..., _ValueType]
_Caller = Callable[..., Result[_ValueType, _ErrorType]]
# Placeholder Errors
E1 = TypeVar("E1", bound=BaseException)
E2 = TypeVar("E2", bound=BaseException)
E3 = TypeVar("E3", bound=BaseException)
E4 = TypeVar("E4", bound=BaseException)
E5 = TypeVar("E5", bound=BaseException)
@overload
def catch(
error: Type[E1],
function: _Callee[_ValueType],
) -> _Caller[_ValueType, E1]:
...
@overload
def catch(
error: Tuple[Type[E1]],
function: _Callee[_ValueType],
) -> _Caller[_ValueType, E1]:
...
@overload
def catch(
error: Tuple[Type[E1], Type[E2]],
function: _Callee[_ValueType],
) -> _Caller[_ValueType, Union[E1, E2]]:
...
@overload
def catch(
error: Tuple[Type[E1], Type[E2], Type[E3]],
function: _Callee[_ValueType],
) -> _Caller[_ValueType, Union[E1, E2, E3]]:
...
@overload
def catch(
error: Tuple[Type[E1], Type[E2], Type[E3], Type[E4]],
function: _Callee[_ValueType],
) -> _Caller[_ValueType, Union[E1, E2, E3, E4]]:
...
@overload
def catch(
error: Tuple[Type[E1], Type[E2], Type[E3], Type[E4], Type[E5]],
function: _Callee[_ValueType],
) -> _Caller[_ValueType, Union[E1, E2, E3, E4, E5]]:
...
@overload
def catch(
error: Tuple[Type[E1], ...],
function: _Callee[_ValueType],
) -> _Caller[_ValueType, E1]:
...
def catch(error: Any, function: Callable) -> Callable:
@wraps(function)
def decorator(*args, **kwargs) -> Result:
try:
return Success(function(*args, **kwargs))
except error as exc:
return Failure(exc)
return decorator |
Or use an intermediate object to carry the typed parameters. Unsure if this helps, or I'm just missing the point. Sorry if the latter. :) from typing import (
Callable,
Generic,
NoReturn as Unused,
Tuple,
Type,
TypeVar,
Union,
overload,
)
Y = TypeVar("Y")
N = TypeVar("N")
class Result(Generic[Y, N]):
def __init__(self, value):
...
R = TypeVar("R")
E1 = TypeVar("E1", bound=BaseException)
E2 = TypeVar("E2", bound=BaseException)
E3 = TypeVar("E3", bound=BaseException)
E4 = TypeVar("E4", bound=BaseException)
class Decorator(Generic[E1, E2, E3, E4]):
def __init__(self, error: Tuple[Type[E1], Type[E2], Type[E3], Type[E4]]):
self._error = error
def __call__(
self, func: Callable[..., R]
) -> Callable[..., Result[R, Union[E1, E2, E3, E4]]]:
def wrapper(*args, **kwargs):
try:
return Result(self._func(*args, **kwargs))
except self._error as err:
return Result(err)
return wrapper
@overload
def safe(
error: Type[E1],
) -> Decorator[E1, Unused, Unused, Unused]:
...
@overload
def safe(
error: Tuple[Type[E1]],
) -> Decorator[E1, Unused, Unused, Unused]:
...
@overload
def safe(error: Tuple[Type[E1], Type[E2]]) -> Decorator[E1, E2, Unused, Unused]:
...
@overload
def safe(error: Tuple[Type[E1], Type[E2], Type[E3]]) -> Decorator[E1, E2, E3, Unused]:
...
@overload
def safe(
error: Tuple[Type[E1], Type[E2], Type[E3], Type[E4]]
) -> Decorator[E1, E2, E3, E4]:
...
@overload
def safe(func: Callable[..., R]) -> Callable[..., Result[R, Exception]]:
...
def safe(error):
if isinstance(error, BaseException):
return Decorator((error,))
elif isinstance(error, tuple):
return Decorator(error)
return Decorator((Exception,))(error)
@safe
def test1(num: int) -> str:
return str(num)
@safe(ValueError)
def test2(num: int) -> str:
return str(num)
@safe((ValueError, TypeError))
def test3(num: int) -> str:
return str(num)
reveal_type(test1(123)) # note: Revealed type is 'Result[builtins.str*, builtins.Exception]'
reveal_type(test2(123)) # note: Revealed type is 'Result[builtins.str*, builtins.ValueError]'
reveal_type(test3(123)) # note: Revealed type is 'Result[builtins.str*, Union[builtins.ValueError, builtins.TypeError]]' |
This decorator will help to catch only expected errors, not all.
The text was updated successfully, but these errors were encountered: