From 99780fc10d620c0e730d1f0878d7e78cd1136ee3 Mon Sep 17 00:00:00 2001 From: Lucas Guillermou Date: Fri, 28 Feb 2025 12:09:54 +0100 Subject: [PATCH 1/2] Raise URLNotFoundError instead of infinite retry --- docs/docs/python-sdk/reference/config.mdx | 9 +++++++++ infrahub_sdk/client.py | 7 ++++++- infrahub_sdk/config.py | 3 +++ infrahub_sdk/exceptions.py | 6 ++++++ tasks.py | 4 ++-- tests/integration/test_infrahub_client.py | 6 +++++- 6 files changed, 31 insertions(+), 4 deletions(-) diff --git a/docs/docs/python-sdk/reference/config.mdx b/docs/docs/python-sdk/reference/config.mdx index 7e87746a..320aebb4 100644 --- a/docs/docs/python-sdk/reference/config.mdx +++ b/docs/docs/python-sdk/reference/config.mdx @@ -150,6 +150,15 @@ The following settings can be defined in the `Config` class **Default value**: False
**Environment variable**: `INFRAHUB_RETRY_ON_FAILURE`
+## max_retry_duration + +**Property**: max_retry_duration
+ +**Description**: Maximum duration until we stop attempting to retry if enabled.
+**Type**: `integer`
+**Default value**: 300
+**Environment variable**: `INFRAHUB_MAX_RETRY_DURATION`
+ ## schema_converge_timeout **Property**: schema_converge_timeout
diff --git a/infrahub_sdk/client.py b/infrahub_sdk/client.py index 4dda3eb8..372810c9 100644 --- a/infrahub_sdk/client.py +++ b/infrahub_sdk/client.py @@ -3,6 +3,7 @@ import asyncio import copy import logging +import time from collections.abc import Coroutine, MutableMapping from functools import wraps from time import sleep @@ -38,6 +39,7 @@ NodeNotFoundError, ServerNotReachableError, ServerNotResponsiveError, + URLNotFoundError, ) from .graphql import Mutation, Query from .node import ( @@ -878,7 +880,8 @@ async def execute_graphql( retry = True resp = None - while retry: + start_time = time.time() + while retry and time.time() - start_time < self.config.max_retry_duration: retry = self.retry_on_failure try: resp = await self._post(url=url, payload=payload, headers=headers, timeout=timeout) @@ -902,6 +905,8 @@ async def execute_graphql( errors = response.get("errors", []) messages = [error.get("message") for error in errors] raise AuthenticationError(" | ".join(messages)) from exc + if exc.response.status_code == 404: + raise URLNotFoundError(url=url) if not resp: raise Error("Unexpected situation, resp hasn't been initialized.") diff --git a/infrahub_sdk/config.py b/infrahub_sdk/config.py index 4034553a..51c790dc 100644 --- a/infrahub_sdk/config.py +++ b/infrahub_sdk/config.py @@ -56,6 +56,9 @@ class ConfigBase(BaseSettings): pagination_size: int = Field(default=50, description="Page size for queries to the server") retry_delay: int = Field(default=5, description="Number of seconds to wait until attempting a retry.") retry_on_failure: bool = Field(default=False, description="Retry operation in case of failure") + max_retry_duration: int = Field( + default=300, description="Maximum duration until we stop attempting to retry if enabled." + ) schema_converge_timeout: int = Field( default=60, description="Number of seconds to wait for schema to have converged" ) diff --git a/infrahub_sdk/exceptions.py b/infrahub_sdk/exceptions.py index b3120441..8b5b4b43 100644 --- a/infrahub_sdk/exceptions.py +++ b/infrahub_sdk/exceptions.py @@ -121,6 +121,12 @@ def __init__(self, message: str | None = None): super().__init__(self.message) +class URLNotFoundError(Error): + def __init__(self, url: str): + self.message = f"`{url}` not found." + super().__init__(self.message) + + class FeatureNotSupportedError(Error): """Raised when trying to use a method on a node that doesn't support it.""" diff --git a/tasks.py b/tasks.py index fc4de5c4..cea9a59a 100644 --- a/tasks.py +++ b/tasks.py @@ -181,6 +181,6 @@ def generate_infrahubctl(context: Context) -> None: @task(name="generate-sdk") -def generate_python_sdk(context: Context) -> None: +def generate_python_sdk(context: Context) -> None: # noqa: ARG001 """Generate documentation for the Python SDK.""" - _generate_infrahub_sdk_configuration_documentation(context=context) + _generate_infrahub_sdk_configuration_documentation() diff --git a/tests/integration/test_infrahub_client.py b/tests/integration/test_infrahub_client.py index 41ff5a14..20af4a35 100644 --- a/tests/integration/test_infrahub_client.py +++ b/tests/integration/test_infrahub_client.py @@ -5,7 +5,7 @@ import pytest from infrahub_sdk.branch import BranchData -from infrahub_sdk.exceptions import BranchNotFoundError +from infrahub_sdk.exceptions import BranchNotFoundError, URLNotFoundError from infrahub_sdk.node import InfrahubNode from infrahub_sdk.schema import ProfileSchemaAPI from infrahub_sdk.testing.docker import TestInfrahubDockerClient @@ -146,6 +146,10 @@ async def test_count_with_filter(self, client: InfrahubClient, base_dataset): count = await client.count(kind=TESTING_PERSON, name__values=["Liam Walker", "Ethan Carter"]) assert count == 2 + async def test_query_unexisting_branch(self, client: InfrahubClient): + with pytest.raises(URLNotFoundError, match=r"/graphql/unexisting` not found."): + await client.execute_graphql(query="unused", branch_name="unexisting") + async def test_create_generic_rel_with_hfid( self, client: InfrahubClient, base_dataset, cat_luna, person_sophia, schema_animal, schema_cat ): From fd67def3d3c3535b32d316767f1b4c4beb09e962 Mon Sep 17 00:00:00 2001 From: Lucas Guillermou Date: Mon, 3 Mar 2025 10:38:59 +0100 Subject: [PATCH 2/2] add client sync changes --- infrahub_sdk/client.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/infrahub_sdk/client.py b/infrahub_sdk/client.py index 372810c9..0d689afc 100644 --- a/infrahub_sdk/client.py +++ b/infrahub_sdk/client.py @@ -1618,7 +1618,8 @@ def execute_graphql( retry = True resp = None - while retry: + start_time = time.time() + while retry and time.time() - start_time < self.config.max_retry_duration: retry = self.retry_on_failure try: resp = self._post(url=url, payload=payload, headers=headers, timeout=timeout) @@ -1642,6 +1643,8 @@ def execute_graphql( errors = response.get("errors", []) messages = [error.get("message") for error in errors] raise AuthenticationError(" | ".join(messages)) from exc + if exc.response.status_code == 404: + raise URLNotFoundError(url=url) if not resp: raise Error("Unexpected situation, resp hasn't been initialized.")