Skip to content

chores: Implement unified response parser#32

Merged
codebestia merged 3 commits into
ShadeProtocol:mainfrom
KodeSage:feat/unified_parser
Jun 29, 2026
Merged

chores: Implement unified response parser#32
codebestia merged 3 commits into
ShadeProtocol:mainfrom
KodeSage:feat/unified_parser

Conversation

@KodeSage

@KodeSage KodeSage commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

CLOSES #12

Description

This change introduces a single _parse_response function that every resource method can route HTTP responses through. It centralizes JSON decoding, success detection, and the mapping of HTTP status codes to the SDK's typed exception hierarchy, preventing per-resource error handling from drifting over time.

Previously, response handling logic lived alongside the retry-aware _raise_for_status helper and operated on raw (status, headers, body) tuples. _parse_response provides a clean, transport-agnostic entry point that takes an httpx.Response and either returns the decoded body or raises the correct typed
exception with the raw body and status attached.

Behavior implemented:

  • 2xx → decode JSON and return the dict.
  • 401 / 403AuthenticationError.
  • 400 / 422InvalidRequestError, carrying field-level errors when the
    response body provides them.
  • 404NotFoundError.
  • 429RateLimitError (parses the Retry-After header).
  • 5xxNetworkError (subject to retry by callers).
  • Any other non-2xxHTTPError, so nothing escapes the funnel unhandled.
  • JSON decode failure on a 2xx body, a non-dict 2xx body, or a 2xx body that
    carries a truthy error key → ShadeError("Invalid response from API") /
    the extracted error message.

Every raised exception exposes both status_code and the raw response_body. Non-JSON error bodies still map to their typed exception with the raw body attached, and because all typed errors subclass ShadeError, a malformed body never surfaces a raw JSONDecodeError.

To support field-level validation errors, InvalidRequestError now accepts and stores an optional field_errors attribute (backward-compatible — existing construction calls are unaffected).

Note on file naming: the proposed work referenced http_client.py, but the actual HTTP module in this project is src/shade/http.py (which already imports httpx as an optional dependency). The function was added there to avoid
fragmenting the HTTP layer.

Dependencies: no new runtime dependencies. httpx is already an optional dependency of the project; the type hint is declared as a string so the module continues to import cleanly when httpx is absent.

Fixes # (issue)

Type of change

  • New feature (non-breaking change which adds functionality)

How Has This Been Tested?

A new test module, tests/test_parse_response.py, exercises every acceptance
criterion against real httpx.Response objects. The full existing suite was
also run to confirm no regressions.

Reproduce locally:

# install test/runtime deps (stellar-sdk and httpx are required by the suite)
pip install stellar_sdk httpx

# run only the new tests
python -m pytest tests/test_parse_response.py -q

# run the full suite
python -m pytest -q

Result: 91 passed (61 existing + 30 new).

  • Success path — 2xx returns the decoded dict; empty body returns {};
    non-dict JSON and error-key bodies on 2xx are rejected.
  • Status → exception mapping — parametrized coverage for 401/403, 400/422,
    404, 429, 500/502/503/504, and other 4xx (e.g. 418).
  • Field-level errors — extracted from both nested (error.fields) and
    top-level (errors) shapes; None when absent.
  • Decode failure — non-JSON 2xx body raises ShadeError rather than a raw
    JSONDecodeError; non-JSON error bodies still map to the typed exception.
  • Exception context — raw response_body and status_code present on every
    raised exception; message falls back to a sensible default when the body has none.

Test configuration: Python 3.11, pytest, httpx 0.28.1.

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Summary by CodeRabbit

  • New Features

    • Centralized API response parsing to provide clearer, typed errors for common HTTP failure cases.
    • Improved invalid-request errors with support for nuanced field-level validation details.
  • Bug Fixes

    • Prevented low-level JSON decoding errors from leaking by handling decode failures consistently.
    • Ensured consistent exception messages and included status/response context across mapped error types.
  • Tests

    • Added comprehensive automated coverage for success cases, decode behavior, and HTTP-to-error mapping.
  • Chores

    • Updated the runtime HTTP client dependency.

@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

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: 96d1c3d2-e9ac-4166-b1d8-ddfcc0ffa1dc

📥 Commits

Reviewing files that changed from the base of the PR and between 5180078 and 365f5a3.

⛔ Files ignored due to path filters (1)
  • poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • pyproject.toml
  • src/shade/errors.py
  • src/shade/http.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • pyproject.toml
  • src/shade/errors.py
  • src/shade/http.py

📝 Walkthrough

Walkthrough

Adds unified response parsing in src/shade/http.py, extends InvalidRequestError to distinguish omitted and explicit field_errors, adds httpx as a runtime dependency, and covers the response-mapping behavior with new tests.

Unified Response Parser

Layer / File(s) Summary
InvalidRequestError field_errors contract
src/shade/errors.py
Adds _UNSET and updates InvalidRequestError to distinguish omitted field_errors from explicit values while documenting param and field_errors behavior.
_parse_response and helper extractors
src/shade/http.py, pyproject.toml
Imports ShadeError, adds _error_message and _field_errors, implements _parse_response, and adds the httpx dependency used by the new response handling path.
test_parse_response coverage
tests/test_parse_response.py
Adds tests for success cases, decode failures, status-to-exception mapping, field error extraction, Retry-After parsing, and exception context.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • codebestia

🐇 I hop through JSON, neat and bright,
With status codes tucked left and right.
Field errors bloom, then tests all sing,
And raw decode woes lose their sting.
The parser now lands on steady paws —
A tidy hop of protocol laws.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding a unified response parser.
Description check ✅ Passed The PR description follows the template and includes summary, issue reference, type, testing, and checklist sections.
Linked Issues check ✅ Passed The code and tests implement the unified parser, exception mapping, body/status preservation, and 2xx error handling required by #12.
Out of Scope Changes check ✅ Passed No unrelated code changes are evident beyond the parser implementation, tests, and supporting dependency update.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@codebestia

Copy link
Copy Markdown
Contributor

Hello @KodeSage
Please fix the CI.

@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!
Thank you for your contribution.

@codebestia codebestia merged commit 0cf3f50 into ShadeProtocol:main Jun 29, 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 unified response parser

2 participants