Skip to content

Commit

Permalink
Set up json fix option 2
Browse files Browse the repository at this point in the history
  • Loading branch information
steveberdy committed Jul 2, 2021
1 parent 1466ad7 commit 5ff9fb7
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 34 deletions.
4 changes: 1 addition & 3 deletions docs/user/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,7 @@ There's also a builtin JSON decoder, in case you're dealing with JSON data::

In case the JSON decoding fails, ``r.json()`` raises an exception. For example, if
the response gets a 204 (No Content), or if the response contains invalid JSON,
attempting ``r.json()`` raises ``simplejson.JSONDecodeError`` if simplejson is
installed or raises ``ValueError: No JSON object could be decoded`` on Python 2 or
``json.JSONDecodeError`` on Python 3.
attempting ``r.json()`` raises ``requests.JSONDecodeError``.

It should be noted that the success of the call to ``r.json()`` does **not**
indicate the success of the response. Some servers may return a JSON object in a
Expand Down
2 changes: 1 addition & 1 deletion requests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def _check_cryptography(cryptography_version):
from .exceptions import (
RequestException, Timeout, URLRequired,
TooManyRedirects, HTTPError, ConnectionError,
FileModeWarning, ConnectTimeout, ReadTimeout
FileModeWarning, ConnectTimeout, ReadTimeout, JSONDecodeError
)

# Set default logging handler to avoid "No handler found" warnings.
Expand Down
6 changes: 1 addition & 5 deletions requests/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
Python 3.
"""

import json
import chardet

import sys
Expand All @@ -25,10 +26,6 @@
#: Python 3.x?
is_py3 = (_ver[0] == 3)

try:
import simplejson as json
except ImportError:
import json

# ---------
# Specifics
Expand All @@ -46,7 +43,6 @@
# Keep OrderedDict for backwards compatibility.
from collections import Callable, Mapping, MutableMapping, OrderedDict


builtin_str = str
bytes = str
str = unicode
Expand Down
6 changes: 6 additions & 0 deletions requests/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
This module contains the set of Requests' exceptions.
"""
from urllib3.exceptions import HTTPError as BaseHTTPError
import json
import simplejson


class RequestException(IOError):
Expand All @@ -25,6 +27,10 @@ def __init__(self, *args, **kwargs):
super(RequestException, self).__init__(*args, **kwargs)


class JSONDecodeError(json.JSONDecodeError, simplejson.JSONDecodeError):
"""Couldn't decode the text into json"""


class InvalidJSONError(RequestException):
"""A JSON error occurred."""

Expand Down
62 changes: 37 additions & 25 deletions requests/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
from .cookies import cookiejar_from_dict, get_cookie_header, _copy_cookie_jar
from .exceptions import (
HTTPError, MissingSchema, InvalidURL, ChunkedEncodingError,
ContentDecodingError, ConnectionError, StreamConsumedError, InvalidJSONError)
ContentDecodingError, ConnectionError, StreamConsumedError,
InvalidJSONError, JSONDecodeError)
from ._internal_utils import to_native_string, unicode_is_ascii
from .utils import (
guess_filename, get_auth_from_url, requote_uri,
Expand All @@ -39,7 +40,7 @@
Callable, Mapping,
cookielib, urlunparse, urlsplit, urlencode, str, bytes,
is_py2, chardet, builtin_str, basestring)
from .compat import json as complexjson
from .compat import json
from .status_codes import codes

#: The set of HTTP status codes that indicate an automatically
Expand Down Expand Up @@ -176,12 +177,14 @@ def register_hook(self, event, hook):
"""Properly register a hook."""

if event not in self.hooks:
raise ValueError('Unsupported event specified, with event name "%s"' % (event))
raise ValueError(
'Unsupported event specified, with event name "%s"' % (event))

if isinstance(hook, Callable):
self.hooks[event].append(hook)
elif hasattr(hook, '__iter__'):
self.hooks[event].extend(h for h in hook if isinstance(h, Callable))
self.hooks[event].extend(
h for h in hook if isinstance(h, Callable))

def deregister_hook(self, event, hook):
"""Deregister a previously registered hook.
Expand Down Expand Up @@ -224,8 +227,8 @@ class Request(RequestHooksMixin):
"""

def __init__(self,
method=None, url=None, headers=None, files=None, data=None,
params=None, auth=None, cookies=None, hooks=None, json=None):
method=None, url=None, headers=None, files=None, data=None,
params=None, auth=None, cookies=None, hooks=None, json=None):

# Default empty dicts for dict params.
data = [] if data is None else data
Expand Down Expand Up @@ -308,8 +311,8 @@ def __init__(self):
self._body_position = None

def prepare(self,
method=None, url=None, headers=None, files=None, data=None,
params=None, auth=None, cookies=None, hooks=None, json=None):
method=None, url=None, headers=None, files=None, data=None,
params=None, auth=None, cookies=None, hooks=None, json=None):
"""Prepares the entire request with the given parameters."""

self.prepare_method(method)
Expand Down Expand Up @@ -384,7 +387,8 @@ def prepare_url(self, url, params):
raise InvalidURL(*e.args)

