Skip to content

refactor(api): add response schemas to the thin domains - #296

Merged
harshtandiya merged 2 commits into
developfrom
refactor/api-thin
Jul 29, 2026
Merged

refactor(api): add response schemas to the thin domains#296
harshtandiya merged 2 commits into
developfrom
refactor/api-thin

Conversation

@harshtandiya

@harshtandiya harshtandiya commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #295 — read that first. Diff here is only the four thin domains.

Endpoints now return pydantic models and throw named errors instead of hand-built dicts and generic 417s.

def get_user_info() -> UserInfoResponse | GuestInfoResponse:
def get_enabled_languages() -> list[LanguageOption]:
def get_campaign_details(campaign: str) -> CampaignResponse:
def get_my_proposals() -> list[ProposalListItem]:

Three modelling calls, all made to keep the wire byte-identical:

  • get_user_info needs two models. Guest gets 2 keys, logged-in user gets 10. One model with optionals would add "name": null, "roles": null, … to the guest payload.
  • Settings flags typed int, not bool. They're Check fields travelling as 0/1.
  • roles stays an untyped list. It holds child-table documents that serialize as full docs.

Errors carry their own copy (base class is in #295):

class AlreadyRegistered(Conflict):
    title = _lt("Already Registered")
    message = _lt("You have already registered your interest in this campaign.")

AlreadyRegistered.throw()

Two things I deliberately left alone:

  • CRMNotAvailable is 400, not 5xx — frappe/app.py:415 writes an Error Log for anything ≥ 500, and a missing app would log on every click.
  • Login-required and campaign-not-found keep frappe.AuthenticationError / frappe.DoesNotExistError. Right status, clear message, wrapping adds nothing.
  • register_campaign_interest still doesn't check campaign.enabled (only get_campaign_details does). Extracting a shared get_campaign() made adding it tempting; kept behaviour-neutral. Probably a real gap — separate call.

Checks: 22 tests green (2 campaign cases skip, Buzz Campaign won't insert without the crm app). Payloads captured before/after via orjson_dumps(..., default=json_handler) are byte-identical, including datetime as 2026-07-08 00:30:45.683135 rather than RFC-3339. ruff / pre-commit / full bench run-tests --app buzz clean.

🤖 Generated with Claude Code

@greptile-apps greptile-apps 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.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps 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.

Your trial has ended. Reactivate Greptile to resume code reviews.

@harshtandiya

harshtandiya commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto updated #295 — the BuzzAPIError base moved there, since that PR introduces api/exceptions.py. This one now only has the per-domain error classes.

@harshtandiya harshtandiya added the backport main backport to main branch label Jul 29, 2026
Base automatically changed from refactor/api-move to develop July 29, 2026 15:55
harshtandiya and others added 2 commits July 29, 2026 22:06
auth, account, campaigns and proposals hand-built dicts with no declared
contract. Each now returns a pydantic model extending APIResponse, so the
response shape is the type signature.

get_user_info keeps two models rather than one: a guest sees 2 keys and a
logged-in user 10, and a merged model would have added null fields to the
guest payload. Settings flags stay typed int because they travel as 0/1.

ProposalListItem moves to proposals/schemas.py and extends APIResponse,
which drops its manual model_dump() call.

Invalid-language and disabled-campaign errors now carry real status codes
(400, 409) instead of the generic 417.

Payloads were captured before and after and are byte-identical.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Each error condition in these two domains now has a class holding its own
title and message, instead of a string at the throw site:

    UnknownLanguage.throw(language_code=language_code)
    AlreadyRegistered.throw()

CRMNotAvailable stays in the 4xx range on purpose. A missing app is a site
configuration gap, and a 5xx would write an Error Log on every click.

Login-required and campaign-not-found keep using frappe.AuthenticationError
and frappe.DoesNotExistError — both already carry the right status and a
clear message, so wrapping them would add classes without adding meaning.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@greptile-apps greptile-apps 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.

Your trial has ended. Reactivate Greptile to resume code reviews.

@harshtandiya
harshtandiya merged commit 11e7d6f into develop Jul 29, 2026
7 checks passed
@harshtandiya
harshtandiya deleted the refactor/api-thin branch July 29, 2026 16:43
@github-actions

Copy link
Copy Markdown
Contributor

Backport failed for main, because it was unable to cherry-pick the commit(s).

Please cherry-pick the changes locally and resolve any conflicts.

git fetch origin main
git worktree add -d .worktree/backport-296-to-main origin/main
cd .worktree/backport-296-to-main
git switch --create backport-296-to-main
git cherry-pick -x 11e7d6feeb7c1c25f8ba9b2fd54914ecd8e2ba6d

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport main backport to main branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant