File: result.rst

package info (click to toggle)
python-returns 0.26.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,652 kB
  • sloc: python: 11,000; makefile: 18
file content (261 lines) | stat: -rw-r--r-- 7,503 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
.. _result:

Result
======

Make sure to get familiar with :ref:`Railway oriented programming <railway>`.

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

.. code:: python

  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 :ref:`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 <https://www.python.org/dev/peps/pep-0622/>`_,
see the example below:

.. literalinclude:: ../../tests/test_examples/test_result/test_result_pattern_matching.py


Aliases
-------

There are several useful aliases for ``Result`` type with some common values:

- :attr:`returns.result.ResultE` is an alias for ``Result[... Exception]``,
  just use it when you want to work with ``Result`` containers
  that use exceptions as error type.
  It is named ``ResultE`` because it is ``ResultException``
  and ``ResultError`` at the same time.


Decorators
----------

Limitations
~~~~~~~~~~~

Typing will only work correctly
if :ref:`our mypy plugin <mypy-plugins>` is used.
This happens due to `mypy issue <https://github.com/python/mypy/issues/3157>`_.

safe
~~~~

:func:`safe <returns.result.safe>` is used to convert
regular functions that can throw exceptions to functions
that return :class:`Result <returns.result.Result>` type.

Supports only regular functions.
If you need to mark ``async`` functions as ``safe``,
use :func:`future_safe <returns.future.future_safe>` instead.

.. code:: python

  >>> 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:

.. code:: python

  >>> @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 :func:`safe <returns.result.safe>` function but instead
of wrapping the exception error in a :class:`Failure <returns.result.Failure>` container it'll wrap the
argument that lead to that exception.

.. code:: python

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

.. _result-units:

How to create unit objects?
~~~~~~~~~~~~~~~~~~~~~~~~~~~

Use ``Success`` or ``Failure``.
Alternatively :meth:`returns.result.Result.from_value`
or :meth:`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:

.. code:: python

  >>> 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`` :ref:`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:

.. code:: python

  >>> 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:

.. code:: python

  >>> 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 :meth:`returns.pipeline.is_successful`
like so:

.. code:: python

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

- `Railway Oriented Programming <https://fsharpforfunandprofit.com/rop/>`_
- `Recoverable Errors with Result in Rust <https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html>`_
- `Either overview in TypeScript <https://gcanti.github.io/fp-ts/modules/Either.ts.html>`_


API Reference
-------------

.. autoclasstree:: returns.result
   :strict:

.. automodule:: returns.result
   :members: