Skip to content

chores: creates pydantic base model for response objects - #47

Merged
codebestia merged 1 commit into
ShadeProtocol:mainfrom
KodeSage:feat/create_pydantic
Jul 24, 2026
Merged

chores: creates pydantic base model for response objects#47
codebestia merged 1 commit into
ShadeProtocol:mainfrom
KodeSage:feat/create_pydantic

Conversation

@KodeSage

@KodeSage KodeSage commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Closes #36

Description

Adds ShadeObject, the common Pydantic base class that every Shade API response model will inherit from. It provides dict round-tripping (from_dict / to_dict), a readable __repr__, and translation of Pydantic validation failures into the SDK's own InvalidRequestError.

Motivation: response models were each going to hand-roll parsing, validation, and __repr__. Building on pydantic.BaseModel removes that boilerplate and gives us free type coercion (e.g. a stringy "500" from the API becomes an int), while extra="allow" means a field added server-side never breaks an older SDK — the unknown value is preserved and re-emitted on to_dict().

Design notes worth a reviewer's attention:

  • to_dict() defaults to by_alias=True so output matches the API wire format. Round-tripping still works because populate_by_name=True lets models accept either the alias or the Python field name.
  • __init__ is overridden as well as from_dict. Wrapping only from_dict would still leak a raw ValidationError out of direct construction (Payment(id="pay_1")), so both paths funnel through the same translation.
  • The error translation reuses the shape InvalidRequestError already carries from API responses: field_errors becomes {location: [messages]} and param is set to the first offending field.
  • _id_field is a ClassVar (default "id") that subclasses override when their primary key is named differently, e.g. _id_field = "invoice_id". When the field is absent, repr degrades to <Account> rather than raising.

Dependencies: this adds pydantic ^2.0 to pyproject.toml and poetry.lock. It is the SDK's first runtime dependency beyond httpx and stellar-sdk.

Fixes #36

Type of change

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

How Has This Been Tested?

Added tests/test_models_base.py (12 tests) covering the acceptance criteria, exercised against three sample subclasses: a plain model, one with an aliased field and overridden _id_field, and one with no ID field at all.

  • Round-trippingfrom_dict(m.to_dict()) preserves data, including datetime values and aliased fields
  • Alias handling — aliased fields are accepted under both the alias (invoiceId) and the field name (invoice_id), and dump under the alias
  • Unknown fields — an unrecognised API field is accepted without raising, readable as an attribute, and present in to_dict()
  • Type coercion{"amount": "500"} yields amount == 500
  • __repr__<Payment id='pay_1'>, <Invoice invoice_id='inv_1'>, and <Account> for a model with no ID field
  • Error translation — a missing required field and a bad type both raise InvalidRequestError (never ValidationError), from from_dict and from direct construction, with param and field_errors populated; non-dict input to from_dict is rejected with a clear message

Reproduce with:

poetry install
poetry run pytest
poetry run flake8 src/shade/models tests/test_models_base.py --max-line-length=127

Test configuration: Python 3.10 (matches CI), pydantic 2.13.4, macOS. Full suite is 144 passed; flake8 reports 0 findings on the new files.

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 — no user-facing docs updated yet; ShadeObject is an internal base class with no concrete models on it so far. Happy to add a README section once the first models land.
  • 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

Note for reviewers: there is a pre-existing empty src/shade/base.py sitting alongside the new src/shade/models/base.py. I left it untouched to keep this PR scoped, but the two names are easy to confuse — worth deleting in a follow-up if nothing is planned for it.

Summary by CodeRabbit

  • New Features

    • Added publicly accessible models for representing Shade API response objects.
    • Added convenient conversion from dictionaries and back to serialized data, including support for field aliases.
    • Unknown response fields are preserved to improve compatibility with future API changes.
    • Added clearer validation errors for malformed response data.
    • Added readable model representations with relevant identifiers when available.
  • Tests

    • Added coverage for model conversion, validation, aliases, type handling, and preserved fields.

@coderabbitai

coderabbitai Bot commented Jul 22, 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: ec9880e6-98c3-4cbe-9974-bd474970d2ed

📥 Commits

Reviewing files that changed from the base of the PR and between 0cf3f50 and 89fde73.

⛔ Files ignored due to path filters (1)
  • poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • pyproject.toml
  • src/shade/__init__.py
  • src/shade/models/__init__.py
  • src/shade/models/base.py
  • tests/test_models_base.py

📝 Walkthrough

Walkthrough

Adds Pydantic as a runtime dependency and introduces the exported ShadeObject base model with serialization, alias handling, extra-field preservation, readable representations, and conversion of validation failures to InvalidRequestError, covered by unit tests.

Changes

ShadeObject model

Layer / File(s) Summary
Public model contract
pyproject.toml, src/shade/models/__init__.py, src/shade/__init__.py
Adds Pydantic and exports ShadeObject from shade.models and shade.
ShadeObject implementation
src/shade/models/base.py
Adds configurable Pydantic validation, from_dict, alias-based to_dict, identifier-aware repr, and structured InvalidRequestError conversion.
Model behavior validation
tests/test_models_base.py
Tests defaults, round trips, aliases, unknown fields, type coercion, representations, and validation failures.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant ShadeObject
  participant Pydantic
  participant InvalidRequestError
  Caller->>ShadeObject: from_dict(data)
  ShadeObject->>Pydantic: Validate model data
  Pydantic-->>ShadeObject: Model or ValidationError
  ShadeObject->>InvalidRequestError: Convert validation failure
  ShadeObject-->>Caller: Model or InvalidRequestError
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately describes the main change: adding a Pydantic base model for response objects.
Description check ✅ Passed The description matches the template well, including summary, issue reference, type of change, testing, dependencies, and checklist.
Linked Issues check ✅ Passed The PR implements the #36 requirements: serialization helpers, round-tripping, unknown fields, readable repr, and InvalidRequestError translation.
Out of Scope Changes check ✅ Passed The changes stay within scope, limited to the base model, exports, dependency addition, and tests supporting the linked issue.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

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

@codebestia
codebestia merged commit 1704937 into ShadeProtocol:main Jul 24, 2026
2 checks passed
giftexceed added a commit to giftexceed/shade-python that referenced this pull request Jul 24, 2026
Rebuild the Merchant model on the shared pydantic ShadeObject introduced on
main (ShadeProtocol#47), replacing the standalone plain-Python model and base.

- Move src/shade/merchant.py -> src/shade/models/merchant.py and delete the
  now-redundant src/shade/base.py.
- Map camelCase JSON to snake_case fields with pydantic Field(alias=...);
  from_dict / to_dict / repr come from ShadeObject.
- Enforce validation via pydantic: StrictBool for active/verified (no
  silent coercion of strings like "false"), a Stellar public-key
  field_validator on address, and a before-validator rejecting boolean
  merchant_id (which pydantic would otherwise coerce to 1/0). All surface as
  InvalidRequestError through the base.
- Export Merchant from shade.models and the top-level package.
- Update tests: unknown keys are now preserved (extra="allow"), the
  merchant_id error param is the "merchantId" alias, and add a boolean
  merchant_id rejection case.
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 base model class with serialization helpers

2 participants