Decorators
Use decorators to protect your results and prevent from unexpected exceptions. They always return a Result
object.
to_result
¶
Use @to_result
to wrap a function transforming returned value in a Success
and raised Exceptions in a Failure
.
from meiga import to_result, Result, Error
class NoSuchKey(Exception): ...
class TypeMismatch(Exception): ...
@to_result
def string_from_key(dictionary: dict, key: str) -> str:
if key not in dictionary.keys():
raise NoSuchKey()
value = dictionary[key]
if not isinstance(value, str):
raise TypeMismatch()
return value
dictionary = {"key1": "value", "key2": 2}
key = "key1"
result: Result[str, Error] = string_from_key(dictionary=dictionary, key=key)
early_return
¶
Use @early_return
decoration in combination with unwrap_or_return()
.
The unwrap_or_return
will unwrap the value of the Result
monad only if it is a success.
Otherwise, this will return a Failure (Result with a failure) when using @early_return
decorator.
from meiga import early_return, BoolResult, isSuccess
@early_return
def update_user(user_id: UserId, new_name: str) -> BoolResult:
user = repository.retrieve(user_id).unwrap_or_return()
user.update_name(new_name)
repository.save(user).unwrap_or_return()
event_bus.publish(user.pull_domain_events()).unwrap_or_return()
return isSuccess
Given a user repository with a method retrieve which returns a typed Result[User, UserNotFoundError]
, when we unwrap_or_return
, we will unwrap the value of returned Result in case of Success.
On the other side, when retrieve function with not valid UserId
, the repository automatically returns a result failure interrupting the execution of the following lines of code.
Note
This is possible because the unwrap_or_return
function will raise an specific exception (OnFailureException
) if the result is a failure an cannot be wrapped:
def unwrap_or_return(self, return_value_on_failure: Any = None) -> TS:
if not self._is_success:
return_value = (
self if return_value_on_failure is None else return_value_on_failure
)
raise OnFailureException(return_value)
return cast(TS, self.value)
And @early_return
decorator catches the exception and coverts it to a Result
:
P = ParamSpec("P")
R = TypeVar("R", bound=Result)
def early_return(func: Callable[P, R]) -> Callable[P, R]:
@wraps(func)
def _early_return(*args: P.args, **kwargs: P.kwargs) -> R:
try:
if isinstance(func, staticmethod):
return Failure(UnexpectedDecorationOrderError())
elif isinstance(func, classmethod):
return Failure(UnexpectedDecorationOrderError())
else:
return func(*args, **kwargs)
except OnFailureException as exc:
return exc.result
except Error as error:
return cast(R, Failure(error))
return _early_return
Warning
When decorate staticmethod
and classmethod
check the order, otherwise it will raise an error (UnexpectedDecorationOrderError) as these kinds of methods are not callable.
async_early_return
¶
Use @async_early_return
decoration in combination with unwrap_or_return()
when using async functions.
from meiga import async_early_return, BoolResult, isSuccess
@async_early_return
async def update_user(user_id: UserId, new_name: str) -> BoolResult:
user = (await repository.retrieve(user_id)).unwrap_or_return()
user.update_name(new_name)
(await repository.save(user)).unwrap_or_return()
(await event_bus.publish(user.pull_domain_events())).unwrap_or_return()
return isSuccess