Skip to content

MPT-15431 Add MPTMaxRetryError in the MPT Errors and error handler#316

Merged
jentyk merged 3 commits intomainfrom
feat/MPT-15431
Apr 21, 2026
Merged

MPT-15431 Add MPTMaxRetryError in the MPT Errors and error handler#316
jentyk merged 3 commits intomainfrom
feat/MPT-15431

Conversation

@jentyk
Copy link
Copy Markdown
Member

@jentyk jentyk commented Apr 20, 2026

Closes MPT-15431

  • Add MPTMaxRetryError (subclass of MPTError) with constructor (message: str, attempts: int) to signal retry exhaustion
  • Switch HTTP clients to httpx-retries (Retry / RetryTransport) and store configured retry count on clients
  • HTTPClient and AsyncHTTPClient now catch httpx.RequestError and raise MPTMaxRetryError including attempt count
  • Replace direct response.raise_for_status() handling with new handle_response_http_error(response) helper that transforms HTTPStatusError into domain exceptions
  • Add httpx-retries==0.5.* to pyproject.toml
  • Update unit tests to expect MPTMaxRetryError and assert retry attempt counts for timeout scenarios

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 20, 2026

📝 Walkthrough

Walkthrough

Adds MPTMaxRetryError, replaces httpx transport retry config with httpx-retries Retry/RetryTransport in sync and async clients, centralizes HTTP status error handling in handle_response_http_error, updates tests to expect retry-exhaustion behavior, and adds httpx-retries dependency.

Changes

Cohort / File(s) Summary
Exceptions
mpt_api_client/exceptions.py
Added MPTMaxRetryError(MPTError) with __init__(message: str, attempts: int) formatting message to include attempt count.
Async HTTP client
mpt_api_client/http/async_client.py
Switched transport to httpx_retries.Retry/RetryTransport, stored self._retries, map httpx.RequestError -> MPTMaxRetryError(str(err), self._retries + 1), delegate response error handling to handle_response_http_error.
Sync HTTP client
mpt_api_client/http/client.py
Same changes as async: use RetryTransport, keep self._retries, convert RequestError to MPTMaxRetryError, call centralized response error handler instead of previous per-response transform flow.
HTTP utilities
mpt_api_client/http/request_response_utils.py
Added handle_response_http_error(response: httpx.Response) -> None which calls response.raise_for_status(), transforms caught httpx.HTTPStatusError via existing transformer, and re-raises the transformed exception preserving the original as cause.
Dependencies
pyproject.toml
Added runtime dependency httpx-retries==0.5.*.
Unit tests
tests/unit/http/test_async_client.py, tests/unit/http/test_client.py
Updated tests to expect retry-exhaustion MPTMaxRetryError message (includes attempt count) and assert exact mocked route call_count matching configured retries.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Jira Issue Key In Title ✅ Passed The PR title contains exactly one Jira issue key in the correct format (MPT-15431).
Test Coverage Required ✅ Passed The pull request modifies 5 code files and includes test file changes in 2 test files.
Single Commit Required ✅ Passed The pull request contains exactly one commit (7975d70) with all changes consolidated, satisfying the single commit requirement.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@jentyk jentyk force-pushed the feat/MPT-15431 branch 4 times, most recently from b4c0f6c to ca9e41f Compare April 21, 2026 08:51
@jentyk jentyk marked this pull request as ready for review April 21, 2026 08:53
@jentyk jentyk requested a review from a team as a code owner April 21, 2026 08:53
@jentyk jentyk requested review from albertsola and d3rky April 21, 2026 08:53
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
tests/unit/http/test_async_client.py (1)

78-85: Consider using MPTMaxRetryError for consistency with the sync client test.

The sync client test (test_client.py line 74) explicitly asserts MPTMaxRetryError, while this async test uses the base MPTError. While functionally correct (due to inheritance), using the specific exception type would:

  1. Be consistent with the sync test
  2. More precisely verify the expected behavior
  3. Fail faster if the exception type changes
♻️ Proposed fix for test consistency
-from mpt_api_client.exceptions import MPTError
+from mpt_api_client.exceptions import MPTMaxRetryError
-    with pytest.raises(MPTError, match=r"Mock Timeout error after 6 retry attempts."):
+    with pytest.raises(MPTMaxRetryError, match=r"Mock Timeout error after 6 retry attempts."):
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/http/test_async_client.py` around lines 78 - 85, In
test_async_http_call_failure, replace the generic MPTError in the pytest.raises
assertion with the more specific MPTMaxRetryError to match the sync test; ensure
you still await async_http_client.request("GET", "/timeout") inside the with
pytest.raises(MPTMaxRetryError, match=r"Mock Timeout error after 6 retry
attempts.") block so the test asserts the specific max-retry exception type.
mpt_api_client/http/client.py (1)

91-95: Docstring missing MPTMaxRetryError in raised exceptions.

The async client's docstring (line 85 in async_client.py) includes MPTMaxRetryError in the Raises section, but the sync client's docstring does not. For consistency, add the same exception to the Raises section.

📝 Proposed docstring update
         Raises:
             MPTError: If the request fails.
             MPTApiError: If the response contains an error.
             MPTHttpError: If the response contains an HTTP error.
+            MPTMaxRetryError: If the request fails after maximum retry attempts.
         """
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mpt_api_client/http/client.py` around lines 91 - 95, The sync client's
docstring in mpt_api_client/http/client.py lists raised exceptions (MPTError,
MPTApiError, MPTHttpError) but omits MPTMaxRetryError; update the Raises section
of the sync client's docstring to include MPTMaxRetryError (mirroring the async
client in async_client.py) so that the docstring for the relevant request
method/client class reflects all possible exceptions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@mpt_api_client/http/client.py`:
- Around line 91-95: The sync client's docstring in
mpt_api_client/http/client.py lists raised exceptions (MPTError, MPTApiError,
MPTHttpError) but omits MPTMaxRetryError; update the Raises section of the sync
client's docstring to include MPTMaxRetryError (mirroring the async client in
async_client.py) so that the docstring for the relevant request method/client
class reflects all possible exceptions.

In `@tests/unit/http/test_async_client.py`:
- Around line 78-85: In test_async_http_call_failure, replace the generic
MPTError in the pytest.raises assertion with the more specific MPTMaxRetryError
to match the sync test; ensure you still await async_http_client.request("GET",
"/timeout") inside the with pytest.raises(MPTMaxRetryError, match=r"Mock Timeout
error after 6 retry attempts.") block so the test asserts the specific max-retry
exception type.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 68a7645a-b7a5-4644-ae05-e6982792e3b7

📥 Commits

Reviewing files that changed from the base of the PR and between dbf9103 and ca9e41f.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • mpt_api_client/exceptions.py
  • mpt_api_client/http/async_client.py
  • mpt_api_client/http/client.py
  • mpt_api_client/http/request_response_utils.py
  • pyproject.toml
  • tests/unit/http/test_async_client.py
  • tests/unit/http/test_client.py

Comment thread tests/unit/http/test_async_client.py Outdated
Comment thread tests/unit/http/test_client.py Outdated
@jentyk jentyk requested a review from albertsola April 21, 2026 09:48
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@mpt_api_client/http/async_client.py`:
- Around line 27-30: The Retry initialization currently uses defaults that
exclude POST/PATCH; update the retry creation (the Retry(...) passed into
RetryTransport) to explicitly set allowed_methods to include POST and PATCH
(e.g., include "GET","POST","PATCH","PUT","DELETE","HEAD","OPTIONS","TRACE") so
POST and PATCH calls made via RetryTransport will be retried; change the
Retry(...) calls that create the retry object used by RetryTransport (the
variables named retry and transport in the Async client) to pass allowed_methods
accordingly.

In `@mpt_api_client/http/client.py`:
- Around line 37-40: The Retry created in the HTTP client uses default
allowed_methods which excludes POST/PATCH; update the Retry instantiation (where
self._retries is used to build Retry and passed to RetryTransport) to explicitly
include POST and PATCH in allowed_methods (e.g., a frozenset containing GET,
HEAD, OPTIONS, POST, PUT, PATCH, DELETE) so POST/PATCH requests will be retried
on transient failures; make the identical change in the async client where
Retry(total=self._retries) is constructed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 8492fe7f-0b3c-41c3-8908-5005a2238d42

📥 Commits

Reviewing files that changed from the base of the PR and between a058817 and eae975b.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • mpt_api_client/exceptions.py
  • mpt_api_client/http/async_client.py
  • mpt_api_client/http/client.py
  • mpt_api_client/http/request_response_utils.py
  • pyproject.toml
  • tests/unit/http/test_async_client.py
  • tests/unit/http/test_client.py
✅ Files skipped from review due to trivial changes (2)
  • mpt_api_client/exceptions.py
  • pyproject.toml
🚧 Files skipped from review as they are similar to previous changes (3)
  • tests/unit/http/test_client.py
  • tests/unit/http/test_async_client.py
  • mpt_api_client/http/request_response_utils.py

Comment thread mpt_api_client/http/async_client.py
Comment thread mpt_api_client/http/client.py
@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
mpt_api_client/http/async_client.py (1)

27-31: Validate retries upfront to fail fast on invalid config.

Consider guarding against negative values (e.g., retries < 0) in __init__ to prevent ambiguous runtime behavior from invalid client configuration.

Suggested diff
     def __init__(
         self,
         *,
         base_url: str | None = None,
         api_token: str | None = None,
         timeout: float = 20.0,
         retries: int = 5,
     ):
+        if retries < 0:
+            raise ValueError("retries must be greater than or equal to 0")
         self._retries = retries
         retry = Retry(
             total=retries,
             allowed_methods={"DELETE", "GET", "HEAD", "OPTIONS", "POST", "PUT", "PATCH"},
         )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mpt_api_client/http/async_client.py` around lines 27 - 31, In __init__ of the
async client (where self._retries is set and Retry(...) is constructed),
validate the retries parameter up-front: ensure it's an integer and non-negative
(e.g., raise ValueError for retries < 0 or TypeError if not int) before
assigning to self._retries and creating the Retry object; this enforces
fail-fast behavior and prevents ambiguous runtime config.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@mpt_api_client/http/async_client.py`:
- Around line 27-31: In __init__ of the async client (where self._retries is set
and Retry(...) is constructed), validate the retries parameter up-front: ensure
it's an integer and non-negative (e.g., raise ValueError for retries < 0 or
TypeError if not int) before assigning to self._retries and creating the Retry
object; this enforces fail-fast behavior and prevents ambiguous runtime config.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: a33017fd-7463-4ee1-a431-77f378ca0b94

📥 Commits

Reviewing files that changed from the base of the PR and between eae975b and 7975d70.

📒 Files selected for processing (6)
  • mpt_api_client/exceptions.py
  • mpt_api_client/http/async_client.py
  • mpt_api_client/http/client.py
  • mpt_api_client/http/request_response_utils.py
  • tests/unit/http/test_async_client.py
  • tests/unit/http/test_client.py
✅ Files skipped from review due to trivial changes (1)
  • tests/unit/http/test_async_client.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • tests/unit/http/test_client.py
  • mpt_api_client/exceptions.py
  • mpt_api_client/http/request_response_utils.py

@jentyk jentyk merged commit 312c025 into main Apr 21, 2026
4 checks passed
@jentyk jentyk deleted the feat/MPT-15431 branch April 21, 2026 10:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants