@@ -3,210 +3,6 @@ Helper functions
3
3
4
4
We feature several helper functions to make your developer experience better.
5
5
6
-
7
- is_successful
8
- -------------
9
-
10
- :func: `is_succesful <returns.functions.is_successful> ` is used to
11
- tell whether or not your result is a success.
12
- We treat only treat types that does not throw as a successful ones,
13
- basically: :class: `Success <returns.result.Success> `.
14
-
15
- .. code :: python
16
-
17
- from returns.result import Success, Failure
18
- from returns.functions import is_successful
19
-
20
- is_successful(Success(1 ))
21
- # => True
22
-
23
- is_successful(Failure(' text' ))
24
- # => False
25
-
26
- .. _pipeline :
27
-
28
- pipeline
29
- --------
30
-
31
- What is a ``pipeline ``?
32
- It is a more user-friendly syntax to work with containers
33
- that support both async and regular functions.
34
-
35
- Consider this task.
36
- We were asked to create a method
37
- that will connect together a simple pipeline of three steps:
38
-
39
- 1. We validate passed ``username `` and ``email ``
40
- 2. We create a new ``Account `` with this data, if it does not exists
41
- 3. We create a new ``User `` associated with the ``Account ``
42
-
43
- And we know that this pipeline can fail in several places:
44
-
45
- 1. Wrong ``username `` or ``email `` might be passed, so the validation will fail
46
- 2. ``Account `` with this ``username `` or ``email `` might already exist
47
- 3. ``User `` creation might fail as well,
48
- since it also makes an ``HTTP `` request to another micro-service deep inside
49
-
50
- Here's the code to illustrate the task.
51
-
52
- .. code :: python
53
-
54
- from returns.functions import pipeline
55
- from returns.result import Result, Success, Failure
56
-
57
-
58
- class CreateAccountAndUser (object ):
59
- """ Creates new Account-User pair."""
60
-
61
- # TODO : we need to create a pipeline of these methods somehow...
62
-
63
- # Protected methods
64
-
65
- def _validate_user (
66
- self , username : str , email : str ,
67
- ) -> Result[' UserSchema' , str ]:
68
- """ Returns an UserSchema for valid input, otherwise a Failure."""
69
-
70
- def _create_account (
71
- self , user_schema : ' UserSchema' ,
72
- ) -> Result[' Account' , str ]:
73
- """ Creates an Account for valid UserSchema's. Or returns a Failure."""
74
-
75
- def _create_user (
76
- self , account : ' Account' ,
77
- ) -> Result[' User' , str ]:
78
- """ Create an User instance. If user already exists returns Failure."""
79
-
80
- Using bind technique
81
- ~~~~~~~~~~~~~~~~~~~~
82
-
83
- We can implement this feature using a traditional ``bind `` method.
84
-
85
- .. code :: python
86
-
87
- class CreateAccountAndUser (object ):
88
- """ Creates new Account-User pair."""
89
-
90
- def __call__ (self , username : str , email : str ) -> Result[' User' , str ]:
91
- """ Can return a Success(user) or Failure(str_reason)."""
92
- return self ._validate_user(username, email).bind(
93
- self ._create_account,
94
- ).bind(
95
- self ._create_user,
96
- )
97
-
98
- # Protected methods
99
- # ...
100
-
101
- And this will work without any problems.
102
- But, is it easy to read a code like this? **No **, it is not.
103
-
104
- What alternative we can provide? ``@pipeline ``!
105
-
106
- Using pipeline
107
- ~~~~~~~~~~~~~~
108
-
109
- And here's how we can refactor previous version to be more clear.
110
-
111
- .. code :: python
112
-
113
- class CreateAccountAndUser (object ):
114
- """ Creates new Account-User pair."""
115
-
116
- @pipeline
117
- def __call__ (self , username : str , email : str ) -> Result[' User' , str ]:
118
- """ Can return a Success(user) or Failure(str_reason)."""
119
- user_schema = self ._validate_user(username, email).unwrap()
120
- account = self ._create_account(user_schema).unwrap()
121
- return self ._create_user(account)
122
-
123
- # Protected methods
124
- # ...
125
-
126
- Let's see how this new ``.unwrap() `` method works:
127
-
128
- - if you result is ``Success `` it will return its inner value
129
- - if your result is ``Failure `` it will raise a ``UnwrapFailedError ``
130
-
131
- And that's where ``@pipeline `` decorator becomes in handy.
132
- It will catch any ``UnwrapFailedError `` during the pipeline
133
- and then return a simple ``Failure `` result.
134
-
135
- .. mermaid ::
136
- :caption: Pipeline execution.
137
-
138
- sequenceDiagram
139
- participant pipeline
140
- participant validation
141
- participant account creation
142
- participant user creation
143
-
144
- pipeline->>validation: runs the first step
145
- validation-->>pipeline: returns Failure(validation message) if fails
146
- validation->>account creation: passes Success(UserSchema) if valid
147
- account creation-->>pipeline: return Failure(account exists) if fails
148
- account creation->>user creation: passes Success(Account) if valid
149
- user creation-->>pipeline: returns Failure(http status) if fails
150
- user creation-->>pipeline: returns Success(user) if user is created
151
-
152
- See, do notation allows you to write simple yet powerful pipelines
153
- with multiple and complex steps.
154
- And at the same time the produced code is simple and readable.
155
-
156
- And that's it!
157
-
158
-
159
- safe
160
- ----
161
-
162
- :func: `safe <returns.functions.safe> ` is used to convert
163
- regular functions that can throw exceptions to functions
164
- that return :class: `Result <returns.result.Result> ` type.
165
-
166
- Supports both async and regular functions.
167
-
168
- .. code :: python
169
-
170
- from returns.functions import safe
171
-
172
- @safe
173
- def divide (number : int ) -> float :
174
- return number / number
175
-
176
- divide(1 )
177
- # => Success(1.0)
178
-
179
- divide(0 )
180
- # => Failure(ZeroDivisionError)
181
-
182
- Limitations
183
- ~~~~~~~~~~~
184
-
185
- There's one limitation in typing
186
- that we are facing right now
187
- due to `mypy issue <https://github.com/python/mypy/issues/3157 >`_:
188
-
189
- .. code :: python
190
-
191
- from returns.functions import safe
192
-
193
- @safe
194
- def function (param : int ) -> int :
195
- return param
196
-
197
- reveal_type(function)
198
- # Actual => def (*Any, **Any) -> builtins.int
199
- # Expected => def (int) -> builtins.int
200
-
201
- This effect can be reduced
202
- with the help of `Design by Contract <https://en.wikipedia.org/wiki/Design_by_contract >`_
203
- with these implementations:
204
-
205
- - https://github.com/deadpixi/contracts
206
- - https://github.com/orsinium/deal
207
- - https://github.com/Parquery/icontract
208
-
209
-
210
6
compose
211
7
-------
212
8
0 commit comments