@@ -14,8 +14,9 @@ Make your functions return something meaningful, typed, and safe!
14
14
## Features
15
15
16
16
- Provides a bunch of primitives to write declarative business logic
17
+ - Enforces [ Railway Oriented Programming] ( https://fsharpforfunandprofit.com/rop/ )
17
18
- Fully typed with annotations and checked with ` mypy ` ,
18
- allowing you to write type-safe code as well
19
+ allowing you to write type-safe code as well, [ PEP561 compatible ] ( https://www.python.org/dev/peps/pep-0561/ )
19
20
- Pythonic and pleasant to write and to read (!)
20
21
21
22
@@ -28,6 +29,7 @@ pip install returns
28
29
29
30
Make sure you know how to get started, [ checkout our docs] ( https://returns.readthedocs.io/en/latest/ ) !
30
31
32
+
31
33
## Why?
32
34
33
35
Consider this code that you can find in ** any** ` python ` project.
@@ -37,52 +39,50 @@ Consider this code that you can find in **any** `python` project.
37
39
``` python
38
40
import requests
39
41
40
-
41
42
def fetch_user_profile (user_id : int ) -> ' UserProfile' :
42
43
""" Fetches UserProfile dict from foreign API."""
43
44
response = requests.get(' /api/users/{0} ' .format(user_id))
44
45
response.raise_for_status()
45
46
return response.json()
46
47
```
47
48
48
- Seems legit, does not it?
49
- It also seems like a pretty straight forward code to test.
49
+ Seems legit, does not it?
50
+ It also seems like a pretty straight forward code to test.
50
51
All you need is to mock ` requests.get ` to return the structure you need.
51
52
52
- But, there are hidden problems in this tiny code sample
53
+ But, there are hidden problems in this tiny code sample
53
54
that are almost impossible to spot at the first glance.
54
55
55
56
### Hidden problems
56
57
57
-
58
- The same exact code, but with the problems explained.
58
+ Let's have a look at the exact same code,
59
+ but with the all hidden problems explained.
59
60
60
61
``` python
61
62
import requests
62
63
63
-
64
64
def fetch_user_profile (user_id : int ) -> ' UserProfile' :
65
65
""" Fetches UserProfile dict from foreign API."""
66
66
response = requests.get(' /api/users/{0} ' .format(user_id))
67
-
68
- # What if we try to find user that does not exist?
67
+
68
+ # What if we try to find user that does not exist?
69
69
# Or network will go down? Or the server will return 500?
70
70
# In this case the next line will fail with an exception.
71
- # We need to handle all possible errors in this function
71
+ # We need to handle all possible errors in this function
72
72
# and do not return corrupt data to consumers.
73
73
response.raise_for_status()
74
74
75
- # What if we have received invalid JSON?
75
+ # What if we have received invalid JSON?
76
76
# Next line will raise an exception!
77
77
return response.json()
78
78
```
79
79
80
- Now, all (probably all?) problems are clear.
81
- How can we be sure that this function will be safe
80
+ Now, all (probably all?) problems are clear.
81
+ How can we be sure that this function will be safe
82
82
to use inside our complex business logic?
83
83
84
- We really can not be sure!
85
- We will have to create ** lots** of ` try ` and ` except ` cases
84
+ We really can not be sure!
85
+ We will have to create ** lots** of ` try ` and ` except ` cases
86
86
just to catch the expected exceptions.
87
87
88
88
Our code will become complex and unreadable with all this mess!
@@ -96,26 +96,25 @@ import requests
96
96
from returns.functions import pipeline, safe
97
97
from returns.result import Result
98
98
99
-
100
99
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 .
100
+ """ Single responsibility callable object that fetches user profile ."""
101
+
102
+ # : You can later use dependency injection to replace `requests`
103
+ # : with any other http library (or even a custom service) .
105
104
_http = requests
106
-
105
+
107
106
@pipeline
108
107
def __call__ (self , user_id : int ) -> Result[' UserProfile' , Exception ]:
109
108
""" Fetches UserProfile dict from foreign API."""
110
109
response = self ._make_request(user_id).unwrap()
111
110
return self ._parse_json(response)
112
-
111
+
113
112
@safe
114
113
def _make_request (self , user_id : int ) -> requests.Response:
115
114
response = self ._http.get(' /api/users/{0} ' .format(user_id))
116
115
response.raise_for_status()
117
116
return response
118
-
117
+
119
118
@safe
120
119
def _parse_json (self , response : requests.Response) -> ' UserProfile' :
121
120
return response.json()
@@ -124,18 +123,18 @@ class FetchUserProfile(object):
124
123
Now we have a clean and a safe way to express our business need.
125
124
We start from making a request, that might fail at any moment.
126
125
127
- Now, instead of returning a regular value
126
+ Now, instead of returning a regular value
128
127
it returns a wrapped value inside a special container
129
128
thanks to the
130
129
[ @safe ] ( https://returns.readthedocs.io/en/latest/pages/functions.html#returns.functions.safe )
131
130
decorator.
132
131
133
- It will return [ Success[ Response] or Failure[ Exception]] ( https://returns.readthedocs.io/en/latest/pages/result.html ) .
132
+ It will return [ Success[ Response] or Failure[ Exception]] ( https://returns.readthedocs.io/en/latest/pages/result.html ) .
134
133
And will never throw this exception at us.
135
134
136
135
When we will need raw value, we can use ` .unwrap() ` method to get it.
137
136
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 )
137
+ But it is safe to use ` .unwrap() ` inside [ @pipeline ] ( https://returns.readthedocs.io/en/latest/pages/functions.html#returns.functions.pipeline )
139
138
functions.
140
139
Because it will catch this exception and wrap it inside a new ` Failure[Exception] ` !
141
140
@@ -144,6 +143,9 @@ And we can clearly see all result patterns that might happen in this particular
144
143
- ` Failure[HttpException] `
145
144
- ` Failure[JsonDecodeException] `
146
145
146
+ And we can work with each of them precisely.
147
+
148
+ What more? [ Go to the docs!] ( https://returns.readthedocs.io )
147
149
148
150
## License
149
151
0 commit comments