Skip to content

Commit 4f0c97c

Browse files
committed
Rename in progress
1 parent ad7be9f commit 4f0c97c

24 files changed

+391
-273
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ We follow Semantic Versions since the `0.1.0` release.
1414
- Renames `do_notation` to `pipeline`, moves it to `functions.py`
1515
- Renames `ebind` to `rescue`
1616
- Renames `efmap` to `fix`
17+
- Renames `Monad` to `Container`
1718

1819

1920
## Version 0.3.1

README.md

Lines changed: 115 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
# returns
22

3+
[![Returns logo](https://raw.githubusercontent.com/dry-python/brand/master/logo/returns.png)](https://github.com/dry-python/returns)
4+
5+
-----
6+
37
[![wemake.services](https://img.shields.io/badge/%20-wemake.services-green.svg?label=%20&logo=data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC%2FxhBQAAAAFzUkdCAK7OHOkAAAAbUExURQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP%2F%2F%2F5TvxDIAAAAIdFJOUwAjRA8xXANAL%2Bv0SAAAADNJREFUGNNjYCAIOJjRBdBFWMkVQeGzcHAwksJnAPPZGOGAASzPzAEHEGVsLExQwE7YswCb7AFZSF3bbAAAAABJRU5ErkJggg%3D%3D)](https://wemake.services) [![Build Status](https://travis-ci.org/dry-python/returns.svg?branch=master)](https://travis-ci.org/dry-python/returns) [![Coverage Status](https://coveralls.io/repos/github/dry-python/returns/badge.svg?branch=master)](https://coveralls.io/github/dry-python/returns?branch=master) [![Documentation Status](https://readthedocs.org/projects/returns/badge/?version=latest)](https://returns.readthedocs.io/en/latest/?badge=latest) [![Python Version](https://img.shields.io/pypi/pyversions/returns.svg)](https://pypi.org/project/returns/) [![wemake-python-styleguide](https://img.shields.io/badge/style-wemake-000000.svg)](https://github.com/wemake-services/wemake-python-styleguide)
48

9+
-----
510

611
Make your functions return something meaningful, typed, and safe!
712

813

914
## Features
1015

11-
- Provides primitives to write declarative business logic
16+
- Provides a bunch of primitives to write declarative business logic
1217
- Fully typed with annotations and checked with `mypy`,
1318
allowing you to write type-safe code as well
1419
- Pythonic and pleasant to write and to read (!)
@@ -21,31 +26,125 @@ Make your functions return something meaningful, typed, and safe!
2126
pip install returns
2227
```
2328

29+
Make sure you know how to get started, [checkout our docs](https://returns.readthedocs.io/en/latest/)!
30+
2431
## Why?
2532

26-
TODO: example with `requests` and `json`
33+
Consider this code that you can find in **any** `python` project.
2734

35+
### Straight-forward approach
2836

29-
## Pipeline example
37+
```python
38+
import requests
39+
40+
41+
def fetch_user_profile(user_id: int) -> 'UserProfile':
42+
"""Fetches UserProfile dict from foreign API."""
43+
response = requests.get('/api/users/{0}'.format(user_id))
44+
response.raise_for_status()
45+
return response.json()
46+
```
3047

48+
Seems legit, does not it?
49+
It also seems like a pretty straight forward code to test.
50+
All you need is to mock `requests.get` to return the structure you need.
51+
52+
But, there are hidden problems in this tiny code sample
53+
that are almost impossible to spot at the first glance.
54+
55+
### Hidden problems
56+
57+
58+
The same exact code, but with the problems explained.
3159

3260
```python
33-
from returns.pipeline import pipeline
34-
from returns.result import Result, Success, Failure
61+
import requests
62+
63+
64+
def fetch_user_profile(user_id: int) -> 'UserProfile':
65+
"""Fetches UserProfile dict from foreign API."""
66+
response = requests.get('/api/users/{0}'.format(user_id))
67+
68+
# What if we try to find user that does not exist?
69+
# Or network will go down? Or the server will return 500?
70+
# In this case the next line will fail with an exception.
71+
# We need to handle all possible errors in this function
72+
# and do not return corrupt data to consumers.
73+
response.raise_for_status()
74+
75+
# What if we have received invalid JSON?
76+
# Next line will raise an exception!
77+
return response.json()
78+
```
3579

36-
class CreateAccountAndUser(object):
37-
"""Creates new Account-User pair."""
80+
Now, all (probably all?) problems are clear.
81+
How can we be sure that this function will be safe
82+
to use inside our complex business logic?
3883

39-
@pipeline
40-
def __call__(self, username: str, email: str) -> Result['User', str]:
41-
"""Can return a Success(user) or Failure(str_reason)."""
42-
user_schema = self._validate_user(username, email).unwrap()
43-
account = self._create_account(user_schema).unwrap()
44-
return self._create_user(account)
84+
We really can not be sure!
85+
We will have to create **lots** of `try` and `except` cases
86+
just to catch the expected exceptions.
4587

46-
# Protected methods
47-
# ...
88+
Our code will become complex and unreadable with all this mess!
4889

90+
91+
### Pipeline example
92+
93+
94+
```python
95+
import requests
96+
from returns.functions import pipeline, safe
97+
from returns.result import Result
98+
99+
100+
class FetchUserProfile(object):
101+
"""Single responibility callable object that fetches user profiles."""
102+
103+
#: You can later use dependency injection to replace `requests`
104+
#: with any other http library you want.
105+
_http = requests
106+
107+
@pipeline
108+
def __call__(self, user_id: int) -> Result['UserProfile', Exception]:
109+
"""Fetches UserProfile dict from foreign API."""
110+
response = self._make_request(user_id).unwrap()
111+
return self._parse_json(response)
112+
113+
@safe
114+
def _make_request(self, user_id: int) -> requests.Response:
115+
response = self._http.get('/api/users/{0}'.format(user_id))
116+
response.raise_for_status()
117+
return response
118+
119+
@safe
120+
def _parse_json(self, response: requests.Response) -> 'UserProfile':
121+
return response.json()
49122
```
50123

51-
We are [covering what's going on in this example](https://returns.readthedocs.io/en/latest/pages/do-notation.html) in the docs.
124+
Now we have a clean and a safe way to express our business need.
125+
We start from making a request, that might fail at any moment.
126+
127+
Now, instead of returning a regular value
128+
it returns a wrapped value inside a special container
129+
thanks to the
130+
[@safe](https://returns.readthedocs.io/en/latest/pages/functions.html#returns.functions.safe)
131+
decorator.
132+
133+
It will return [Success[Response] or Failure[Exception]](https://returns.readthedocs.io/en/latest/pages/result.html).
134+
And will never throw this exception at us.
135+
136+
When we will need raw value, we can use `.unwrap()` method to get it.
137+
If the result is `Failure[Exception]` we will actually raise an exception at this point.
138+
But it is safe to use `.unwrap()` inside [@pipeline](https://returns.readthedocs.io/en/latest/pages/functions.html#returns.functions.pipeline)
139+
functions.
140+
Because it will catch this exception and wrap it inside a new `Failure[Exception]`!
141+
142+
And we can clearly see all result patterns that might happen in this particular case:
143+
- `Success[UserProfile]`
144+
- `Failure[HttpException]`
145+
- `Failure[JsonDecodeException]`
146+
147+
148+
## License
149+
150+
MIT.

docs/conf.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,9 @@ def _get_project_meta():
119119
# documentation.
120120
html_theme_options = {
121121
'logo_name': 'returns',
122-
'description': 'Monads for python made simple and safe',
122+
'description': (
123+
'Make your functions return something meaningful, typed, and safe!'
124+
),
123125
'github_user': 'dry-python',
124126
'github_repo': 'returns',
125127
}

docs/index.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ Contents
1919
pages/monad.rst
2020
pages/maybe.rst
2121
pages/result.rst
22-
pages/do-notation.rst
2322
pages/functions.rst
2423

2524
.. toctree::

docs/pages/monad.rst renamed to docs/pages/container.rst

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
Monad: the concept
2-
==================
1+
Container: the concept
2+
======================
33

44
.. currentmodule:: returns.primitives.monads
55

66
We won't say that monad is `a monoid in the category of endofunctors <https://stackoverflow.com/questions/3870088/a-monad-is-just-a-monoid-in-the-category-of-endofunctors-whats-the-problem>`_.
77

8-
Monad is a concept that allows you
8+
Container is a concept that allows you
99
to write code without traditional error handling
1010
while maintaining the execution context.
1111

@@ -16,7 +16,7 @@ Internals
1616

1717
The main idea behind a monad is that it wraps some internal state.
1818
That's what
19-
:py:attr:`Monad._inner_value <returns.primitives.monad.Monad._inner_value>`
19+
:py:attr:`Container._inner_value <returns.primitives.monad.Container._inner_value>`
2020
is used for.
2121

2222
And we have several functions to create new monads based on the previous state.
@@ -42,7 +42,7 @@ The difference is simple:
4242
- ``map`` works with functions that return regular values
4343
- ``bind`` works with functions that return monads
4444

45-
:func:`Monad.bind <returns.primitives.monad.Monad.bind>`
45+
:func:`Container.bind <returns.primitives.monad.Container.bind>`
4646
is used to literally bind two different monads together.
4747

4848
.. code:: python
@@ -58,7 +58,7 @@ is used to literally bind two different monads together.
5858
So, the rule is: whenever you have some impure functions,
5959
it should return a monad instead.
6060

61-
And we use :func:`Monad.map <returns.primitives.monad.Monad.map>`
61+
And we use :func:`Container.map <returns.primitives.monad.Container.map>`
6262
to use monads with pure functions.
6363

6464
.. code:: python
@@ -77,9 +77,9 @@ Reverse operations
7777
We also support two special methods to work with "failed"
7878
monads like ``Failure`` and ``Nothing``:
7979

80-
- :func:`Monad.fix <returns.primitives.monad.Monad.fix>` the opposite
80+
- :func:`Container.fix <returns.primitives.monad.Container.fix>` the opposite
8181
of ``map`` method that works only when monad is failed
82-
- :func:`Monad.rescue <returns.primitives.monad.Monad.rescue>` the opposite
82+
- :func:`Container.rescue <returns.primitives.monad.Container.rescue>` the opposite
8383
of ``bind`` method that works only when monad is failed
8484

8585
``fix`` can be used to fix some fixable errors
@@ -116,9 +116,9 @@ Unwrapping values
116116
And we have two more functions to unwrap
117117
inner state of monads into a regular types:
118118

119-
- :func:`Monad.value_or <returns.primitives.monad.Monad.value_or>` - returns
119+
- :func:`Container.value_or <returns.primitives.monad.Container.value_or>` - returns
120120
a value if it is possible, returns ``default_value`` otherwise
121-
- :func:`Monad.unwrap <returns.primitives.monad.Monad.unwrap>` - returns
121+
- :func:`Container.unwrap <returns.primitives.monad.Container.unwrap>` - returns
122122
a value if it possible, raises ``UnwrapFailedError`` otherwise
123123

124124
.. code:: python
@@ -140,7 +140,7 @@ inner state of monads into a regular types:
140140
The most user-friendly way to use ``unwrap`` method is with :ref:`do-notation`.
141141

142142
For failing monads you can
143-
use :func:`Monad.failure <returns.primitives.monad.Monad.failure>`
143+
use :func:`Container.failure <returns.primitives.monad.Container.failure>`
144144
to unwrap failed state:
145145

146146
.. code:: python
@@ -196,7 +196,7 @@ This way your code will be type-safe from errors.
196196
API Reference
197197
-------------
198198

199-
.. autoclasstree:: returns.primitives.monad
199+
.. autoclasstree:: returns.primitives.container
200200

201-
.. automodule:: returns.primitives.monad
201+
.. automodule:: returns.primitives.container
202202
:members:

0 commit comments

Comments
 (0)