1
1
# returns
2
2
3
+ [ ![ Returns logo] ( https://raw.githubusercontent.com/dry-python/brand/master/logo/returns.png )] ( https://github.com/dry-python/returns )
4
+
5
+ -----
6
+
3
7
[](https://wemake.services) [](https://travis-ci.org/dry-python/returns) [](https://coveralls.io/github/dry-python/returns?branch=master) [](https://returns.readthedocs.io/en/latest/?badge=latest) [](https://pypi.org/project/returns/) [](https://github.com/wemake-services/wemake-python-styleguide)
4
8
9
+ -----
5
10
6
11
Make your functions return something meaningful, typed, and safe!
7
12
8
13
9
14
## Features
10
15
11
- - Provides primitives to write declarative business logic
16
+ - Provides a bunch of primitives to write declarative business logic
12
17
- Fully typed with annotations and checked with ` mypy ` ,
13
18
allowing you to write type-safe code as well
14
19
- Pythonic and pleasant to write and to read (!)
@@ -21,31 +26,125 @@ Make your functions return something meaningful, typed, and safe!
21
26
pip install returns
22
27
```
23
28
29
+ Make sure you know how to get started, [ checkout our docs] ( https://returns.readthedocs.io/en/latest/ ) !
30
+
24
31
## Why?
25
32
26
- TODO: example with ` requests ` and ` json `
33
+ Consider this code that you can find in ** any ** ` python ` project.
27
34
35
+ ### Straight-forward approach
28
36
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
+ ```
30
47
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.
31
59
32
60
``` 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
+ ```
35
79
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?
38
83
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.
45
87
46
- # Protected methods
47
- # ...
88
+ Our code will become complex and unreadable with all this mess!
48
89
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()
49
122
```
50
123
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.
0 commit comments