From 5bed31181b90dd8bfe40968d20bc2acaada9533c Mon Sep 17 00:00:00 2001 From: William FH <13333726+hinthornw@users.noreply.github.com> Date: Wed, 24 Jan 2024 07:06:20 -0800 Subject: [PATCH] Catch 409's (#375) --- python/langsmith/client.py | 21 +++++++++++++++++++++ python/langsmith/utils.py | 4 ++++ python/tests/unit_tests/test_client.py | 20 ++++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/python/langsmith/client.py b/python/langsmith/client.py index 9b5e48172..d8bc7a8bc 100644 --- a/python/langsmith/client.py +++ b/python/langsmith/client.py @@ -425,6 +425,7 @@ def request_with_retries( request_kwargs: Mapping, stop_after_attempt: int = 1, retry_on: Optional[Sequence[Type[BaseException]]] = None, + to_ignore: Optional[Sequence[Type[BaseException]]] = None, ) -> requests.Response: """Send a request with retries. @@ -436,6 +437,13 @@ def request_with_retries( The URL to send the request to. request_kwargs : Mapping Additional request parameters. + stop_after_attempt : int, default=1 + The number of attempts to make. + retry_on : Sequence[Type[BaseException]] or None, default=None + The exceptions to retry on. In addition to: + [LangSmithConnectionError, LangSmithAPIError]. + to_ignore : Sequence[Type[BaseException]] or None, default=None + The exceptions to ignore / pass on. Returns ------- @@ -458,6 +466,8 @@ def request_with_retries( *(retry_on or []), *(ls_utils.LangSmithConnectionError, ls_utils.LangSmithAPIError), ) + to_ignore_: Tuple[Type[BaseException], ...] = (*(to_ignore or ()),) + response = None for idx in range(stop_after_attempt): try: try: @@ -486,6 +496,10 @@ def request_with_retries( raise ls_utils.LangSmithNotFoundError( f"Resource not found for {url}. {repr(e)}" ) + elif response.status_code == 409: + raise ls_utils.LangSmithConflictError( + f"Conflict for {url}. {repr(e)}" + ) else: raise ls_utils.LangSmithError( f"Failed to {request_method} {url} in LangSmith" @@ -511,6 +525,11 @@ def request_with_retries( raise ls_utils.LangSmithError( f"Failed to {request_method} {url} in LangSmith API. {emsg}" ) from e + except to_ignore_ as e: + if response is not None: + logger.debug("Passing on exception %s", e) + return response + # Else we still raise an error except retry_on_: if idx + 1 == stop_after_attempt: raise @@ -868,6 +887,7 @@ def create_run( "headers": headers, "timeout": self.timeout_ms / 1000, }, + to_ignore=(ls_utils.LangSmithConflictError,), ) def batch_ingest_runs( @@ -961,6 +981,7 @@ def batch_ingest_runs( "Content-Type": "application/json", }, }, + to_ignore=(ls_utils.LangSmithConflictError,), ) def update_run( diff --git a/python/langsmith/utils.py b/python/langsmith/utils.py index 1d87f5cf1..382dc6190 100644 --- a/python/langsmith/utils.py +++ b/python/langsmith/utils.py @@ -37,6 +37,10 @@ class LangSmithNotFoundError(LangSmithError): """Couldn't find the requested resource.""" +class LangSmithConflictError(LangSmithError): + """The resource already exists.""" + + class LangSmithConnectionError(LangSmithError): """Couldn't connect to the LangSmith API.""" diff --git a/python/tests/unit_tests/test_client.py b/python/tests/unit_tests/test_client.py index fc026fd3b..9e455c87e 100644 --- a/python/tests/unit_tests/test_client.py +++ b/python/tests/unit_tests/test_client.py @@ -383,6 +383,26 @@ def test_http_status_500_handling(mock_sleep): assert mock_session.request.call_count == 2 +@patch("langsmith.client.time.sleep") +def test_pass_on_409_handling(mock_sleep): + client = Client(api_key="test") + with patch.object(client, "session") as mock_session: + mock_response = MagicMock() + mock_response.status_code = 409 + mock_response.raise_for_status.side_effect = HTTPError() + mock_session.request.return_value = mock_response + + response = client.request_with_retries( + "GET", + "https://test.url", + {}, + stop_after_attempt=5, + to_ignore=[ls_utils.LangSmithConflictError], + ) + assert mock_session.request.call_count == 1 + assert response == mock_response + + @patch("langsmith.client.ls_utils.raise_for_status_with_text") def test_http_status_429_handling(mock_raise_for_status): client = Client(api_key="test")