fairagro-middleware-api-client 10.0.3.dev25__tar.gz → 10.0.3.dev26__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fairagro_middleware_api_client-10.0.3.dev25 → fairagro_middleware_api_client-10.0.3.dev26}/PKG-INFO +1 -1
- {fairagro_middleware_api_client-10.0.3.dev25 → fairagro_middleware_api_client-10.0.3.dev26}/src/middleware/api_client/api_client.py +21 -3
- {fairagro_middleware_api_client-10.0.3.dev25 → fairagro_middleware_api_client-10.0.3.dev26}/tests/unit/test_client.py +20 -0
- {fairagro_middleware_api_client-10.0.3.dev25 → fairagro_middleware_api_client-10.0.3.dev26}/.gitignore +0 -0
- {fairagro_middleware_api_client-10.0.3.dev25 → fairagro_middleware_api_client-10.0.3.dev26}/README.md +0 -0
- {fairagro_middleware_api_client-10.0.3.dev25 → fairagro_middleware_api_client-10.0.3.dev26}/example_client_config.yaml +0 -0
- {fairagro_middleware_api_client-10.0.3.dev25 → fairagro_middleware_api_client-10.0.3.dev26}/pyproject.toml +0 -0
- {fairagro_middleware_api_client-10.0.3.dev25 → fairagro_middleware_api_client-10.0.3.dev26}/spec/harvest-client/design.md +0 -0
- {fairagro_middleware_api_client-10.0.3.dev25 → fairagro_middleware_api_client-10.0.3.dev26}/spec/harvest-client/spec.md +0 -0
- {fairagro_middleware_api_client-10.0.3.dev25 → fairagro_middleware_api_client-10.0.3.dev26}/src/middleware/api_client/__init__.py +0 -0
- {fairagro_middleware_api_client-10.0.3.dev25 → fairagro_middleware_api_client-10.0.3.dev26}/src/middleware/api_client/config.py +0 -0
- {fairagro_middleware_api_client-10.0.3.dev25 → fairagro_middleware_api_client-10.0.3.dev26}/src/middleware/api_client/models.py +0 -0
- {fairagro_middleware_api_client-10.0.3.dev25 → fairagro_middleware_api_client-10.0.3.dev26}/src/middleware/api_client/py.typed +0 -0
- {fairagro_middleware_api_client-10.0.3.dev25 → fairagro_middleware_api_client-10.0.3.dev26}/tests/client_test_support.py +0 -0
- {fairagro_middleware_api_client-10.0.3.dev25 → fairagro_middleware_api_client-10.0.3.dev26}/tests/conftest.py +0 -0
- {fairagro_middleware_api_client-10.0.3.dev25 → fairagro_middleware_api_client-10.0.3.dev26}/tests/integration/conftest.py +0 -0
- {fairagro_middleware_api_client-10.0.3.dev25 → fairagro_middleware_api_client-10.0.3.dev26}/tests/integration/test_create_arcs.py +0 -0
- {fairagro_middleware_api_client-10.0.3.dev25 → fairagro_middleware_api_client-10.0.3.dev26}/tests/unit/conftest.py +0 -0
- {fairagro_middleware_api_client-10.0.3.dev25 → fairagro_middleware_api_client-10.0.3.dev26}/tests/unit/test_api_client_config.py +0 -0
- {fairagro_middleware_api_client-10.0.3.dev25 → fairagro_middleware_api_client-10.0.3.dev26}/tests/unit/test_client_config.py +0 -0
- {fairagro_middleware_api_client-10.0.3.dev25 → fairagro_middleware_api_client-10.0.3.dev26}/tests/unit/test_harvest_arcs.py +0 -0
- {fairagro_middleware_api_client-10.0.3.dev25 → fairagro_middleware_api_client-10.0.3.dev26}/tests/unit/test_retry_logic.py +0 -0
|
@@ -160,6 +160,23 @@ class ApiClient:
|
|
|
160
160
|
return cls._should_retry_request_error(method, path, request_error)
|
|
161
161
|
return False
|
|
162
162
|
|
|
163
|
+
@classmethod
|
|
164
|
+
def _format_request_error(cls, error: BaseException) -> str:
|
|
165
|
+
"""Return a non-empty description for a transport/request error.
|
|
166
|
+
|
|
167
|
+
``httpx.ConnectError`` (and similar) often stringify to ``""``, which
|
|
168
|
+
produces useless log lines like ``Request error: ``. Fall back to the
|
|
169
|
+
exception type name, then ``repr``.
|
|
170
|
+
"""
|
|
171
|
+
message = str(error).strip()
|
|
172
|
+
if message:
|
|
173
|
+
return message
|
|
174
|
+
type_name = type(error).__name__
|
|
175
|
+
representation = repr(error).strip()
|
|
176
|
+
if representation and representation != type_name:
|
|
177
|
+
return f"{type_name}: {representation}"
|
|
178
|
+
return type_name
|
|
179
|
+
|
|
163
180
|
@classmethod
|
|
164
181
|
def _build_failure_error_message(
|
|
165
182
|
cls,
|
|
@@ -175,9 +192,10 @@ class ApiClient:
|
|
|
175
192
|
return f"Request failed after {max_retries} retries: HTTP {status_code}", status_code
|
|
176
193
|
return cls._format_http_error_message(status_code, failure.response.text), status_code
|
|
177
194
|
|
|
195
|
+
detail = cls._format_request_error(failure)
|
|
178
196
|
if retryable:
|
|
179
|
-
return f"Request failed after {max_retries} retries: {
|
|
180
|
-
return f"Request failed: {
|
|
197
|
+
return f"Request failed after {max_retries} retries: {detail}", None
|
|
198
|
+
return f"Request failed: {detail}", None
|
|
181
199
|
|
|
182
200
|
@classmethod
|
|
183
201
|
def _should_retry_or_raise_failure(
|
|
@@ -204,7 +222,7 @@ class ApiClient:
|
|
|
204
222
|
if isinstance(failure, httpx.HTTPStatusError):
|
|
205
223
|
logger.warning("Transient HTTP error %d from server, will retry", failure.response.status_code)
|
|
206
224
|
else:
|
|
207
|
-
logger.warning("Request error: %s. Retrying...", failure)
|
|
225
|
+
logger.warning("Request error: %s. Retrying...", cls._format_request_error(failure))
|
|
208
226
|
return True
|
|
209
227
|
|
|
210
228
|
msg, normalized_status_code = cls._build_failure_error_message(
|
|
@@ -371,6 +371,26 @@ async def test_get_timeout_not_retried(client_config: Config) -> None:
|
|
|
371
371
|
assert route.call_count == 1
|
|
372
372
|
|
|
373
373
|
|
|
374
|
+
def test_format_request_error_falls_back_when_message_empty() -> None:
|
|
375
|
+
"""Empty ConnectError messages fall back to the exception type name."""
|
|
376
|
+
empty = httpx.ConnectError("")
|
|
377
|
+
assert str(empty) == ""
|
|
378
|
+
formatted = ApiClient._format_request_error(empty) # noqa: SLF001
|
|
379
|
+
assert "ConnectError" in formatted
|
|
380
|
+
assert formatted.strip() != ""
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
@pytest.mark.asyncio
|
|
384
|
+
@respx.mock
|
|
385
|
+
async def test_empty_connect_error_message_is_not_blank(client_config: Config) -> None:
|
|
386
|
+
"""ApiClientError for empty ConnectError still carries a useful message."""
|
|
387
|
+
respx.post(f"{client_config.api_url}v3/harvests").mock(side_effect=httpx.ConnectError(""))
|
|
388
|
+
async with ApiClient(client_config) as client:
|
|
389
|
+
with pytest.raises(ApiClientError, match="Request failed: ConnectError") as exc_info:
|
|
390
|
+
await client.create_harvest(rdi="test-rdi")
|
|
391
|
+
assert str(exc_info.value).strip() != "Request failed:"
|
|
392
|
+
|
|
393
|
+
|
|
374
394
|
@pytest.mark.asyncio
|
|
375
395
|
@respx.mock
|
|
376
396
|
async def test_global_max_concurrency_limits_parallel_requests(client_config: Config) -> None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|