if not scheme:
error = ("Invalid URL {0!r}: No schema supplied. Perhaps you meant http://{0}?")
error = (
"Invalid URL {0!r}: No schema supplied. Perhaps you meant http://{0}?")
error = error.format(to_native_string(url, 'utf8'))

raise MissingSchema(error)
Expand Down Expand Up @@ -438,7 +442,8 @@ def prepare_url(self, url, params):
else:
query = enc_params

url = requote_uri(urlunparse([scheme, netloc, path, None, query, fragment]))
url = requote_uri(urlunparse(
[scheme, netloc, path, None, query, fragment]))
self.url = url

def prepare_headers(self, headers):
Expand Down Expand Up @@ -468,9 +473,9 @@ def prepare_body(self, data, files, json=None):
content_type = 'application/json'

try:
body = complexjson.dumps(json, allow_nan=False)
body = json.dumps(json, allow_nan=False)
except ValueError as ve:
raise InvalidJSONError(ve, request=self)
raise InvalidJSONError(ve, request=self)

if not isinstance(body, bytes):
body = body.encode('utf-8')
Expand Down Expand Up @@ -500,7 +505,8 @@ def prepare_body(self, data, files, json=None):
self._body_position = object()

if files:
raise NotImplementedError('Streamed bodies and files are mutually exclusive.')
raise NotImplementedError(
'Streamed bodies and files are mutually exclusive.')

if length:
self.headers['Content-Length'] = builtin_str(length)
Expand Down Expand Up @@ -776,7 +782,8 @@ def generate():
if self._content_consumed and isinstance(self._content, bool):
raise StreamConsumedError()
elif chunk_size is not None and not isinstance(chunk_size, int):
raise TypeError("chunk_size must be an int, it is instead a %s." % type(chunk_size))
raise TypeError(
"chunk_size must be an int, it is instead a %s." % type(chunk_size))
# simulate reading small chunks of the content
reused_chunks = iter_slices(self._content, chunk_size)

Expand Down Expand Up @@ -833,7 +840,8 @@ def content(self):
if self.status_code == 0 or self.raw is None:
self._content = None
else:
self._content = b''.join(self.iter_content(CONTENT_CHUNK_SIZE)) or b''
self._content = b''.join(
self.iter_content(CONTENT_CHUNK_SIZE)) or b''

self._content_consumed = True
# don't need to release the connection; that's been handled by urllib3
Expand Down Expand Up @@ -882,12 +890,8 @@ def json(self, **kwargs):
r"""Returns the json-encoded content of a response, if any.
:param \*\*kwargs: Optional arguments that ``json.loads`` takes.
:raises simplejson.JSONDecodeError: If the response body does not
contain valid json and simplejson is installed.
:raises json.JSONDecodeError: If the response body does not contain
valid json and simplejson is not installed on Python 3.
:raises ValueError: If the response body does not contain valid
json and simplejson is not installed on Python 2.
:raises requests.JSONDecodeError: If the response body does not
contain valid json.
"""

if not self.encoding and self.content and len(self.content) > 3:
Expand All @@ -898,7 +902,7 @@ def json(self, **kwargs):
encoding = guess_json_utf(self.content)
if encoding is not None:
try:
return complexjson.loads(
return json.loads(
self.content.decode(encoding), **kwargs
)
except UnicodeDecodeError:
Expand All @@ -907,7 +911,13 @@ def json(self, **kwargs):
# and the server didn't bother to tell us what codec *was*
# used.
pass
return complexjson.loads(self.text, **kwargs)

try:
return json.loads(self.text, **kwargs)
except json.JSONDecodeError as e:
# Catch all errors and raise as requests.JSONDecodeError
# This aliases json.JSONDecodeError and simplejson.JSONDecodeError
raise JSONDecodeError(e.msg, e.doc, e.pos)

@property
def links(self):
Expand Down Expand Up @@ -944,10 +954,12 @@ def raise_for_status(self):
reason = self.reason

if 400 <= self.status_code < 500:
http_error_msg = u'%s Client Error: %s for url: %s' % (self.status_code, reason, self.url)
http_error_msg = u'%s Client Error: %s for url: %s' % (
self.status_code, reason, self.url)

elif 500 <= self.status_code < 600:
http_error_msg = u'%s Server Error: %s for url: %s' % (self.status_code, reason, self.url)
http_error_msg = u'%s Server Error: %s for url: %s' % (
self.status_code, reason, self.url)

if http_error_msg:
raise HTTPError(http_error_msg, response=self)
Expand Down
22 changes: 22 additions & 0 deletions tests/test_json.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import pytest
import simplejson
import json

from requests import get, JSONDecodeError

success_url = "https://httpbin.org/get"
failure_url = "https://google.com"


def test_json_decode_success():
assert isinstance(get(success_url).json(), dict)


def test_json_decode_failure_normal_catch():
with pytest.raises(json.JSONDecodeError):
get(failure_url).json()


def test_json_decode_failure_simplejson_catch():
with pytest.raises(simplejson.JSONDecodeError):
get(failure_url).json()

0 comments on commit 5ff9fb7

Please sign in to comment.