@@ -8,6 +8,7 @@ and can pass different things into your logic instead of hardcoding you stuff.
8
8
And by doing this you are on your way to achieve `Single Responsibility <https://en.wikipedia.org/wiki/Single_responsibility_principle >`_
9
9
for your functions and objects.
10
10
11
+
11
12
Using the context
12
13
-----------------
13
14
@@ -135,13 +136,62 @@ Let's see how our code changes:
135
136
136
137
And now you can pass your dependencies in a really direct and explicit way.
137
138
139
+ ask
140
+ ~~~
141
+
142
+ Let's try to configure how we mark our unguessed letters
143
+ (previously unguessed letters were marked as ``'.' ``).
144
+ Let's say, we want to change this to be ``_ ``.
145
+
146
+ How can we do that with our existing function?
147
+
148
+ .. code :: python
149
+
150
+ def calculate_points (word : str ) -> RequiresContext[_Deps, int ]:
151
+ guessed_letters_count = len ([letter for letter in word if letter != ' .' ])
152
+ return _award_points_for_letters(guessed_letters_count)
153
+
154
+ We are already using ``RequiresContext ``,
155
+ but its dependencies are just hidden from us!
156
+ We have a special helper for this case: :meth: `returns.context.Context.ask() `,
157
+ which returns us current dependencies.
158
+
159
+ The only thing we need to is to properly
160
+ annotate the type for our case: ``Context[_Deps].ask() ``
161
+ Sadly, currently ``mypy `` is not able to infer the dependency type
162
+ out of the context and we need to explicitly provide it.
163
+
164
+ Let's see the final result:
165
+
166
+ .. code :: python
167
+
168
+ from returns.context import Context, RequiresContext
169
+
170
+ class _Deps (Protocol ): # we rely on abstractions, not direct values or types
171
+ WORD_THRESSHOLD : int
172
+ UNGUESSED_CHAR : str
173
+
174
+ def calculate_points (word : str ) -> RequiresContext[_Deps, int ]:
175
+ def factory (deps : _Deps) -> RequiresContext[_Deps, int ]:
176
+ guessed_letters_count = len ([
177
+ letter for letter in word if letter != deps.UNGUESSED_CHAR
178
+ ])
179
+ return _award_points_for_letters(guessed_letters_count)
180
+
181
+ return Context[_Deps].ask().bind(factory)
182
+
183
+ And now we access the current context from any place in our callstack.
184
+ Isn't it convenient?
185
+
138
186
139
187
RequiresContext container
140
188
-------------------------
141
189
142
190
The concept behind ``RequiresContext `` container is really simple.
143
191
It is a container around ``Callable[[EnvType], ReturnType] `` function.
144
192
193
+ By its definition it works with pure functions that never fails.
194
+
145
195
It can be illustrated as a simple nested function:
146
196
147
197
.. code :: python
@@ -199,54 +249,29 @@ There's how execution flows:
199
249
F4 --> F5
200
250
F5["bool_to_str()"] --> F6["str"]
201
251
252
+ The rule is: the dependencies are injected at the very last moment in time.
253
+ And then normal logical execution happens.
202
254
203
- Context helper
204
- --------------
205
-
206
- Let's go back to our ``django `` example
207
- and try to configure how we mark our unguessed letters
208
- (previously unguessed letters were marked as ``'.' ``).
209
- Let's say, we want to change this to be ``_ ``.
210
-
211
- How can we do that with our existing function?
212
-
213
- .. code :: python
214
-
215
- def calculate_points (word : str ) -> RequiresContext[_Deps, int ]:
216
- guessed_letters_count = len ([letter for letter in word if letter != ' .' ])
217
- return _award_points_for_letters(guessed_letters_count)
218
-
219
- We are already using ``RequiresContext ``,
220
- but its dependencies are just hidden from us!
221
- We have a special helper for this case: ``Context.ask() ``,
222
- which returns us current dependencies.
223
-
224
- The only thing we need to is to properly
225
- annotate the type for our case: ``Context[_Deps].ask() ``
226
- Sadly, currently ``mypy `` is not able to infer the dependency type
227
- out of the context and we need to explicitly provide it.
228
-
229
- Let's see the final result:
230
-
231
- .. code :: python
232
255
233
- from returns.context import Context, RequiresContext
256
+ RequiresContextResult container
257
+ -------------------------------
234
258
235
- class _Deps (Protocol ): # we rely on abstractions, not direct values or types
236
- WORD_THRESSHOLD : int
237
- UNGUESSED_CHAR : str
259
+ This container is a combintaion of ``RequiresContext[env, Result[a, b]] ``.
260
+ Which means that it is a wrapper around pure function that might fail.
238
261
239
- def calculate_points (word : str ) -> RequiresContext[_Deps, int ]:
240
- def factory (deps : _Deps) -> RequiresContext[_Deps, int ]:
241
- guessed_letters_count = len ([
242
- letter for letter in word if letter != deps.UNGUESSED_CHAR
243
- ])
244
- return _award_points_for_letters(guessed_letters_count)
262
+ We also added a lot of useful methods for this container,
263
+ so you can work easily with it:
245
264
246
- return Context[_Deps].ask().bind(factory)
265
+ - :meth: `returns.context.RequiresContextResult.from_typecast ` turns
266
+ accidental ``RequiresContext[env, Result[a, b]] `` into
267
+ full-featured ``RequiresContextResult[env, a, b] ``
268
+ - :meth: `returns.context.RequiresContextResult.bind_result ` allows to bind
269
+ functions that return ``Result `` with just one call
270
+ - :meth: returns.context.RequiresContextResult.bind_context` allows to bind
271
+ functions that return ``RequiresContext `` easily
272
+ - There are also several useful contructors from any possible type
247
273
248
- And now we access the current context from any place in our callstack.
249
- Isn't it convenient?
274
+ Use it when you work with pure context-related functions that might fail.
250
275
251
276
252
277
FAQ
@@ -258,6 +283,9 @@ Why do I have to use explicit type annotation for ask method?
258
283
How to create unit objects?
259
284
~~~~~~~~~~~~~~~~~~~~~~~~~~~
260
285
286
+ How can I access dependencies inside the context?
287
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
288
+
261
289
262
290
263
291
@@ -273,7 +301,18 @@ Further reading
273
301
API Reference
274
302
-------------
275
303
276
- .. autoclasstree :: returns.context
304
+ RequiresContext
305
+ ~~~~~~~~~~~~~~~
306
+
307
+ .. autoclasstree :: returns.context.requires_context
308
+
309
+ .. automodule :: returns.context.requires_context
310
+ :members:
311
+
312
+ RequiresContextResult
313
+ ~~~~~~~~~~~~~~~~~~~~~
314
+
315
+ .. autoclasstree :: returns.context.requires_context_result
277
316
278
- .. automodule :: returns.context
317
+ .. automodule :: returns.context.requires_context_result
279
318
:members:
0 commit comments