Result

Result is obviously a result of some series of computations. It might succeed with some resulting value. Or it might return an error with some extra details.

Result consist of two types: Success and Failure. Success represents successful operation result and Failure indicates that something has failed.

from returns.result import Result, Success, Failure

def find_user(user_id: int) -> Result['User', str]:
    user = User.objects.filter(id=user_id)
    if user.exists():
        return Success(user[0])
    return Failure('User was not found')

user_search_result = find_user(1)
# => Success(User{id: 1, ...})

user_search_result = find_user(0)  # id 0 does not exist!
# => Failure('User was not found')

When is it useful? When you do not want to use exceptions to break your execution scope. Or when you do not want to use None to represent empty values, since it will raise TypeError somewhere and other None exception-friends.

is_successful

is_succesful is used to tell whether or not your result is a success. We treat only treat types that does not throw as a successful ones, basically: Success.

from returns.result import Success, Failure, is_successful

is_successful(Success(1))
# => True

is_successful(Failure('text'))
# => False

pipeline

What is a pipeline? It is a more user-friendly syntax to work with containers that support both async and regular functions.

Consider this task. We were asked to create a method that will connect together a simple pipeline of three steps:

  1. We validate passed username and email

  2. We create a new Account with this data, if it does not exists

  3. We create a new User associated with the Account

And we know that this pipeline can fail in several places:

  1. Wrong username or email might be passed, so the validation will fail

  2. Account with this username or email might already exist

  3. 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.result import Result, Success, Failure, pipeline


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."""

Using bind technique

We can implement this feature using a traditional bind method.

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? @pipeline!

Using pipeline

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 UnwrapFailedError

And that’s where @pipeline decorator becomes in handy. It will catch any UnwrapFailedError during the pipeline and then return a simple Failure result.

sequenceDiagram participant pipeline participant validation participant account creation participant user creation pipeline->>validation: runs the first step validation-->>pipeline: returns Failure(validation message) if fails validation->>account creation: passes Success(UserSchema) if valid account creation-->>pipeline: return Failure(account exists) if fails account creation->>user creation: passes Success(Account) if valid user creation-->>pipeline: returns Failure(http status) if fails user creation-->>pipeline: returns Success(user) if user is created

Pipeline execution.

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!

safe

safe is used to convert regular functions that can throw exceptions to functions that return Result type.

Supports both async and regular functions.

from returns.result import safe

@safe
def divide(number: int) -> float:
    return number / number

divide(1)
# => Success(1.0)

divide(0)
# => Failure(ZeroDivisionError)

Limitations

There’s one limitation in typing that we are facing right now due to mypy issue:

from returns.result 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:

API Reference

graph TD; Success Result Failure GenericContainerTwoSlots --> Result Result --> Failure Result --> Success
class Result(inner_value)[source]

Bases: returns.primitives.container.GenericContainerTwoSlots

Base class for Failure and Success.

abstract fix(function)[source]

Applies ‘function’ to the contents of the functor.

And returns a new functor value. Works for containers that represent failure. Is the opposite of map().

abstract rescue(function)[source]

Applies ‘function’ to the result of a previous calculation.

And returns a new container. Works for containers that represent failure. Is the opposite of bind().

abstract value_or(default_value)[source]

Forces to unwrap value from container or return a default.

abstract unwrap()[source]

Custom magic method to unwrap inner value from container.

Should be redefined for ones that actually have values. And for ones that raise an exception for no values.

This method is the opposite of failure().

abstract failure()[source]

Custom magic method to unwrap inner value from the failed container.

This method is the opposite of unwrap().

class Failure(inner_value)[source]

Bases: returns.result.Result

Represents a calculation which has failed.

It should contain an error code or message. To help with readability you may alternatively use the alias ‘Failure’.

map(function)[source]

Returns the ‘Failure’ instance that was used to call the method.

bind(function)[source]

Returns the ‘Failure’ instance that was used to call the method.

fix(function)[source]

Applies function to the inner value.

Applies ‘function’ to the contents of the ‘Success’ instance and returns a new ‘Success’ object containing the result. ‘function’ should accept a single “normal” (non-container) argument and return a non-container result.

rescue(function)[source]

Applies ‘function’ to the result of a previous calculation.

‘function’ should accept a single “normal” (non-container) argument and return Result a ‘Failure’ or ‘Success’ type object.

value_or(default_value)[source]

Returns the value if we deal with ‘Success’ or default otherwise.

unwrap()[source]

Raises an exception, since it does not have a value inside.

failure()[source]

Unwraps inner error value from failed container.

class Success(inner_value)[source]

Bases: returns.result.Result

Represents a calculation which has succeeded and contains the result.

To help with readability you may alternatively use the alias ‘Success’.

map(function)[source]

Applies function to the inner value.

Applies ‘function’ to the contents of the ‘Success’ instance and returns a new ‘Success’ object containing the result. ‘function’ should accept a single “normal” (non-container) argument and return a non-container result.

bind(function)[source]

Applies ‘function’ to the result of a previous calculation.

‘function’ should accept a single “normal” (non-container) argument and return Result a ‘Failure’ or ‘Success’ type object.

fix(function)[source]

Returns the ‘Success’ instance that was used to call the method.

rescue(function)[source]

Returns the ‘Success’ instance that was used to call the method.

value_or(default_value)[source]

Returns the value if we deal with ‘Success’ or default otherwise.

unwrap()[source]

Returns the unwrapped value from the inside of this container.

failure()[source]

Raises an exception, since it does not have an error inside.

is_successful(container)[source]

Determins if a container was successful or not.

We treat container that raise UnwrapFailedError on .unwrap() not successful.

safe(function)[source]

Decorator to covert exception throwing function to ‘Result’ container.

Show be used with care, since it only catches ‘Exception’ subclasses. It does not catch ‘BaseException’ subclasses.

Supports both async and regular functions.

pipeline(function)[source]

Decorator to enable ‘do-notation’ context.

Should be used for series of computations that rely on .unwrap method.

Supports both async and regular functions.