Skip to content

feat(http): implement rate-limit handling with auto-retry (#11)#24

Merged
codebestia merged 6 commits into
ShadeProtocol:mainfrom
bukkybyte:feature/11-rate-limit-handling
Jun 24, 2026
Merged

feat(http): implement rate-limit handling with auto-retry (#11)#24
codebestia merged 6 commits into
ShadeProtocol:mainfrom
bukkybyte:feature/11-rate-limit-handling

Conversation

@bukkybyte

@bukkybyte bukkybyte commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

PR description:

Closes #11

What

Adds automatic 429 handling so callers never need to implement their own backoff.

Changes

  • src/shade/errors.pyRateLimitError(retry_after=...) in a ShadeError → HTTPError → RateLimitError hierarchy
  • src/shade/http.pySyncHTTPClient and AsyncHTTPClient with retry loop; Retry-After header parsing; exponential fallback
  • src/shade/gateway.py — wired to both clients; exposes process_payment_async()
  • tests/test_rate_limit.py — 19 tests covering all acceptance criteria

Behaviour

Scenario Result
429 + Retry-After: 5, attempt < max_retries sleep 5 s, retry
429, no header, attempt < max_retries exponential backoff (1 s, 2 s, 4 s…)
429, retries exhausted RateLimitError(retry_after=5)
max_retries=0 RateLimitError immediately, no sleep
Async path asyncio.sleep, never time.sleep

Summary by CodeRabbit

  • New Features
    • Added a real payment gateway client with synchronous and asynchronous payment processing.
    • Introduced a low-level HTTP transport with automatic retry behavior for rate limiting and improved error handling.
    • Added new exceptions for general HTTP failures and HTTP 429 responses, including Retry-After details when available.
  • Tests
    • Updated gateway tests to validate payment request payloads and async payment behavior.
    • Added a rate-limit test suite covering retry-after parsing and retry/backoff logic.
  • Chores
    • Added aiohttp as a runtime dependency for async requests.

@codebestia

Copy link
Copy Markdown
Contributor

Hello @bukkybyte
Please resolve the conflicts.

@codebestia

Copy link
Copy Markdown
Contributor

Please fix the CI as well

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6b983db4-0925-40b2-a100-06722d2b5f48

📥 Commits

Reviewing files that changed from the base of the PR and between 0a12b43 and 6e07cc0.

📒 Files selected for processing (2)
  • tests/test_gateway.py
  • tests/test_rate_limit.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/test_gateway.py
  • tests/test_rate_limit.py

📝 Walkthrough

Walkthrough

Adds HTTP error types, low-level sync and async HTTP clients with 429 retry handling, rewrites Gateway to use them, adds aiohttp as a dependency, and updates tests for the new request and retry behavior.

Changes

Rate-limit handling: HTTP clients, error types, and Gateway wiring

Layer / File(s) Summary
Public error types and exports
pyproject.toml, src/shade/errors.py, src/shade/__init__.py
Adds aiohttp ^3.14.1, defines HTTPError and RateLimitError, adds an errors module docstring, and re-exports the new HTTP clients and error types from the package root.
Retry helpers and SyncHTTPClient
src/shade/http.py, tests/test_rate_limit.py
Defines retry/backoff helpers, base URL validation, shared status handling, and SyncHTTPClient; adds rate-limit tests for parsing, retry sleeping, exhaustion, and non-429 failures.
AsyncHTTPClient
src/shade/http.py, tests/test_rate_limit.py
Implements AsyncHTTPClient with lazy aiohttp, JSON requests, shared 429 handling, and asyncio.sleep retries; adds async rate-limit tests.
Gateway rewrite using HTTP clients
src/shade/gateway.py, tests/test_gateway.py
Replaces the placeholder Gateway with HTTP-backed payment calls, adds process_payment_async, and updates gateway tests for the new constructor and request assertions.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant Gateway
  participant SyncHTTPClient
  participant AsyncHTTPClient

  Caller->>Gateway: process_payment(amount, currency)
  Gateway->>SyncHTTPClient: request("POST", "/payments", payload)
  SyncHTTPClient-->>Gateway: response body
  Gateway-->>Caller: Dict[str, Any]

  Caller->>Gateway: process_payment_async(amount, currency)
  Gateway->>AsyncHTTPClient: request("POST", "/payments", payload)
  AsyncHTTPClient-->>Gateway: response body
  Gateway-->>Caller: Dict[str, Any]
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 A 429 hopped by one day,
But Retry-After showed me the way.
With sync and async in my trail,
I bounced, then retried, and did not fail.
Fresh HTTP carrots, crisp and bright,
Made this little SDK feel right.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Gateway API rewiring, package re-exports, and the new aiohttp dependency go beyond the 429-handling requirements in #11. Limit the PR to 429 detection, Retry-After parsing, retry/backoff, and async sleep; move gateway/export/dependency changes to a separate PR if not needed.
Docstring Coverage ⚠️ Warning Docstring coverage is 16.95% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly names the main change: HTTP rate-limit handling with auto-retry, and it matches the linked issue.
Description check ✅ Passed The description covers the summary, issue reference, changes, and behavior, though it omits some template sections like testing and checklist.
Linked Issues check ✅ Passed The PR adds 429 detection, Retry-After parsing, retry/backoff logic, and async non-blocking sleep as requested in #11.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/shade/errors.py (1)

43-57: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Remove the duplicate methods inside RateLimitError.

These definitions override the 429-specific __init__/__str__ above, so RateLimitError(..., retry_after=...) will now fail and status_code is no longer guaranteed to stay 429.

Proposed fix
 class RateLimitError(HTTPError):
@@
     def __str__(self) -> str:  # pragma: no cover
         base = super().__str__()
         if self.retry_after is not None:
             return f"{base} (retry after {self.retry_after}s)"
         return base
-    def __init__(
-        self,
-        message: str,
-        status_code: Optional[int] = None,
-        response_body: Optional[str] = None,
-    ) -> None:
-        self.message = message
-        self.status_code = status_code
-        self.response_body = response_body
-        super().__init__(message)
-
-    def __str__(self) -> str:
-        if self.status_code is None:
-            return self.message
-        return f"{self.message} (status code: {self.status_code})"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/shade/errors.py` around lines 43 - 57, RateLimitError’s duplicate
__init__ and __str__ are overriding the 429-specific behavior and breaking
retry_after handling. Remove the redundant methods from RateLimitError so it
inherits the existing 429-aware constructor/formatter, and keep the status_code
fixed at 429 via the shared error implementation in the errors module.
🧹 Nitpick comments (2)
src/shade/__init__.py (1)

15-28: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Deduplicate __all__ while fixing the Ruff export-list warning.

Gateway and ShadeError are listed twice here. Cleaning that up makes the public surface unambiguous and should simplify the RUF022 follow-up.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/shade/__init__.py` around lines 15 - 28, The export list in the __all__
definition contains duplicate symbols, which triggers the Ruff export-list
warning. Update the __all__ list in src/shade/__init__.py to remove the repeated
Gateway and ShadeError entries so each public export appears only once, keeping
the intended API surface unchanged.

Source: Linters/SAST tools

tests/test_gateway.py (1)

11-21: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a matching test for process_payment_async.

This file now verifies the sync path only, but the PR also adds Gateway.process_payment_async() in src/shade/gateway.py:79-87. A small test that patches gateway._async_http.request and asserts the same method/path/payload would close the main coverage gap in the new public API.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_gateway.py` around lines 11 - 21, Add a test for
Gateway.process_payment_async to cover the new async public API alongside
test_process_payment. In tests/test_gateway.py, mirror the existing sync test by
patching gateway._async_http.request, invoking process_payment_async, and
asserting it returns the mocked response and calls request with the same POST
/payments payload used by process_payment. Use the existing Gateway test setup
so the new async coverage stays consistent with the sync path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/shade/gateway.py`:
- Around line 27-45: The Gateway initializer currently allows an empty api_key,
but SyncHTTPClient and AsyncHTTPClient still attach Authorization headers, so
Gateway() can be created in a broken state. Update Gateway.__init__ to require a
non-empty api_key and fail immediately by raising an error before constructing
the HTTP clients. Keep the fix localized to Gateway.__init__ and ensure the
validation happens before SyncHTTPClient and AsyncHTTPClient are instantiated.

In `@src/shade/http.py`:
- Around line 123-133: The sync client constructor currently accepts any
caller-provided base_url, which can allow non-HTTP schemes through
urllib.request.urlopen. Update the __init__ path in the HTTP client to validate
that base_url is an absolute http:// or https:// URL before storing it, and
reject any other scheme early with a clear error; keep the same behavior in the
sync request flow so only HTTP/HTTPS endpoints are usable. Use the HTTP client
constructor and any request helper methods that consume self.base_url as the
reference points for the change.

In `@tests/test_rate_limit.py`:
- Around line 162-165: The test file contains semicolon-chained statements that
Ruff flags with E702, so split each combined assignment/increment into separate
lines throughout the affected helper blocks. Update the `fake_execute` function
and the other nearby test sections using the same pattern so each `nonlocal`
update, assignment, and increment is on its own line, which will unblock lint
and CI. Keep the logic unchanged while refactoring the affected test helpers and
response-index handling.

---

Outside diff comments:
In `@src/shade/errors.py`:
- Around line 43-57: RateLimitError’s duplicate __init__ and __str__ are
overriding the 429-specific behavior and breaking retry_after handling. Remove
the redundant methods from RateLimitError so it inherits the existing 429-aware
constructor/formatter, and keep the status_code fixed at 429 via the shared
error implementation in the errors module.

---

Nitpick comments:
In `@src/shade/__init__.py`:
- Around line 15-28: The export list in the __all__ definition contains
duplicate symbols, which triggers the Ruff export-list warning. Update the
__all__ list in src/shade/__init__.py to remove the repeated Gateway and
ShadeError entries so each public export appears only once, keeping the intended
API surface unchanged.

In `@tests/test_gateway.py`:
- Around line 11-21: Add a test for Gateway.process_payment_async to cover the
new async public API alongside test_process_payment. In tests/test_gateway.py,
mirror the existing sync test by patching gateway._async_http.request, invoking
process_payment_async, and asserting it returns the mocked response and calls
request with the same POST /payments payload used by process_payment. Use the
existing Gateway test setup so the new async coverage stays consistent with the
sync path.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d4863d73-494a-402c-afe1-79494609b3bd

📥 Commits

Reviewing files that changed from the base of the PR and between d9eb3d8 and e814a01.

⛔ Files ignored due to path filters (1)
  • poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • pyproject.toml
  • src/shade/__init__.py
  • src/shade/errors.py
  • src/shade/gateway.py
  • src/shade/http.py
  • tests/test_gateway.py
  • tests/test_rate_limit.py

Comment thread src/shade/gateway.py
Comment thread src/shade/http.py
Comment thread tests/test_rate_limit.py
@codebestia

Copy link
Copy Markdown
Contributor

@bukkybyte
Please fix the CI and resolve the coderabbit reviews.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_gateway.py`:
- Around line 31-33: The test in test_gateway.py is using event-loop management
that should be replaced with a simpler synchronous async call. Update the test
that invokes gateway.process_payment_async to use asyncio.run instead of
asyncio.get_event_loop().run_until_complete, keeping the assertion logic the
same and ensuring the test remains a normal sync test without relying on
pytest-asyncio.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7fc759fd-83c0-4752-802a-53b614e3a310

📥 Commits

Reviewing files that changed from the base of the PR and between e814a01 and 0a12b43.

📒 Files selected for processing (6)
  • src/shade/__init__.py
  • src/shade/errors.py
  • src/shade/gateway.py
  • src/shade/http.py
  • tests/test_gateway.py
  • tests/test_rate_limit.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/shade/init.py
  • src/shade/gateway.py
  • src/shade/errors.py
  • tests/test_rate_limit.py
  • src/shade/http.py

Comment thread tests/test_gateway.py Outdated
@bukkybyte

Copy link
Copy Markdown
Contributor Author

@bukkybyte Please fix the CI and resolve the coderabbit reviews.

resolved. please check

@codebestia codebestia left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM!
Nice Implementation.
Thank you for your contribution.

@codebestia codebestia merged commit f6a4cff into ShadeProtocol:main Jun 24, 2026
2 checks passed
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.

Implement rate-limit handling

2 participants