Skip to content

Commit

Permalink
Merge pull request #316 from ansible-collections/json_decoder_hook
Browse files Browse the repository at this point in the history
Allow passing a object_hook to rest client
  • Loading branch information
tupyy authored Mar 18, 2024
2 parents 7767f69 + 4daa68e commit 070b91f
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 4 deletions.
3 changes: 3 additions & 0 deletions changelogs/fragments/client_object_hook.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
minor_changes:
- client - allow user to pass a `object_hook` function to rest client for custom decoding of the json response(https://github.com/ansible-collections/servicenow.itsm/pull/316).
18 changes: 14 additions & 4 deletions plugins/module_utils/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@


class Response:
def __init__(self, status, data, headers=None):
def __init__(self, status, data, headers=None, json_decoder_hook=None):
self.status = status
self.data = data
# [('h1', 'v1'), ('H2', 'V2')] -> {'h1': 'v1', 'h2': 'V2'}
Expand All @@ -29,12 +29,13 @@ def __init__(self, status, data, headers=None):
)

self._json = None
self.json_decoder_hook = json_decoder_hook

@property
def json(self):
if self._json is None:
try:
self._json = json.loads(self.data)
self._json = json.loads(self.data, object_hook=self.json_decoder_hook)
except ValueError:
raise ServiceNowError(
"Received invalid JSON response: {0}".format(self.data)
Expand All @@ -57,6 +58,7 @@ def __init__(
api_path="api/now",
timeout=None,
validate_certs=None,
json_decoder_hook=None,
):
if not (host or "").startswith(("https://", "http://")):
raise ServiceNowError(
Expand All @@ -77,6 +79,7 @@ def __init__(
self.access_token = access_token
self.timeout = timeout
self.validate_certs = validate_certs
self.json_decoder_hook = json_decoder_hook

self._auth_header = None
self._client = Request()
Expand Down Expand Up @@ -158,8 +161,15 @@ def _request(self, method, path, data=None, headers=None):
raise ServiceNowError(e.reason)

if PY2:
return Response(raw_resp.getcode(), raw_resp.read(), raw_resp.info())
return Response(raw_resp.status, raw_resp.read(), raw_resp.headers)
return Response(
raw_resp.getcode(),
raw_resp.read(),
raw_resp.info(),
self.json_decoder_hook,
)
return Response(
raw_resp.status, raw_resp.read(), raw_resp.headers, self.json_decoder_hook
)

def request(self, method, path, query=None, data=None, headers=None, bytes=None):
# Make sure we only have one kind of payload
Expand Down
30 changes: 30 additions & 0 deletions tests/unit/plugins/module_utils/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@
)


def custom_decoder_hook(dct):
if "result" in dct:
if not isinstance(dct["result"], list):
return dct

item = dct["result"]
for k in item:
if "__meta" in k:
item.remove(k)
return dct


class TestResponseInit:
@pytest.mark.parametrize(
"raw_headers,expected_headers",
Expand Down Expand Up @@ -67,6 +79,24 @@ def test_json_is_cached(self, mocker):

assert json_mock.loads.call_count == 1

def test_custom_decoder_hook(self):
resp = client.Response(
200,
'{"result": [{"__meta": {"encodedQuery": "some_query"}}]}',
headers=[("Content-type", "application/json")],
json_decoder_hook=custom_decoder_hook,
)
assert resp.json == {"result": []}

def test_custom_decoder_hook_with_values(self):
resp = client.Response(
200,
'{"result": [{"some_obj": "value"},{"__meta": {"encodedQuery": "some_query"}}]}',
headers=[("Content-type", "application/json")],
json_decoder_hook=custom_decoder_hook,
)
assert resp.json == {"result": [{"some_obj": "value"}]}


class TestClientInit:
@pytest.mark.parametrize("host", [None, "", "invalid", "missing.schema"])
Expand Down

0 comments on commit 070b91f

Please sign in to comment.