Container is a concept that allows you to write code around the existing wrapped values while maintaining the execution context.
List of supported containers:
Maybe
to handle None
cases
Result
to handle possible exceptions
IO
to mark explicit IO
actions
RequiresContext
to pass context to your functions (DI and similar)
There are also some combintations like
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)
>>> str(result)
'<Success: 2>'
>>> result: Result[int, Any] = result.map(lambda state: state + 1)
>>> str(result)
'<Success: 3>'
The same work with built-in functions as well:
>>> from returns.io import IO
>>> io = IO('bytes').map(list)
>>> str(io)
"<IO: ['b', 'y', 't', 'e', 's']>"
Note:
All containers support these methods.
You can read more about methods that some other containers support and interfaces behind them.
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 also provide returns.primitives.types.Immutable
mixin
that users can use to quickly make their classes immutable.
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 a table of some compositions that do not make sense:
IO[Result[A, B]]
🤔,
use returns.io.IOResult.from_typecast()
and IOResult
IO[Maybe[A]]
🤔,
use maybe_to_result
and then returns.io.IOResult.from_typecast()
to convert it to IOResult
IO[IO[A]]
🤔, use flatten
Maybe[Maybe[A]]
🤔, use flatten
Result[Result[A, B], C]
🤔,
use flatten
Result[Maybe[A], B]
🤔,
use maybe_to_result
and then flatten
Maybe[Result[A, B]]
🤔,
use result_to_maybe
and then flatten
RequiresContext[env, Result[A, B]]
🤔,
use RequiresContextResult.from_typecast
and RequiresResultContext
RequiresContext[env, RequiresContext[env, A]]
🤔,
use flatten
RequiresContextResult[env, RequiresContextResult[env, A, B], B]
🤔,
use flatten
RequiresContext[env, IOResult[A, B]]
🤔,
use RequiresContextIOResult.from_typecast
and RequiresResultContext
RequiresContextIOResult[env, RequiresContextIOResult[env, A, B], B]
🤔,
use flatten
Result[IO[A], B]
🚫
Result[A, IO[A]]
🚫
Result[A, Maybe[B]]
🚫
Result[A, Result[B, C]]
🚫
Maybe[IO[A]]
🚫
RequiresContext[IO[A], B]
🚫
IO[RequiresContext[A, B]
🚫
You can use Converters to convert Maybe
and Result
containers.
You can also use flatten
to merge nested containers.
BaseContainer
is a base class for all other container.
It defines some basic things like representation, hashing, pickling, etc.
BaseContainer
(inner_value)[source]¶Bases: returns.primitives.types.Immutable
Utility class to provide all needed magic methods to the context.
Here are our interfaces (or protocols to be more specific) that we use inside our app:
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.
Mappable
(*args, **kwargs)[source]¶Bases: typing_extensions.Protocol
Allows to chain wrapped values with regular functions.
Behaves like functor.
Fixable
(*args, **kwargs)[source]¶Bases: typing_extensions.Protocol
Represents containers that can be fixed and rescued.
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]
Altable
(*args, **kwargs)[source]¶Bases: typing_extensions.Protocol
Allows to unwrap failures.
Instanceable
(*args, **kwargs)[source]¶Bases: typing_extensions.Protocol
Allows to create unit containers from raw values.
This is heavily related to classes that do not have conunter-parts.
Like IO
and RequiresContext
.
Unitable
(*args, **kwargs)[source]¶Bases: typing_extensions.Protocol
Allows to create unit values from success and failure.
This is heavily Result
related class.