We feature several helper functions to make your developer experience better.
from returns.result import Success, Failure from returns.functions import is_successful is_successful(Success(1)) # => True is_successful(Failure('text')) # => False
What is a
It is a more user-friendly syntax to work with containers.
Consider this task. We were asked to create a method that will connect together a simple pipeline of three steps:
We validate passed
We create a new
Account with this data, if it does not exists
We create a new
User associated with the
And we know that this pipeline can fail in several places:
Account with this
User creation might fail as well,
since it also makes an
HTTP request to another micro-service deep inside
Here’s the code to illustrate the task.
from returns.functions import pipeline from returns.result import Result, Success, Failure class CreateAccountAndUser(object): """Creates new Account-User pair.""" # TODO: we need to create a pipeline of these methods somehow... # Protected methods def _validate_user( self, username: str, email: str, ) -> Result['UserSchema', str]: """Returns an UserSchema for valid input, otherwise a Failure.""" def _create_account( self, user_schema: 'UserSchema', ) -> Result['Account', str]: """Creates an Account for valid UserSchema's. Or returns a Failure.""" def _create_user( self, account: 'Account', ) -> Result['User', str]: """Create an User instance. If user already exists returns Failure."""
We can implement this feature using a traditional
class CreateAccountAndUser(object): """Creates new Account-User pair.""" def __call__(self, username: str, email: str) -> Result['User', str]: """Can return a Success(user) or Failure(str_reason).""" return self._validate_user(username, email).bind( self._create_account, ).bind( self._create_user, ) # Protected methods # ...
And this will work without any problems. But, is it easy to read a code like this? No, it is not.
What alternative we can provide?
And here’s how we can refactor previous version to be more clear.
class CreateAccountAndUser(object): """Creates new Account-User pair.""" @pipeline def __call__(self, username: str, email: str) -> Result['User', str]: """Can return a Success(user) or Failure(str_reason).""" user_schema = self._validate_user(username, email).unwrap() account = self._create_account(user_schema).unwrap() return self._create_user(account) # Protected methods # ...
Let’s see how this new
.unwrap() method works:
if you result is
Success it will return its inner value
if your result is
Failure it will raise a
And that’s where
@pipeline decorator becomes in handy.
It will catch any
UnwrapFailedError during the pipeline
and then return a simple
See, do notation allows you to write simple yet powerful pipelines with multiple and complex steps. And at the same time the produced code is simple and readable.
And that’s it!
from returns.functions import safe @safe def divide(number: int) -> float: return number / number divide(1) # => Success(1.0) divide(0) # => Failure(ZeroDivisionError)
There’s one limitation in typing that we are facing right now due to mypy issue:
from returns.functions import safe @safe def function(param: int) -> int: return param reveal_type(function) # Actual => def (*Any, **Any) -> builtins.int # Expected => def (int) -> builtins.int
This effect can be reduced with the help of Design by Contract with these implementations:
We also ship an utility function to compose two different functions together.
from returns.functions import compose bool_after_int = compose(int, bool) bool_after_int('1') # => True bool_after_int('0') # => False
Composition is also type-safe. The only limitation is that we only support functions with one argument and one return to be composed.
Determins if a container was successful or not.
We treat container that raise
Decorator to covert exception throwing function to ‘Result’ monad.
Show be used with care, since it only catches ‘Exception’ subclasses. It does not catch ‘BaseException’ subclasses.
Decorator to enable ‘do-notation’ context.
Should be used for series of computations that rely on
Allows function composition.
Works as: second . first You can read it as “second after first”.
We can only compose functions with one argument and one return.