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. diff --git a/serpapi/exceptions.py b/serpapi/exceptions.py index f501189..423cb91 100644 --- a/serpapi/exceptions.py +++ b/serpapi/exceptions.py @@ -22,7 +22,21 @@ class SearchIDNotProvided(ValueError, SerpApiError): class HTTPError(requests.exceptions.HTTPError, SerpApiError): """HTTP Error.""" - pass + 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 new file mode 100644 index 0000000..8ff68f8 --- /dev/null +++ b/tests/test_exceptions.py @@ -0,0 +1,17 @@ +from unittest.mock import Mock +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 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__")