1212import uritemplate
1313
1414
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+
15103def _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+ """
19109 if isinstance (node , (Document , Object )):
20110 # Strip Links from Documents, treat Documents as plain dicts.
21111 return OrderedDict ([
@@ -33,6 +123,9 @@ def _coerce_to_error_content(node):
33123
34124
35125def _coerce_to_error (obj , default_title ):
126+ """
127+ Given an arbitrary return result, coerce it into an Error instance.
128+ """
36129 if isinstance (obj , Document ):
37130 return Error (
38131 title = obj .title or default_title ,
@@ -42,14 +135,53 @@ def _coerce_to_error(obj, default_title):
42135 return Error (title = default_title , content = obj )
43136 elif isinstance (obj , list ):
44137 return Error (title = default_title , content = {'messages' : obj })
138+ elif obj is None :
139+ return Error (title = default_title )
45140 return Error (title = default_title , content = {'message' : obj })
46141
47142
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 )
51160
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
53185
54186
55187class HTTPTransport (BaseTransport ):
@@ -70,133 +202,17 @@ def headers(self):
70202 return self ._headers
71203
72204 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 )
194214
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 )
201217
202- return document
218+ return result
0 commit comments