Container is a concept that allows you to write code around the existing wrapped values while maintaining the execution context.
List of supported containers:
We will show you container’s simple API of one attribute and several simple methods.
The main idea behind a container is that it wraps some internal state.
That’s what
._inner_value
is used for.
And we have several functions to create new containers based on the previous state. And we can see how this state is evolving during the execution.
We use two methods to create a new container from the previous one.
bind
and map
.
The difference is simple:
map
works with functions that return regular value
bind
works with functions that return new container of the same type
bind()
is used to literally bind two different containers together.
from returns.result import Result, Success
def may_fail(user_id: int) -> Result[float, str]:
...
value: Result[int, str] = Success(1)
# Can be assumed as either Success[float] or Failure[str]:
result: Result[float, str] = value.bind(may_fail)
And we have map()
to use containers with regular functions.
from typing import Any
from returns.result import Success, Result
def double(state: int) -> int:
return state * 2
result: Result[int, Any] = Success(1).map(double)
# => Success(2)
result: Result[int, Any] = result.map(lambda state: state + 1)
# => Success(3)
The same work with built-in functions as well:
from returns.io import IO
IO('bytes').map(list)
# => <IO: ['b', 'y', 't', 'e', 's']>
Note:
All containers support these methods.
When talking about error handling we use a concept of Railway oriented programming. It mean that our code can go on two tracks:
Successful one: where everything goes perfectly: HTTP requests work, database is always serving us data, parsing values does not failed
Failed one: where something went wrong
We can switch from track to track: we can fail something or we can fix the situation.
We also support two special methods to work with “failed”
types like Failure
:
rescue()
is the opposite of bind
method
that works only when container is in failed state
fix()
transforms error to value (failure became success)
that works only when container is in failed state,
is the opposite of map
method
alt()
transforms error to another error
that works only when container is in failed state,
is the opposite of map
method
fix
can be used to fix some fixable errors
during the pipeline execution:
from returns.result import Failure, Result
def double(state: int) -> float:
return state * 2.0
result: Result[Any, float] = Failure(1).alt(double)
# => Failure(2.0)
result: Result[float, int] = Failure(1).fix(double)
# => Success(2.0)
rescue
should return one of Success
or Failure
types.
It can also rescue your flow and get on the successful track again:
from returns.result import Result, Failure, Success
def tolerate_exception(state: Exception) -> Result[int, Exception]:
if isinstance(state, ZeroDivisionError):
return Success(0)
return Failure(state)
value: Result[int, Exception] = Failure(ZeroDivisionError())
result: Result[int, Exception] = value.rescue(tolerate_exception)
# => Success(0)
value2: Result[int, Exception] = Failure(ValueError())
result2: Result[int, Exception] = value2.rescue(tolerate_exception)
# => Failure(ValueError())
Note:
Not all containers support these methods.
IO cannot be fixed or rescued.
And we have two more functions to unwrap inner state of containers into a regular types:
.value_or
returns a value if it is possible, returns default_value
otherwise
.unwrap
returns a value if it is possible, raises UnwrapFailedError
otherwise
from returns.result import Failure, Success
from returns.maybe import Some, Nothing
Success(1).value_or(None)
# => 1
Some(0).unwrap()
# => 0
Failure(1).value_or(default_value=100)
# => 100
Failure(1).unwrap()
# => Traceback (most recent call last): UnwrapFailedError
Nothing.unwrap()
# => Traceback (most recent call last): UnwrapFailedError
The most user-friendly way to use .unwrap()
method is with pipeline.
We even discourage using .unwrap()
without a @pipeline
.
For failing containers you can
use .failure
to unwrap the failed state:
Failure(1).failure()
# => 1
Success(1).failure()
# => Traceback (most recent call last): UnwrapFailedError
Be careful, since this method will raise an exception
when you try to .failure()
a successful container.
Note:
Not all containers support these methods.
IO cannot be unwrapped.
We like to think of returns
as immutable structures.
You cannot mutate the inner state of the created container,
because we redefine __setattr__
and __delattr__
magic methods.
You cannot also set new attributes to container instances,
since we are using __slots__
for better performance and strictness.
Well, nothing is really immutable in python, but you were warned.
We try to make our containers optionally type safe.
What does it mean?
It is still good old python
, do whatever you want without mypy
If you are using mypy
you will be notified about type violations
We also ship PEP561
compatible .pyi
files together with the source code.
In this case these types will be available to users
when they install our application.
We also ship custom mypy
plugins to overcome some existing problems,
please make sure to use them,
since they increase your developer experience and type-safety:
decorator_plugin
to solve untyped decorator issue
[mypy]
plugins =
returns.contrib.mypy.decorator_plugin
You can have a look at the suggested mypy
configuration
in our own repository.
You can and should compose different containers together. Here’s the full table of compositions that make sense:
IO[Result[A, B]]
✅
IO[Maybe[A]]
✅
IO[IO[A]]
🤔, use join
Maybe[Maybe[A]]
🤔, use join
Result[Result[A, B], C]
🤔, use join
Result[Maybe[A], B]
🤔,use maybe_to_result
Maybe[Result[A, B]]
🤔,use result_to_maybe
Result[IO[A], B]
🚫
Result[A, IO[A]]
🚫
Result[A, Maybe[B]]
🚫
Result[A, Result[B, C]]
🚫
Maybe[IO[A]]
🚫
You can use Converters to convert Maybe
and Result
containers.
So, you don’t have to compose them.
You can also use join
to merge nested containers.
We have several helper functions
to convert containers from Maybe
to Result
and back again:
maybe_to_result
that converts Maybe
to Result
result_to_maybe
that converts Result
to Maybe
That’s how they work:
from returns.converters import maybe_to_result, result_to_maybe
from returns.maybe import Maybe
from returns.result import Result
result: Result[int, Exception]
maybe: Maybe[int] = result_to_maybe(result)
new_result: Result[int, None] = maybe_to_result(maybe)
Take a note, that type changes.
Also, take a note that Success(None)
will be converted to Nothing
.
You can also use join
to merge nested containers together:
from returns.converters import join
from returns.maybe import Maybe
from returns.result import Success
from returns.io import IO
assert join(IO(IO(1))) == IO(1)
assert join(Maybe(Maybe(1))) == Maybe(1)
assert join(Success(Success(1))) == Success(1)
You can use returns.converters.coalesce_result()
and returns.converters.coalesce_maybe()
converters
to covert containers to a regular value.
These functions accept two functions: one for successful case, one for failing case.
from returns.converters import coalesce_result
from returns.result import Success, Failure
def handle_success(state: int) -> float:
return state / 2
def handle_failure(state: str) -> float:
return 0.0
coalesce_result(handle_success, handle_failure)(Success(1))
# => returns `0.5`
coalesce_result(handle_success, handle_failure)(Failure(1))
# => returns `0.0`
BaseContainer
(inner_value)[source]¶Bases: object
Utility class to provide all needed magic methods to the context.
Bindable
(*args, **kwargs)[source]¶Bases: typing_extensions.Protocol
Represents a “context” in which calculations can be executed.
Bindable
allows you to bind together
a series of calculations while maintaining
the context of that specific container.
bind
(function)[source]¶Applies ‘function’ to the result of a previous calculation.
And returns a new container.
Works for containers that represent success.
Is the opposite of Rescueable.rescue()
.
Bindable
[~_NewValueType]
Mappable
(*args, **kwargs)[source]¶Bases: typing_extensions.Protocol
Allows to chain wrapped values with regular functions.
Behaves like functor.
map
(function)[source]¶Applies ‘function’ to the contents of the functor.
And returns a new functor value.
Is the opposite of Fixable.fix()
.
Mappable
[~_NewValueType]
Fixable
(*args, **kwargs)[source]¶Bases: typing_extensions.Protocol
Represents containers that can be fixed and rescued.
fix
(function)[source]¶Applies ‘function’ to the error and transforms failure to success.
And returns a new functor value.
Works for containers that represent failure.
Is the opposite of Mappable.map()
.
Fixable
[~_NewValueType, +_ErrorType]
Rescueable
(*args, **kwargs)[source]¶Bases: typing_extensions.Protocol
Represents a “context” in which calculations can be executed.
Rescueable
allows you to bind together
a series of calculations while maintaining
the context of that specific container.
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()
.
Rescueable
[~_NewValueType, ~_NewErrorType]
Unwrapable
(*args, **kwargs)[source]¶Bases: typing_extensions.Protocol
Represents containers that can unwrap and return its wrapped value.
value_or
(default_value)[source]¶Forces to unwrap value from container or return a default.
Union
[+_ValueType, ~_NewValueType]
UnwrapableFailure
(*args, **kwargs)[source]¶Bases: typing_extensions.Protocol
Allows to unwrap failures.
result_to_maybe
(result_container)[source]¶Converts Result
container to Maybe
container.
>>> from returns.maybe import Some, Nothing
>>> from returns.result import Failure, Success
>>> result_to_maybe(Success(1)) == Some(1)
True
>>> result_to_maybe(Failure(1)) == Nothing
True
>>> result_to_maybe(Success(None)) == Nothing
True
Maybe
[~_ValueType]
maybe_to_result
(maybe_container)[source]¶Converts Maybe
container to Result
container.
>>> from returns.maybe import Some, Nothing
>>> from returns.result import Failure, Success
>>> maybe_to_result(Nothing) == Failure(None)
True
>>> maybe_to_result(Some(1)) == Success(1)
True
>>> maybe_to_result(Some(None)) == Failure(None)
True
Result
[~_ValueType]
join
(container)[source]¶Joins two nested containers together.
>>> from returns.maybe import Some
>>> from returns.result import Success
>>> from returns.io import IO
>>> join(IO(IO(1))) == IO(1)
True
>>> join(Some(Some(1))) == Some(1)
True
>>> join(Success(Success(1))) == Success(1)
True
coalesce_result
(success_handler, failure_handler)¶Accepts two functions that handle different cases of containers.
First one handles successful containers like Some
and Success
,
and second one for failed containers like Nothing
and Failure
.
This function is useful when you need to coalesce two possible container states into one type.
coalesce_maybe
(success_handler, failure_handler)¶Accepts two functions that handle different cases of containers.
First one handles successful containers like Some
and Success
,
and second one for failed containers like Nothing
and Failure
.
This function is useful when you need to coalesce two possible container states into one type.