Result¶
Make sure to get familiar with Railway oriented programming.
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.
Composition¶
Make sure to check out how to compose container with
flow
or pipe!
Read more about them if you want to compose your containers easily.
Pattern Matching¶
Result
values can be matched using the new feature of Python 3.10,
Structural Pattern Matching,
see the example below:
from returns.result import Failure, Success, safe
@safe
def div(first_number: int, second_number: int) -> int: # noqa: FURB118
return first_number // second_number
match div(1, 0):
# Matches if the result stored inside `Success` is `10`
case Success(10):
print('Result is "10"')
# Matches any `Success` instance and binds its value to the `value` variable
case Success(value):
print(f'Result is "{value}"')
# Matches if the result stored inside `Failure` is `ZeroDivisionError`
case Failure(ZeroDivisionError()):
print('"ZeroDivisionError" was raised')
# Matches any `Failure` instance
case Failure(_):
print('The division was a failure')
Aliases¶
There are several useful aliases for Result
type with some common values:
returns.result.ResultE
is an alias forResult[... Exception]
, just use it when you want to work withResult
containers that use exceptions as error type. It is namedResultE
because it isResultException
andResultError
at the same time.
Decorators¶
Limitations¶
Typing will only work correctly if our mypy plugin is used. This happens due to mypy issue.
safe¶
safe
is used to convert
regular functions that can throw exceptions to functions
that return Result
type.
Supports only regular functions.
If you need to mark async
functions as safe
,
use future_safe
instead.
>>> from returns.result import Success, safe
>>> @safe # Will convert type to: Callable[[int], Result[float, Exception]]
... def divide(number: int) -> float:
... return number / number
>>> assert divide(1) == Success(1.0)
>>> str(divide(0))
'<Failure: division by zero>'
If you want @safe
to handle only a set of exceptions:
>>> @safe(exceptions=(ZeroDivisionError,)) # Other exceptions will be raised
... def divide(number: int) -> float:
... if number > 10:
... raise ValueError('Too big')
... return number / number
>>> assert divide(5) == Success(1.0)
>>> assert divide(0).failure()
>>> divide(15)
Traceback (most recent call last):
...
ValueError: Too big
attempt¶
Similar to safe
function but instead
of wrapping the exception error in a Failure
container it’ll wrap the
argument that lead to that exception.
>>> from returns.result import Failure, Success, attempt
>>> @attempt
... def divide_itself(number: int) -> float:
... return number / number
>>> assert divide_itself(2) == Success(1.0)
>>> assert divide_itself(0) == Failure(0)
Warning
This decorator works only with functions that has just one argument.
FAQ¶
How to create unit objects?¶
Use Success
or Failure
.
Alternatively returns.result.Result.from_value()
or returns.result.Result.from_failure()
.
It might be a good idea to use unit functions together with the explicit annotation. Python’s type system does not allow us to do much, so this is required:
>>> from returns.result import Result, Success
>>> def callback(arg: int) -> Result[float, int]:
... return Success(float(arg))
>>> first: Result[int, int] = Success(1)
>>> assert first.bind(callback) == Success(1.0)
Otherwise first
will have Result[int, Any]
type.
Which is okay in some situations.
How to compose error types?¶
You might want to sometimes use unify
Pointfree functions
instead of .bind
to compose error types together.
While .bind
enforces error type to stay the same,
unify
is designed
to return a Union
of a previous error type and a new one.
It gives an extra flexibility, but also provokes more thinking and can be problematic in some cases.
Like so:
>>> from returns.result import Result, Success, Failure
>>> from returns.pointfree import unify
>>> def div(number: int) -> Result[float, ZeroDivisionError]:
... if number:
... return Success(1 / number)
... return Failure(ZeroDivisionError('division by zero'))
>>> container: Result[int, ValueError] = Success(1)
>>> assert unify(div)(container) == Success(1.0)
>>> # => Revealed type is:
>>> # Result[float, Union[ValueError, ZeroDivisionError]]
So, that’s a way to go, if you need this composition.
map vs bind¶
We use the map
method when we’re working with pure functions, a function
is pure if it doesn’t produce any side-effect (e.g. Exceptions). On the other
hand, we use the bind
method if a function returns a Result
instance
which translates its potential side-effect into a raw value.
See the example below:
>>> import json
>>> from typing import Dict
>>> from returns.result import Failure, Result, Success, safe
>>> # `cast_to_bool` doesn't produce any side-effect
>>> def cast_to_bool(arg: int) -> bool:
... return bool(arg)
>>> # `parse_json` can produce Exceptions, so we use the `safe` decorator
>>> # to prevent any kind of exceptions
>>> @safe
... def parse_json(arg: str) -> Dict[str, str]:
... return json.loads(arg)
>>> assert Success(1).map(cast_to_bool) == Success(True)
>>> assert Success('{"example": "example"}').bind(parse_json) == Success({"example": "example"})
>>> assert Success('').bind(parse_json).alt(str) == Failure('Expecting value: line 1 column 1 (char 0)')
How to check if your result is a success or failure?¶
Result
is a container and you can use returns.pipeline.is_successful()
like so:
>>> from returns.result import Success, Failure
>>> from returns.pipeline import is_successful
>>> assert is_successful(Success(1)) is True
>>> assert is_successful(Failure('text')) is False
Further reading¶
API Reference¶
classDiagram ABC <|-- Result BaseContainer <|-- Result Result <|-- Failure Result <|-- Success ResultBasedN <|-- Result SupportsKindN <|-- Result
- class Result(inner_value)[source]¶
Bases:
BaseContainer
,SupportsKindN
[Result
,_ValueType_co
,_ErrorType_co
,Never
],ResultBasedN
[_ValueType_co
,_ErrorType_co
,Never
],ABC
Base class for
Failure
andSuccess
.Result
does not have a public constructor. UseSuccess()
andFailure()
to construct the needed values.- equals(other)¶
Typesafe equality comparison with other Result objects.
- swap()[source]¶
Swaps value and error types.
So, values become errors and errors become values. It is useful when you have to work with errors a lot. And since we have a lot of
.bind_
related methods and only a single.lash
- it is easier to work with values.>>> from returns.result import Success, Failure >>> assert Success(1).swap() == Failure(1) >>> assert Failure(1).swap() == Success(1)
- Return type:
Result
[TypeVar
(_ErrorType_co
, covariant=True),TypeVar
(_ValueType_co
, covariant=True)]
- map(function)[source]¶
Composes successful container with a pure function.
>>> from returns.result import Failure, Success >>> def mappable(string: str) -> str: ... return string + 'b' >>> assert Success('a').map(mappable) == Success('ab') >>> assert Failure('a').map(mappable) == Failure('a')
- Parameters:
function (
Callable
[[TypeVar
(_ValueType_co
, covariant=True)],TypeVar
(_NewValueType
)])- Return type:
Result
[TypeVar
(_NewValueType
),TypeVar
(_ErrorType_co
, covariant=True)]
- apply(container)[source]¶
Calls a wrapped function in a container on this container.
>>> from returns.result import Failure, Success >>> def appliable(string: str) -> str: ... return string + 'b' >>> assert Success('a').apply(Success(appliable)) == Success('ab') >>> assert Failure('a').apply(Success(appliable)) == Failure('a') >>> assert Success('a').apply(Failure(1)) == Failure(1) >>> assert Failure(1).apply(Failure(2)) == Failure(1)
- bind(function)[source]¶
Composes successful container with a function that returns a container.
>>> from returns.result import Result, Success, Failure >>> def bindable(arg: str) -> Result[str, str]: ... if len(arg) > 1: ... return Success(arg + 'b') ... return Failure(arg + 'c') >>> assert Success('aa').bind(bindable) == Success('aab') >>> assert Success('a').bind(bindable) == Failure('ac') >>> assert Failure('a').bind(bindable) == Failure('a')
- bind_result(function)¶
Alias for bind_result method, it is the same as bind here.
- alt(function)[source]¶
Composes failed container with a pure function to modify failure.
>>> from returns.result import Failure, Success >>> def altable(arg: str) -> str: ... return arg + 'b' >>> assert Success('a').alt(altable) == Success('a') >>> assert Failure('a').alt(altable) == Failure('ab')
- Parameters:
function (
Callable
[[TypeVar
(_ErrorType_co
, covariant=True)],TypeVar
(_NewErrorType
)])- Return type:
Result
[TypeVar
(_ValueType_co
, covariant=True),TypeVar
(_NewErrorType
)]
- lash(function)[source]¶
Composes failed container with a function that returns a container.
>>> from returns.result import Result, Success, Failure >>> def lashable(arg: str) -> Result[str, str]: ... if len(arg) > 1: ... return Success(arg + 'b') ... return Failure(arg + 'c') >>> assert Success('a').lash(lashable) == Success('a') >>> assert Failure('a').lash(lashable) == Failure('ac') >>> assert Failure('aa').lash(lashable) == Success('aab')
- classmethod do(expr)[source]¶
Allows working with unwrapped values of containers in a safe way.
>>> from returns.result import Result, Failure, Success >>> assert Result.do( ... first + second ... for first in Success(2) ... for second in Success(3) ... ) == Success(5) >>> assert Result.do( ... first + second ... for first in Failure('a') ... for second in Success(3) ... ) == Failure('a')
See Do Notation to learn more. This feature requires our mypy plugin.
- Parameters:
expr (
Generator
[TypeVar
(_NewValueType
),None
,None
])- Return type:
Result
[TypeVar
(_NewValueType
),TypeVar
(_NewErrorType
)]
- value_or(default_value)[source]¶
Get value or default value.
>>> from returns.result import Failure, Success >>> assert Success(1).value_or(2) == 1 >>> assert Failure(1).value_or(2) == 2
- Parameters:
default_value (
TypeVar
(_NewValueType
))- Return type:
Union
[TypeVar
(_ValueType_co
, covariant=True),TypeVar
(_NewValueType
)]
- unwrap()[source]¶
Get value or raise exception.
>>> from returns.result import Failure, Success >>> assert Success(1).unwrap() == 1 >>> Failure(1).unwrap() Traceback (most recent call last): ... returns.primitives.exceptions.UnwrapFailedError
- Return type:
TypeVar
(_ValueType_co
, covariant=True)
- failure()[source]¶
Get failed value or raise exception.
>>> from returns.result import Failure, Success >>> assert Failure(1).failure() == 1 >>> Success(1).failure() Traceback (most recent call last): ... returns.primitives.exceptions.UnwrapFailedError
- Return type:
TypeVar
(_ErrorType_co
, covariant=True)
- classmethod from_value(inner_value)[source]¶
One more value to create success unit values.
It is useful as a united way to create a new value from any container.
>>> from returns.result import Result, Success >>> assert Result.from_value(1) == Success(1)
You can use this method or
Success()
, choose the most convenient for you.- Parameters:
inner_value (
TypeVar
(_NewValueType
))- Return type:
Result
[TypeVar
(_NewValueType
),Any
]
- classmethod from_failure(inner_value)[source]¶
One more value to create failure unit values.
It is useful as a united way to create a new value from any container.
>>> from returns.result import Result, Failure >>> assert Result.from_failure(1) == Failure(1)
You can use this method or
Failure()
, choose the most convenient for you.- Parameters:
inner_value (
TypeVar
(_NewErrorType
))- Return type:
Result
[Any
,TypeVar
(_NewErrorType
)]
- classmethod from_result(inner_value)[source]¶
Creates a new
Result
instance from existingResult
instance.>>> from returns.result import Result, Failure, Success >>> assert Result.from_result(Success(1)) == Success(1) >>> assert Result.from_result(Failure(1)) == Failure(1)
This is a part of
returns.interfaces.specific.result.ResultBasedN
interface.
- final class Failure(inner_value)[source]¶
Bases:
Result
[Any
,_ErrorType_co
]Represents a calculation which has failed.
It should contain an error code or message.
- Parameters:
inner_value (
TypeVar
(_ErrorType_co
, covariant=True))
- bind_result(function)¶
Alias for bind method. Part of the ResultBasedN interface.
- final class Success(inner_value)[source]¶
Bases:
Result
[_ValueType_co
,Any
]Represents a calculation which has succeeded and contains the result.
Contains the computation value.
- Parameters:
inner_value (
TypeVar
(_ValueType_co
, covariant=True))
- bind_result(function)¶
Alias for bind method. Part of the ResultBasedN interface.
- safe(exceptions)[source]¶
Decorator to convert exception-throwing function to
Result
container.Should be used with care, since it only catches
Exception
subclasses. It does not catchBaseException
subclasses.If you need to mark
async
function assafe
, usereturns.future.future_safe()
instead. This decorator only works with sync functions. Example:>>> from returns.result import Failure, Success, safe >>> @safe ... def might_raise(arg: int) -> float: ... return 1 / arg >>> assert might_raise(1) == Success(1.0) >>> assert isinstance(might_raise(0), Failure)
You can also use it with explicit exception types as the first argument:
>>> from returns.result import Failure, Success, safe >>> @safe(exceptions=(ZeroDivisionError,)) ... def might_raise(arg: int) -> float: ... return 1 / arg >>> assert might_raise(1) == Success(1.0) >>> assert isinstance(might_raise(0), Failure)
In this case, only exceptions that are explicitly listed are going to be caught.
Similar to
returns.io.impure_safe()
andreturns.future.future_safe()
decorators.- Parameters:
exceptions (
Callable
[[ParamSpec
(_FuncParams
, bound=None
)],TypeVar
(_ValueType_co
, covariant=True)] |tuple
[type
[TypeVar
(_ExceptionType
, bound=Exception
)],...
])- Return type:
Callable
[[ParamSpec
(_FuncParams
, bound=None
)],Result
[TypeVar
(_ValueType_co
, covariant=True),Exception
]] |Callable
[[Callable
[[ParamSpec
(_FuncParams
, bound=None
)],TypeVar
(_ValueType_co
, covariant=True)]],Callable
[[ParamSpec
(_FuncParams
, bound=None
)],Result
[TypeVar
(_ValueType_co
, covariant=True),TypeVar
(_ExceptionType
, bound=Exception
)]]]
- attempt(func)[source]¶
Decorator to convert exception-throwing function to
Result
container.It’s very similar with
returns.result.safe()
, the difference is when an exception is raised it won’t wrap that given exception into a Failure, it’ll wrap the argument that lead to the exception.>>> import json >>> from typing import Dict, Any >>> from returns.result import Failure, Success, attempt >>> @attempt ... def parse_json(string: str) -> Dict[str, Any]: ... return json.loads(string) >>> assert parse_json('{"key": "value"}') == Success({'key': 'value'}) >>> assert parse_json('incorrect input') == Failure('incorrect input')
- Parameters:
func (
Callable
[[TypeVar
(_FirstType
)],TypeVar
(_NewValueType
)])- Return type:
Callable
[[TypeVar
(_FirstType
)],Result
[TypeVar
(_NewValueType
),TypeVar
(_FirstType
)]]