12
12
import uritemplate
13
13
14
14
15
+ def _get_http_method (action ):
16
+ if not action :
17
+ return 'GET'
18
+ return action .upper ()
19
+
20
+
21
+ def _seperate_params (method , fields , params = None ):
22
+ """
23
+ Seperate the params into their location types: path, query, or form.
24
+ """
25
+ if params is None :
26
+ return ({}, {}, {})
27
+
28
+ field_map = {field .name : field for field in fields }
29
+ path_params = {}
30
+ query_params = {}
31
+ form_params = {}
32
+ for key , value in params .items ():
33
+ if key not in field_map or not field_map [key ].location :
34
+ # Default is 'query' for 'GET'/'DELETE', and 'form' others.
35
+ location = 'query' if method in ('GET' , 'DELETE' ) else 'form'
36
+ else :
37
+ location = field_map [key ].location
38
+
39
+ if location == 'path' :
40
+ path_params [key ] = value
41
+ elif location == 'query' :
42
+ query_params [key ] = value
43
+ else :
44
+ form_params [key ] = value
45
+
46
+ return path_params , query_params , form_params
47
+
48
+
49
+ def _expand_path_params (url , path_params ):
50
+ """
51
+ Given a templated URL and some parameters that have been provided,
52
+ expand the URL.
53
+ """
54
+ if path_params :
55
+ return uritemplate .expand (url , path_params )
56
+ return url
57
+
58
+
59
+ def _get_headers (url , decoders = None , credentials = None , extra_headers = None ):
60
+ """
61
+ Return a dictionary of HTTP headers to use in the outgoing request.
62
+ """
63
+ if decoders is None :
64
+ decoders = default_decoders
65
+
66
+ accept = ', ' .join ([decoder .media_type for decoder in decoders ])
67
+
68
+ headers = {
69
+ 'accept' : accept
70
+ }
71
+
72
+ if credentials :
73
+ # Include any authorization credentials relevant to this domain.
74
+ url_components = urlparse .urlparse (url )
75
+ host = url_components .netloc
76
+ if host in credentials :
77
+ headers ['authorization' ] = credentials [host ]
78
+
79
+ if extra_headers :
80
+ # Include any custom headers associated with this transport.
81
+ headers .update (extra_headers )
82
+
83
+ return headers
84
+
85
+
86
+ def _make_http_request (url , method , headers = None , query_params = None , form_params = None ):
87
+ """
88
+ Make an HTTP request and return an HTTP response.
89
+ """
90
+ opts = {
91
+ "headers" : headers or {}
92
+ }
93
+
94
+ if query_params :
95
+ opts ['params' ] = query_params
96
+ elif form_params :
97
+ opts ['data' ] = json .dumps (form_params )
98
+ opts ['headers' ]['content-type' ] = 'application/json'
99
+
100
+ return requests .request (method , url , ** opts )
101
+
102
+
15
103
def _coerce_to_error_content (node ):
16
- # Errors should not contain nested documents or links.
17
- # If we get a 4xx or 5xx response with a Document, then coerce it
18
- # into plain data.
104
+ """
105
+ Errors should not contain nested documents or links.
106
+ If we get a 4xx or 5xx response with a Document, then coerce
107
+ the document content into plain data.
108
+ """
19
109
if isinstance (node , (Document , Object )):
20
110
# Strip Links from Documents, treat Documents as plain dicts.
21
111
return OrderedDict ([
@@ -33,6 +123,9 @@ def _coerce_to_error_content(node):
33
123
34
124
35
125
def _coerce_to_error (obj , default_title ):
126
+ """
127
+ Given an arbitrary return result, coerce it into an Error instance.
128
+ """
36
129
if isinstance (obj , Document ):
37
130
return Error (
38
131
title = obj .title or default_title ,
@@ -42,14 +135,53 @@ def _coerce_to_error(obj, default_title):
42
135
return Error (title = default_title , content = obj )
43
136
elif isinstance (obj , list ):
44
137
return Error (title = default_title , content = {'messages' : obj })
138
+ elif obj is None :
139
+ return Error (title = default_title )
45
140
return Error (title = default_title , content = {'message' : obj })
46
141
47
142
48
- def _get_accept_header (decoders = None ):
49
- if decoders is None :
50
- decoders = default_decoders
143
+ def _decode_result (response , decoders = None ):
144
+ """
145
+ Given an HTTP response, return the decoded Core API document.
146
+ """
147
+ if response .content :
148
+ # Content returned in response. We should decode it.
149
+ content_type = response .headers .get ('content-type' )
150
+ codec = negotiate_decoder (content_type , decoders = decoders )
151
+ result = codec .load (response .content , base_url = response .url )
152
+ else :
153
+ # No content returned in response.
154
+ result = None
155
+
156
+ # Coerce 4xx and 5xx codes into errors.
157
+ is_error = response .status_code >= 400 and response .status_code <= 599
158
+ if is_error and not isinstance (result , Error ):
159
+ result = _coerce_to_error (result , default_title = response .reason )
51
160
52
- return ', ' .join ([decoder .media_type for decoder in decoders ])
161
+ return result
162
+
163
+
164
+ def _handle_inplace_replacements (document , link , link_ancestors ):
165
+ """
166
+ Given a new document, and the link/ancestors it was created,
167
+ determine if we should:
168
+
169
+ * Make an inline replacement and then return the modified document tree.
170
+ * Return the new document as-is.
171
+ """
172
+ if link .inplace is None :
173
+ inplace = link .action .lower () in ('put' , 'patch' , 'delete' )
174
+ else :
175
+ inplace = link .inplace
176
+
177
+ if inplace :
178
+ root = link_ancestors [0 ].document
179
+ keys_to_link_parent = link_ancestors [- 1 ].keys
180
+ if document is None :
181
+ return root .delete_in (keys_to_link_parent )
182
+ return root .set_in (keys_to_link_parent , document )
183
+
184
+ return document
53
185
54
186
55
187
class HTTPTransport (BaseTransport ):
@@ -70,133 +202,17 @@ def headers(self):
70
202
return self ._headers
71
203
72
204
def transition (self , link , params = None , decoders = None , link_ancestors = None ):
73
- method = self .get_http_method (link .action )
74
- path_params , query_params , form_params = self .seperate_params (method , link .fields , params )
75
- url = self .expand_path_params (link .url , path_params )
76
- headers = self .get_headers (url , decoders )
77
- response = self .make_http_request (url , method , headers , query_params , form_params )
78
- document = self .load_document (response , decoders )
79
-
80
- if isinstance (document , Document ) and link_ancestors :
81
- document = self .handle_inplace_replacements (document , link , link_ancestors )
82
-
83
- if isinstance (document , Error ):
84
- raise ErrorMessage (document )
85
-
86
- return document
87
-
88
- def get_http_method (self , action ):
89
- if not action :
90
- return 'GET'
91
- return action .upper ()
92
-
93
- def seperate_params (self , method , fields , params = None ):
94
- """
95
- Seperate the params into their location types: path, query, or form.
96
- """
97
- if params is None :
98
- return ({}, {}, {})
99
-
100
- field_map = {field .name : field for field in fields }
101
- path_params = {}
102
- query_params = {}
103
- form_params = {}
104
- for key , value in params .items ():
105
- if key not in field_map or not field_map [key ].location :
106
- # Default is 'query' for 'GET'/'DELETE', and 'form' others.
107
- location = 'query' if method in ('GET' , 'DELETE' ) else 'form'
108
- else :
109
- location = field_map [key ].location
110
-
111
- if location == 'path' :
112
- path_params [key ] = value
113
- elif location == 'query' :
114
- query_params [key ] = value
115
- else :
116
- form_params [key ] = value
117
-
118
- return path_params , query_params , form_params
119
-
120
- def expand_path_params (self , url , path_params ):
121
- if path_params :
122
- return uritemplate .expand (url , path_params )
123
- return url
124
-
125
- def get_headers (self , url , decoders = None ):
126
- """
127
- Return a dictionary of HTTP headers to use in the outgoing request.
128
- """
129
- headers = {
130
- 'accept' : _get_accept_header (decoders )
131
- }
132
-
133
- if self .credentials :
134
- # Include any authorization credentials relevant to this domain.
135
- url_components = urlparse .urlparse (url )
136
- host = url_components .netloc
137
- if host in self .credentials :
138
- headers ['authorization' ] = self .credentials [host ]
139
-
140
- if self .headers :
141
- # Include any custom headers associated with this transport.
142
- headers .update (self .headers )
143
-
144
- return headers
145
-
146
- def make_http_request (self , url , method , headers = None , query_params = None , form_params = None ):
147
- """
148
- Make an HTTP request and return an HTTP response.
149
- """
150
- opts = {
151
- "headers" : headers or {}
152
- }
153
-
154
- if query_params :
155
- opts ['params' ] = query_params
156
- elif form_params :
157
- opts ['data' ] = json .dumps (form_params )
158
- opts ['headers' ]['content-type' ] = 'application/json'
159
-
160
- return requests .request (method , url , ** opts )
161
-
162
- def load_document (self , response , decoders = None ):
163
- """
164
- Given an HTTP response, return the decoded Core API document.
165
- """
166
- if response .content :
167
- # Content returned in response. We should decode it.
168
- content_type = response .headers .get ('content-type' )
169
- codec = negotiate_decoder (content_type , decoders = decoders )
170
- document = codec .load (response .content , base_url = response .url )
171
- else :
172
- # No content returned in response.
173
- document = None
174
-
175
- # Coerce 4xx and 5xx codes into errors.
176
- is_error = response .status_code >= 400 and response .status_code <= 599
177
- if is_error and not isinstance (document , Error ):
178
- document = _coerce_to_error (document , default_title = response .reason )
179
-
180
- return document
181
-
182
- def handle_inplace_replacements (self , document , link , link_ancestors ):
183
- """
184
- Given a new document, and the link/ancestors it was created,
185
- determine if we should:
186
-
187
- * Make an inline replacement and then return the modified document tree.
188
- * Return the new document as-is.
189
- """
190
- if link .inplace is None :
191
- inplace = link .action .lower () in ('put' , 'patch' , 'delete' )
192
- else :
193
- inplace = link .inplace
205
+ method = _get_http_method (link .action )
206
+ path_params , query_params , form_params = _seperate_params (method , link .fields , params )
207
+ url = _expand_path_params (link .url , path_params )
208
+ headers = _get_headers (url , decoders , self .credentials , self .headers )
209
+ response = _make_http_request (url , method , headers , query_params , form_params )
210
+ result = _decode_result (response , decoders )
211
+
212
+ if isinstance (result , Document ) and link_ancestors :
213
+ result = _handle_inplace_replacements (result , link , link_ancestors )
194
214
195
- if inplace :
196
- root = link_ancestors [0 ].document
197
- keys_to_link_parent = link_ancestors [- 1 ].keys
198
- if document is None :
199
- return root .delete_in (keys_to_link_parent )
200
- return root .set_in (keys_to_link_parent , document )
215
+ if isinstance (result , Error ):
216
+ raise ErrorMessage (result )
201
217
202
- return document
218
+ return result
0 commit comments