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
):