From 94de078246b67d4528ba4c2b4fb38c7f6215ee69 Mon Sep 17 00:00:00 2001 From: Luis Merino Date: Thu, 15 Jan 2026 17:50:32 +0100 Subject: [PATCH] chore(sdk): Add status_code to ApiError when available from response --- src/thecompaniesapi/sdk.py | 26 ++++++++++++++++++++++---- tests/test_client.py | 35 +++++++++++++++++++++++++++++++++-- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/thecompaniesapi/sdk.py b/src/thecompaniesapi/sdk.py index c8674a9..1076e1a 100644 --- a/src/thecompaniesapi/sdk.py +++ b/src/thecompaniesapi/sdk.py @@ -133,8 +133,7 @@ def _make_request( return {'data': response.text, 'status': response.status_code} except requests.exceptions.RequestException as e: - # Handle request errors - raise ApiError(f"Request failed: {str(e)}") from e + raise ApiError.from_request_exception(e, f"Request failed: {str(e)}") from e def get( self, @@ -186,8 +185,27 @@ def delete( class ApiError(Exception): - """Custom exception for API errors.""" - pass + """ + Custom exception for API errors. + """ + def __init__(self, message: str, status_code: int | None = None) -> None: + super().__init__(message) + self.status_code = status_code + + @classmethod + def from_request_exception(cls, e: ApiError, message: str) -> "ApiError": + status_code = None + cause = e.__cause__ + if isinstance(cause, requests.HTTPError) and cause.response is not None: + status_code = cause.response.status_code + elif isinstance(cause, requests.exceptions.RetryError): + # Retry exhausted - try to extract status from message + # e.g., "too many 429 error responses" + import re + + if match := re.search(r"(\d{3})", str(cause)): + status_code = int(match.group(1)) + return cls(message, status_code) class Client: diff --git a/tests/test_client.py b/tests/test_client.py index 22b52af..2183e89 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,6 +1,8 @@ import json import pytest import responses +import requests + from unittest.mock import Mock, patch from src.thecompaniesapi import Client, HttpClient, ApiError @@ -154,8 +156,26 @@ def test_request_error_handling(self): client = HttpClient(api_token="test-token") - with pytest.raises(ApiError, match="Request failed"): + with pytest.raises(ApiError, match="Request failed") as exc_info: + client.get("/v2/error") + assert exc_info.value.status_code == 404 + + @responses.activate + def test_request_error_timeout_handling(self): + """Test error handling for HTTP errors.""" + responses.add( + responses.GET, + "https://api.thecompaniesapi.com/v2/error", + body=requests.exceptions.Timeout() # Simulate a request failure + ) + + client = HttpClient(api_token="test-token") + + try: client.get("/v2/error") + except Exception as e: + assert e.status_code is None + @responses.activate def test_non_json_response(self): @@ -255,4 +275,15 @@ def test_api_error_creation(self): assert str(error) == "Test error message" with pytest.raises(ApiError, match="Test error message"): - raise error + raise error + + + def test_api_error_status_code(self): + """Test ApiError with status code attribute.""" + error = ApiError("Error with status", status_code=404) + assert str(error) == "Error with status" + assert error.status_code == 404 + + with pytest.raises(ApiError) as exc_info: + raise error + assert exc_info.value.status_code == 404 \ No newline at end of file