Skip to content

Commit 77627c5

Browse files
authored
Merge pull request #480 from fastai/print-body
add body to error message
2 parents be8dcd6 + 8ea415a commit 77627c5

File tree

2 files changed

+86
-27
lines changed

2 files changed

+86
-27
lines changed

fastcore/net.py

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@
1717
# %% ../nbs/03b_net.ipynb 1
1818
from .utils import *
1919
from .parallel import *
20-
from functools import wraps
2120

21+
from functools import wraps
2222
import json,urllib,contextlib
2323
import socket,urllib.request,http,urllib
2424
from contextlib import contextmanager,ExitStack
25-
from urllib.request import Request,urlretrieve,install_opener
25+
from urllib.request import Request,urlretrieve,install_opener,HTTPErrorProcessor
2626
from urllib.error import HTTPError,URLError
2727
from urllib.parse import urlencode,urlparse,urlunparse
2828
from http.client import InvalidURL
@@ -104,28 +104,31 @@ def urlopen(url, data=None, headers=None, timeout=None, **kwargs):
104104
if data is not None:
105105
if not isinstance(data, (str,bytes)): data = urlencode(data)
106106
if not isinstance(data, bytes): data = data.encode('ascii')
107-
return urlopener().open(urlwrap(url, data=data, headers=headers), timeout=timeout)
107+
try: return urlopener().open(urlwrap(url, data=data, headers=headers), timeout=timeout)
108+
except HTTPError as e:
109+
e.msg += f"\n====Error Body====\n{e.read().decode(errors='ignore')}"
110+
raise
108111

109-
# %% ../nbs/03b_net.ipynb 18
112+
# %% ../nbs/03b_net.ipynb 20
110113
def urlread(url, data=None, headers=None, decode=True, return_json=False, return_headers=False, timeout=None, **kwargs):
111114
"Retrieve `url`, using `data` dict or `kwargs` to `POST` if present"
112115
try:
113116
with urlopen(url, data=data, headers=headers, timeout=timeout, **kwargs) as u: res,hdrs = u.read(),u.headers
114117
except HTTPError as e:
115-
if 400 <= e.code < 500: raise ExceptionsHTTP[e.code](e.url, e.hdrs, e.fp) from None
118+
if 400 <= e.code < 500: raise ExceptionsHTTP[e.code](e.url, e.hdrs, e.fp, msg=e.msg) from None
116119
else: raise
117120

118121
if decode: res = res.decode()
119122
if return_json: res = loads(res)
120123
return (res,dict(hdrs)) if return_headers else res
121124

122-
# %% ../nbs/03b_net.ipynb 19
125+
# %% ../nbs/03b_net.ipynb 21
123126
def urljson(url, data=None, timeout=None):
124127
"Retrieve `url` and decode json"
125128
res = urlread(url, data=data, timeout=timeout)
126129
return json.loads(res) if res else {}
127130

128-
# %% ../nbs/03b_net.ipynb 21
131+
# %% ../nbs/03b_net.ipynb 23
129132
def urlcheck(url, timeout=10):
130133
if not url: return True
131134
try:
@@ -134,12 +137,12 @@ def urlcheck(url, timeout=10):
134137
except socket.timeout: return False
135138
except InvalidURL: return False
136139

137-
# %% ../nbs/03b_net.ipynb 22
140+
# %% ../nbs/03b_net.ipynb 24
138141
def urlclean(url):
139142
"Remove fragment, params, and querystring from `url` if present"
140143
return urlunparse(urlparse(str(url))[:3]+('','',''))
141144

142-
# %% ../nbs/03b_net.ipynb 24
145+
# %% ../nbs/03b_net.ipynb 26
143146
def urlretrieve(url, filename=None, reporthook=None, data=None, timeout=None):
144147
"Same as `urllib.request.urlretrieve` but also works with `Request` objects"
145148
with contextlib.closing(urlopen(url, data, timeout=timeout)) as fp:
@@ -165,43 +168,43 @@ def urlretrieve(url, filename=None, reporthook=None, data=None, timeout=None):
165168
raise ContentTooShortError(f"retrieval incomplete: got only {read} out of {size} bytes", headers)
166169
return filename,headers
167170

168-
# %% ../nbs/03b_net.ipynb 25
171+
# %% ../nbs/03b_net.ipynb 27
169172
def urldest(url, dest=None):
170173
name = urlclean(Path(url).name)
171174
if dest is None: dest = name
172175
dest = Path(dest)
173176
return dest/name if dest.is_dir() else dest
174177

175-
# %% ../nbs/03b_net.ipynb 26
178+
# %% ../nbs/03b_net.ipynb 28
176179
def urlsave(url, dest=None, reporthook=None, timeout=None):
177180
"Retrieve `url` and save based on its name"
178181
dest = urldest(url, dest)
179182
dest.parent.mkdir(parents=True, exist_ok=True)
180183
nm,msg = urlretrieve(url, dest, reporthook, timeout=timeout)
181184
return nm
182185

183-
# %% ../nbs/03b_net.ipynb 28
186+
# %% ../nbs/03b_net.ipynb 30
184187
def urlvalid(x):
185188
"Test if `x` is a valid URL"
186189
return all (getattrs(urlparse(str(x)), 'scheme', 'netloc'))
187190

188-
# %% ../nbs/03b_net.ipynb 30
191+
# %% ../nbs/03b_net.ipynb 32
189192
def urlrequest(url, verb, headers=None, route=None, query=None, data=None, json_data=True):
190193
"`Request` for `url` with optional route params replaced by `route`, plus `query` string, and post `data`"
191194
if route: url = url.format(**route)
192195
if query: url += '?' + urlencode(query)
193196
if isinstance(data,dict): data = (json.dumps if json_data else urlencode)(data).encode('ascii')
194197
return Request(url, headers=headers or {}, data=data or None, method=verb.upper())
195198

196-
# %% ../nbs/03b_net.ipynb 33
199+
# %% ../nbs/03b_net.ipynb 35
197200
@patch
198201
def summary(self:Request, skip=None)->dict:
199202
"Summary containing full_url, headers, method, and data, removing `skip` from headers"
200203
res = L('full_url','method','data').map_dict(partial(getattr,self))
201204
res['headers'] = {k:v for k,v in self.headers.items() if k not in listify(skip)}
202205
return res
203206

204-
# %% ../nbs/03b_net.ipynb 35
207+
# %% ../nbs/03b_net.ipynb 37
205208
def urlsend(url, verb, headers=None, route=None, query=None, data=None, json_data=True,
206209
return_json=True, return_headers=False, debug=None):
207210
"Send request with `urlrequest`, converting result to json if `return_json`"
@@ -213,7 +216,7 @@ def urlsend(url, verb, headers=None, route=None, query=None, data=None, json_dat
213216

214217
return urlread(req, return_json=return_json, return_headers=return_headers)
215218

216-
# %% ../nbs/03b_net.ipynb 36
219+
# %% ../nbs/03b_net.ipynb 38
217220
def do_request(url, post=False, headers=None, **data):
218221
"Call GET or json-encoded POST on `url`, depending on `post`"
219222
if data:
@@ -223,13 +226,13 @@ def do_request(url, post=False, headers=None, **data):
223226
data = None
224227
return urljson(Request(url, headers=headers, data=data or None))
225228

226-
# %% ../nbs/03b_net.ipynb 37
229+
# %% ../nbs/03b_net.ipynb 39
227230
def _socket_det(port,host,dgram):
228231
if isinstance(port,int): family,addr = socket.AF_INET,(host or socket.gethostname(),port)
229232
else: family,addr = socket.AF_UNIX,port
230233
return family,addr,(socket.SOCK_STREAM,socket.SOCK_DGRAM)[dgram]
231234

232-
# %% ../nbs/03b_net.ipynb 38
235+
# %% ../nbs/03b_net.ipynb 40
233236
def start_server(port, host=None, dgram=False, reuse_addr=True, n_queue=None):
234237
"Create a `socket` server on `port`, with optional `host`, of type `dgram`"
235238
listen_args = [n_queue] if n_queue else []
@@ -243,7 +246,7 @@ def start_server(port, host=None, dgram=False, reuse_addr=True, n_queue=None):
243246
s.listen(*listen_args)
244247
return s
245248

246-
# %% ../nbs/03b_net.ipynb 40
249+
# %% ../nbs/03b_net.ipynb 42
247250
def start_client(port, host=None, dgram=False):
248251
"Create a `socket` client on `port`, with optional `host`, of type `dgram`"
249252
family,addr,typ = _socket_det(port,host,dgram)

nbs/03b_net.ipynb

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@
1818
"#|export\n",
1919
"from fastcore.utils import *\n",
2020
"from fastcore.parallel import *\n",
21-
"from functools import wraps\n",
2221
"\n",
22+
"from functools import wraps\n",
2323
"import json,urllib,contextlib\n",
2424
"import socket,urllib.request,http,urllib\n",
2525
"from contextlib import contextmanager,ExitStack\n",
26-
"from urllib.request import Request,urlretrieve,install_opener\n",
26+
"from urllib.request import Request,urlretrieve,install_opener,HTTPErrorProcessor\n",
2727
"from urllib.error import HTTPError,URLError\n",
2828
"from urllib.parse import urlencode,urlparse,urlunparse\n",
2929
"from http.client import InvalidURL"
@@ -184,14 +184,24 @@
184184
"text/markdown": [
185185
"---\n",
186186
"\n",
187-
"### HTTP4xxClientError\n",
187+
"[source](https://github.com/fastai/fastcore/blob/master/fastcore/net.py#L63){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
188+
"\n",
189+
"#### HTTP4xxClientError\n",
188190
"\n",
189191
"> HTTP4xxClientError (url, code, msg, hdrs, fp)\n",
190192
"\n",
191193
"Base class for client exceptions (code 4xx) from `url*` functions"
192194
],
193195
"text/plain": [
194-
"<nbdev.showdoc.BasicMarkdownRenderer>"
196+
"---\n",
197+
"\n",
198+
"[source](https://github.com/fastai/fastcore/blob/master/fastcore/net.py#L63){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
199+
"\n",
200+
"#### HTTP4xxClientError\n",
201+
"\n",
202+
"> HTTP4xxClientError (url, code, msg, hdrs, fp)\n",
203+
"\n",
204+
"Base class for client exceptions (code 4xx) from `url*` functions"
195205
]
196206
},
197207
"execution_count": null,
@@ -213,14 +223,24 @@
213223
"text/markdown": [
214224
"---\n",
215225
"\n",
216-
"### HTTP5xxServerError\n",
226+
"[source](https://github.com/fastai/fastcore/blob/master/fastcore/net.py#L68){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
227+
"\n",
228+
"#### HTTP5xxServerError\n",
217229
"\n",
218230
"> HTTP5xxServerError (url, code, msg, hdrs, fp)\n",
219231
"\n",
220232
"Base class for server exceptions (code 5xx) from `url*` functions"
221233
],
222234
"text/plain": [
223-
"<nbdev.showdoc.BasicMarkdownRenderer>"
235+
"---\n",
236+
"\n",
237+
"[source](https://github.com/fastai/fastcore/blob/master/fastcore/net.py#L68){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
238+
"\n",
239+
"#### HTTP5xxServerError\n",
240+
"\n",
241+
"> HTTP5xxServerError (url, code, msg, hdrs, fp)\n",
242+
"\n",
243+
"Base class for server exceptions (code 5xx) from `url*` functions"
224244
]
225245
},
226246
"execution_count": null,
@@ -293,7 +313,43 @@
293313
" if data is not None:\n",
294314
" if not isinstance(data, (str,bytes)): data = urlencode(data)\n",
295315
" if not isinstance(data, bytes): data = data.encode('ascii')\n",
296-
" return urlopener().open(urlwrap(url, data=data, headers=headers), timeout=timeout)"
316+
" try: return urlopener().open(urlwrap(url, data=data, headers=headers), timeout=timeout)\n",
317+
" except HTTPError as e: \n",
318+
" e.msg += f\"\\n====Error Body====\\n{e.read().decode(errors='ignore')}\"\n",
319+
" raise"
320+
]
321+
},
322+
{
323+
"cell_type": "markdown",
324+
"metadata": {},
325+
"source": [
326+
"With `urlopen`, the body of the response will also be returned in addition to the message if there is an error:"
327+
]
328+
},
329+
{
330+
"cell_type": "code",
331+
"execution_count": null,
332+
"metadata": {},
333+
"outputs": [
334+
{
335+
"name": "stdout",
336+
"output_type": "stream",
337+
"text": [
338+
"404 Not Found\n",
339+
"====Error Body====\n",
340+
"{\n",
341+
" \"message\": \"Not Found\",\n",
342+
" \"documentation_url\": \"https://docs.github.com/rest\"\n",
343+
"}\n",
344+
"\n"
345+
]
346+
}
347+
],
348+
"source": [
349+
"try: urlopen('https://api.github.com/v3')\n",
350+
"except HTTPError as e: \n",
351+
" print(e.code, e.msg)\n",
352+
" assert 'documentation_url' in e.msg"
297353
]
298354
},
299355
{
@@ -308,7 +364,7 @@
308364
" try:\n",
309365
" with urlopen(url, data=data, headers=headers, timeout=timeout, **kwargs) as u: res,hdrs = u.read(),u.headers\n",
310366
" except HTTPError as e:\n",
311-
" if 400 <= e.code < 500: raise ExceptionsHTTP[e.code](e.url, e.hdrs, e.fp) from None\n",
367+
" if 400 <= e.code < 500: raise ExceptionsHTTP[e.code](e.url, e.hdrs, e.fp, msg=e.msg) from None\n",
312368
" else: raise\n",
313369
"\n",
314370
" if decode: res = res.decode()\n",

0 commit comments

Comments
 (0)