Skip to content

Commit 246d284

Browse files
committed
Working on docs
1 parent ec13294 commit 246d284

File tree

6 files changed

+548
-369
lines changed

6 files changed

+548
-369
lines changed

CHANGELOG.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ See [0Ver](https://0ver.org/).
2828
- Adds `RequiresContextResult` support for `bind`
2929
- Adds `RequiresContextResult` support for `flatten`
3030
- Adds `RequiresContextResult` support for `fold`
31-
- Adds `RequiresContextResult` support for `is_successful`
3231

3332
- Adds `IOResult` helper to work better with `IO[Result[a, b]]`
3433
- Adds `IOResultE` alias for `IOResult[a, Exception]`

docs/pages/context.rst

Lines changed: 83 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and can pass different things into your logic instead of hardcoding you stuff.
88
And by doing this you are on your way to achieve `Single Responsibility <https://en.wikipedia.org/wiki/Single_responsibility_principle>`_
99
for your functions and objects.
1010

11+
1112
Using the context
1213
-----------------
1314

@@ -135,13 +136,62 @@ Let's see how our code changes:
135136
136137
And now you can pass your dependencies in a really direct and explicit way.
137138

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

139187
RequiresContext container
140188
-------------------------
141189

142190
The concept behind ``RequiresContext`` container is really simple.
143191
It is a container around ``Callable[[EnvType], ReturnType]`` function.
144192

193+
By its definition it works with pure functions that never fails.
194+
145195
It can be illustrated as a simple nested function:
146196

147197
.. code:: python
@@ -199,54 +249,29 @@ There's how execution flows:
199249
F4 --> F5
200250
F5["bool_to_str()"] --> F6["str"]
201251

252+
The rule is: the dependencies are injected at the very last moment in time.
253+
And then normal logical execution happens.
202254

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
232255

233-
from returns.context import Context, RequiresContext
256+
RequiresContextResult container
257+
-------------------------------
234258

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

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

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
247273

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

251276

252277
FAQ
@@ -258,6 +283,9 @@ Why do I have to use explicit type annotation for ask method?
258283
How to create unit objects?
259284
~~~~~~~~~~~~~~~~~~~~~~~~~~~
260285

286+
How can I access dependencies inside the context?
287+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
288+
261289

262290

263291

@@ -273,7 +301,18 @@ Further reading
273301
API Reference
274302
-------------
275303

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
277316

278-
.. automodule:: returns.context
317+
.. automodule:: returns.context.requires_context_result
279318
:members:

returns/context/__init__.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
This module is quite big one, so we have split it.
5+
6+
isort:skip_file
7+
"""
8+
9+
from returns.context.requires_context import ( # noqa: F401
10+
Context as Context,
11+
RequiresContext as RequiresContext,
12+
)
13+
from returns.context.requires_context_result import ( # noqa: F401
14+
ContextResult as ContextResult,
15+
RequiresContextResult as RequiresContextResult,
16+
RequiresContextResultE as RequiresContextResultE,
17+
)

0 commit comments

Comments
 (0)