From b08b1353237576922d1abedc89718df36b850e63 Mon Sep 17 00:00:00 2001 From: Terry Tan Date: Tue, 2 Dec 2025 10:39:43 +0800 Subject: [PATCH 1/3] Populate HTTPError with status code and error --- serpapi/exceptions.py | 5 ++++- tests/test_exceptions.py | 18 ++++++++++++++++++ tests/test_integration.py | 12 +++++++++++- 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 tests/test_exceptions.py diff --git a/serpapi/exceptions.py b/serpapi/exceptions.py index f501189..f046d95 100644 --- a/serpapi/exceptions.py +++ b/serpapi/exceptions.py @@ -22,7 +22,10 @@ class SearchIDNotProvided(ValueError, SerpApiError): class HTTPError(requests.exceptions.HTTPError, SerpApiError): """HTTP Error.""" - pass + def __init__(self, http_error_exception: requests.exceptions.HTTPError): + self.status_code = http_error_exception.response.status_code + self.error = http_error_exception.response.json().get("error", None) + super().__init__(*http_error_exception.args, response=http_error_exception.response, request=http_error_exception.request) class HTTPConnectionError(HTTPError, requests.exceptions.ConnectionError, SerpApiError): diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py new file mode 100644 index 0000000..6ea32d3 --- /dev/null +++ b/tests/test_exceptions.py @@ -0,0 +1,18 @@ +import pytest +from unittest.mock import Mock, patch +import requests +import serpapi + + +def test_http_error(): + """Ensure that an HTTPError has the correct status code and error.""" + mock_response = Mock() + mock_response.status_code = 401 + mock_response.json.return_value = { "error": "Invalid API key" } + + requests_error = requests.exceptions.HTTPError(response=mock_response, request=Mock()) + http_error = serpapi.HTTPError(requests_error) + + assert http_error.status_code == 401 + assert http_error.error == "Invalid API key" + assert http_error.response == mock_response \ No newline at end of file diff --git a/tests/test_integration.py b/tests/test_integration.py index 644ef50..0ec9f78 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -26,8 +26,10 @@ def test_account_without_credentials(): def test_account_with_bad_credentials(invalid_key_client): """Ensure that an HTTPError is raised when account is accessed with invalid API Credentials.""" - with pytest.raises(serpapi.HTTPError): + with pytest.raises(serpapi.HTTPError) as exc_info: invalid_key_client.account() + + assert exc_info.value.response.status_code == 401 def test_account_with_credentials(client): @@ -38,6 +40,14 @@ def test_account_with_credentials(client): assert isinstance(account, dict) +def test_search_with_missing_params(client): + with pytest.raises(serpapi.HTTPError) as exc_info: + client.search({ "q": "" }) + + assert exc_info.value.status_code == 400 + assert "Missing query `q` parameter" in exc_info.value.error + + def test_coffee_search(coffee_search): assert isinstance(coffee_search, serpapi.SerpResults) assert hasattr(coffee_search, "__getitem__") From f6a719b8ea3a8a5a2c189e36481ab65158e0e210 Mon Sep 17 00:00:00 2001 From: Terry Tan Date: Tue, 2 Dec 2025 11:06:11 +0800 Subject: [PATCH 2/3] Add error handling example to README --- README.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 668055b..b2bc441 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,29 @@ Let's print the title of the first result, but in a more Pythonic way: The [SerpApi.com API Documentation](https://serpapi.com/search-api) contains a list of all the possible parameters that can be passed to the API. +### Error handling + +Unsuccessful requests raise `serpapi.HTTPError` exception. The returned status code will reflect the sort of error that occurred, please refer to [Status and Error Codes Documentation](https://serpapi.com/api-status-and-error-codes) for more details. + +```python +import serpapi + +client = serpapi.Client(api_key=os.getenv("API_KEY")) + +try: + results = client.search({ + 'engine': 'google', + 'q': 'coffee', + }) +except serpapi.HTTPError as e: + if e.status_code == 401: # Invalid API key + print(e.error) # "Invalid API key. Your API key should be here: https://serpapi.com/manage-api-key" + elif e.status_code == 400: # Missing required parameter + pass + elif e.status_code == 429: # Exceeds the hourly throughput limit OR account run out of searches + pass +``` + ## Documentation Documentation is [available on Read the Docs](https://serpapi-python.readthedocs.io/en/latest/). @@ -339,7 +362,6 @@ results = client.search({ ``` - API Documentation: [serpapi.com/images-results](https://serpapi.com/images-results) - ## License MIT License. From cbd94111689b716a0f22b44e91b188865752862c Mon Sep 17 00:00:00 2001 From: Terry Tan Date: Tue, 2 Dec 2025 11:21:21 +0800 Subject: [PATCH 3/3] Handle edge cases in HTTPError --- serpapi/exceptions.py | 19 +++++++++++++++---- tests/test_exceptions.py | 5 ++--- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/serpapi/exceptions.py b/serpapi/exceptions.py index f046d95..423cb91 100644 --- a/serpapi/exceptions.py +++ b/serpapi/exceptions.py @@ -22,10 +22,21 @@ class SearchIDNotProvided(ValueError, SerpApiError): class HTTPError(requests.exceptions.HTTPError, SerpApiError): """HTTP Error.""" - def __init__(self, http_error_exception: requests.exceptions.HTTPError): - self.status_code = http_error_exception.response.status_code - self.error = http_error_exception.response.json().get("error", None) - super().__init__(*http_error_exception.args, response=http_error_exception.response, request=http_error_exception.request) + def __init__(self, original_exception): + if (isinstance(original_exception, requests.exceptions.HTTPError)): + http_error_exception: requests.exceptions.HTTPError = original_exception + + self.status_code = http_error_exception.response.status_code + try: + self.error = http_error_exception.response.json().get("error", None) + except requests.exceptions.JSONDecodeError: + self.error = None + else: + self.status_code = -1 + self.error = None + + super().__init__(*original_exception.args, response=getattr(original_exception, 'response', None), request=getattr(original_exception, 'request', None)) + class HTTPConnectionError(HTTPError, requests.exceptions.ConnectionError, SerpApiError): diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 6ea32d3..8ff68f8 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -1,5 +1,4 @@ -import pytest -from unittest.mock import Mock, patch +from unittest.mock import Mock import requests import serpapi @@ -15,4 +14,4 @@ def test_http_error(): assert http_error.status_code == 401 assert http_error.error == "Invalid API key" - assert http_error.response == mock_response \ No newline at end of file + assert http_error.response == mock_response