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..0d689afc 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.") @@ -1613,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) @@ -1637,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.") 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 ):