diff --git a/linkup/_version.py b/linkup/_version.py index fe404ae..01ef120 100644 --- a/linkup/_version.py +++ b/linkup/_version.py @@ -1 +1 @@ -__version__ = "0.2.5" +__version__ = "0.2.6" diff --git a/linkup/client.py b/linkup/client.py index 868e0e5..7a37980 100644 --- a/linkup/client.py +++ b/linkup/client.py @@ -12,6 +12,7 @@ LinkupInsufficientCreditError, LinkupInvalidRequestError, LinkupNoResultError, + LinkupTooManyRequestsError, LinkupUnknownError, ) from linkup.types import LinkupSearchResults, LinkupSourcedAnswer @@ -268,11 +269,26 @@ def _raise_linkup_error(self, response: httpx.Response) -> None: f"Original error message: {error_msg}." ) elif response.status_code == 429: - raise LinkupInsufficientCreditError( - "The Linkup API returned an insufficient credit error (429). Make sure " - "you haven't exhausted your credits.\n" - f"Original error message: {error_msg}." - ) + if code == "INSUFFICIENT_FUNDS_CREDITS": + raise LinkupInsufficientCreditError( + "The Linkup API returned an insufficient credit error (429). Make sure " + "you haven't exhausted your credits.\n" + f"Original error message: {error_msg}." + ) + elif code == "TOO_MANY_REQUESTS": + raise LinkupTooManyRequestsError( + "The Linkup API returned a too many requests error (429). Make sure " + "you not sending too many requests at a time.\n" + f"Original error message: {error_msg}." + ) + else: + raise LinkupUnknownError( + "The Linkup API returned an invalid request error (429). Make sure the " + "parameters you used are valid (correct values, types, mandatory " + "parameters, etc.) and you are using the latest version of the Python " + "SDK.\n" + f"Original error message: {error_msg}." + ) else: raise LinkupUnknownError( f"The Linkup API returned an unknown error ({response.status_code}).\n" diff --git a/linkup/errors.py b/linkup/errors.py index a0ceaf7..eb5f71f 100644 --- a/linkup/errors.py +++ b/linkup/errors.py @@ -35,6 +35,15 @@ class LinkupInsufficientCreditError(Exception): pass +class LinkupTooManyRequestsError(Exception): + """Too many requests error, raised when the Linkup API returns a 429 status code. + + It is returned when you are sending too many requests at a time. + """ + + pass + + class LinkupUnknownError(Exception): """Unknown error, raised when the Linkup API returns an unknown status code.""" diff --git a/tests/unit_tests/client_test.py b/tests/unit_tests/client_test.py index 3f98661..a74b48d 100644 --- a/tests/unit_tests/client_test.py +++ b/tests/unit_tests/client_test.py @@ -15,7 +15,11 @@ LinkupSourcedAnswer, LinkupUnknownError, ) -from linkup.errors import LinkupInsufficientCreditError, LinkupNoResultError +from linkup.errors import ( + LinkupInsufficientCreditError, + LinkupNoResultError, + LinkupTooManyRequestsError, +) from linkup.types import LinkupSearchImageResult, LinkupSearchTextResult @@ -221,7 +225,7 @@ def test_search_insufficient_credit_error(mocker: MockerFixture, client: LinkupC mock_response.json.return_value = { "statusCode": 429, "error": { - "code": "INSUFFICIENT_CREDITS", + "code": "INSUFFICIENT_FUNDS_CREDITS", "message": "You do not have enough credits to perform this request.", "details": [], }, @@ -236,6 +240,48 @@ def test_search_insufficient_credit_error(mocker: MockerFixture, client: LinkupC client.search(query="foo", depth="standard", output_type="searchResults") +def test_search_too_many_requests_error(mocker: MockerFixture, client: LinkupClient) -> None: + mock_response = mocker.Mock() + mock_response.status_code = 429 + mock_response.json.return_value = { + "statusCode": 429, + "error": { + "code": "TOO_MANY_REQUESTS", + "message": "Too many requests.", + "details": [], + }, + } + + mocker.patch( + "linkup.client.LinkupClient._request", + return_value=mock_response, + ) + + with pytest.raises(LinkupTooManyRequestsError): + client.search(query="foo", depth="standard", output_type="searchResults") + + +def test_search_error_429_unknown_code(mocker: MockerFixture, client: LinkupClient) -> None: + mock_response = mocker.Mock() + mock_response.status_code = 429 + mock_response.json.return_value = { + "statusCode": 429, + "error": { + "code": "FOOBAR", + "message": "Foobar", + "details": [], + }, + } + + mocker.patch( + "linkup.client.LinkupClient._request", + return_value=mock_response, + ) + + with pytest.raises(LinkupUnknownError): + client.search(query="foo", depth="standard", output_type="searchResults") + + def test_search_structured_search_invalid_request( mocker: MockerFixture, client: LinkupClient, @@ -543,7 +589,7 @@ async def test_async_search_insufficient_credit_error( mock_response.json.return_value = { "statusCode": 429, "error": { - "code": "INSUFFICIENT_CREDITS", + "code": "INSUFFICIENT_FUNDS_CREDITS", "message": "You do not have enough credits to perform this request.", "details": [], }, @@ -557,6 +603,52 @@ async def test_async_search_insufficient_credit_error( await client.async_search(query="foo", depth="standard", output_type="searchResults") +@pytest.mark.asyncio +async def test_async_search_too_many_requests_error( + mocker: MockerFixture, client: LinkupClient +) -> None: + mock_response = mocker.Mock() + mock_response.status_code = 429 + mock_response.json.return_value = { + "statusCode": 429, + "error": { + "code": "TOO_MANY_REQUESTS", + "message": "Too many requests.", + "details": [], + }, + } + + mocker.patch( + "linkup.client.LinkupClient._async_request", + return_value=mock_response, + ) + with pytest.raises(LinkupTooManyRequestsError): + await client.async_search(query="foo", depth="standard", output_type="searchResults") + + +@pytest.mark.asyncio +async def test_async_search_error_429_unknown_code( + mocker: MockerFixture, client: LinkupClient +) -> None: + mock_response = mocker.Mock() + mock_response.status_code = 429 + mock_response.json.return_value = { + "statusCode": 429, + "error": { + "code": "FOOBAR", + "message": "Foobar", + "details": [], + }, + } + + mocker.patch( + "linkup.client.LinkupClient._async_request", + return_value=mock_response, + ) + with pytest.raises(LinkupUnknownError): + await client.async_search(query="foo", depth="standard", output_type="searchResults") + + @pytest.mark.asyncio async def test_async_search_structured_search_invalid_request( mocker: MockerFixture,