From 25a2dbff1f9a82fa0387d1cd6e4e817781187e9a Mon Sep 17 00:00:00 2001 From: Ed Harrod Date: Thu, 23 Oct 2025 10:34:55 +0100 Subject: [PATCH 1/2] client: Raise error for non-JSON-serializable parameters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes issue #69 by providing clear error messages when parameters cannot be serialized to JSON, instead of silently suppressing the error. Changes: - Catch only TypeError instead of all Exceptions when serializing parameters - Raise TypeError with descriptive message for non-serializable types - Add test cases for Decimal and custom objects - Verify None requests still work correctly 🤖 Generated with Claude Code Co-Authored-By: Claude --- luno_python/base_client.py | 9 ++++++--- tests/test_client.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/luno_python/base_client.py b/luno_python/base_client.py index ed7f7f7..631b9eb 100644 --- a/luno_python/base_client.py +++ b/luno_python/base_client.py @@ -70,10 +70,13 @@ def do(self, method, path, req=None, auth=False): :type req: object :type auth: bool """ - try: - params = json.loads(json.dumps(req)) - except Exception: + if req is None: params = None + else: + try: + params = json.loads(json.dumps(req)) + except TypeError as e: + raise TypeError("luno: request parameters must be JSON-serializable: %s" % str(e)) headers = {"User-Agent": self.make_user_agent()} args = dict(timeout=self.timeout, params=params, headers=headers) if auth: diff --git a/tests/test_client.py b/tests/test_client.py index 2b40d14..2ee8885 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,3 +1,5 @@ +from decimal import Decimal + import pytest import requests import requests_mock @@ -262,3 +264,29 @@ def test_get_balances_with_malformed_response(): # Test without account_id on malformed response result = c.get_balances() assert result == MOCK_MALFORMED_RESPONSE + + +def test_client_do_with_non_serializable_params(): + """Test that non-JSON-serializable parameters raise a clear error.""" + c = Client() + c.set_base_url("mock://test/") + + # Test with Decimal (not JSON-serializable) + with pytest.raises(TypeError) as exc_info: + c.do("GET", "/", req={"amount": Decimal("10.5")}) + assert "JSON-serializable" in str(exc_info.value) + + # Test with custom object (not JSON-serializable) + class CustomObject: + pass + + with pytest.raises(TypeError) as exc_info: + c.do("GET", "/", req={"obj": CustomObject()}) + assert "JSON-serializable" in str(exc_info.value) + + # Test with None request (should not raise) + adapter = requests_mock.Adapter() + c.session.mount("mock", adapter) + adapter.register_uri("GET", "mock://test/", json={"result": "ok"}) + result = c.do("GET", "/", req=None) + assert result["result"] == "ok" From 30cc12f93098f7f27052b4528e22fa9dd6121b40 Mon Sep 17 00:00:00 2001 From: Ed Harrod Date: Thu, 23 Oct 2025 12:16:03 +0100 Subject: [PATCH 2/2] client: Preserve original exception chain --- luno_python/base_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/luno_python/base_client.py b/luno_python/base_client.py index 631b9eb..fba61d3 100644 --- a/luno_python/base_client.py +++ b/luno_python/base_client.py @@ -76,7 +76,7 @@ def do(self, method, path, req=None, auth=False): try: params = json.loads(json.dumps(req)) except TypeError as e: - raise TypeError("luno: request parameters must be JSON-serializable: %s" % str(e)) + raise TypeError("luno: request parameters must be JSON-serializable: %s" % str(e)) from e headers = {"User-Agent": self.make_user_agent()} args = dict(timeout=self.timeout, params=params, headers=headers) if auth: