Skip to content

fix(control-plane): map generated ApiException to typed OtariError#21

Draft
njbrake wants to merge 1 commit into
mainfrom
fix/control-plane-error-mapping
Draft

fix(control-plane): map generated ApiException to typed OtariError#21
njbrake wants to merge 1 commit into
mainfrom
fix/control-plane-error-mapping

Conversation

@njbrake

@njbrake njbrake commented Jun 25, 2026

Copy link
Copy Markdown
Member

Note: this PR was drafted by Claude via back-and-forth with @njbrake. The reasoning and decisions are his; the prose is Claude's.

Description

Control-plane resources (keys, users, budgets, pricing, usage) called the generated client directly, so any HTTP error surfaced as the raw otari._client.exceptions.ApiException rather than a typed otari.errors.OtariError. The inference path already maps generated exceptions in client.py, so the two surfaces had an inconsistent error contract: a bad master key on keys.list() raised an ApiException (and a full CLI traceback), while the inference path raised a clean AuthenticationError.

This makes the control plane match the inference path:

  • src/otari/_base.py: extract the existing mapping into module-level map_api_exception and extract_detail helpers (behavior-identical move). _BaseOtariClient._map_api_exception / _extract_detail now delegate to them, so the inference and streaming paths are unchanged.
  • src/otari/control_plane.py: add a small _translate decorator that catches ApiException and re-raises map_api_exception(exc), applied to all 22 ergonomic aliases. The raw generated surface is intentionally left unwrapped as the escape hatch.
  • tests/unit/test_control_plane_aliases.py: parametrized test asserting each resource alias surfaces a typed AuthenticationError (not a raw ApiException) on a 401, with status_code and detail preserved.

No public API change: the ergonomic aliases keep their signatures and return types; only the exception type on failure changes (raw ApiException becomes typed OtariError).

PR Type

  • New Feature
  • Bug Fix
  • Refactor
  • Documentation
  • Infrastructure / CI

Relevant issues

Fixes #20.

Checklist

  • I understand the code I am submitting.
  • I have added or updated tests that cover my change (tests/unit/test_control_plane_aliases.py).
  • I ran the Definition of Done checks locally (uv run ruff check ., uv run mypy src/, uv run pytest tests/unit).
  • Documentation was updated where necessary.
  • If the API contract changed, I regenerated the OpenAPI spec (n/a: no generated-core or spec change).

DoD results: ruff clean, mypy --strict clean (8 files), pytest tests/unit 127 passed.

AI Usage

  • No AI was used.
  • AI was used for drafting/refactoring.
  • This is fully AI-generated.

AI Model/Tool used: Claude Code (Claude Opus 4.8)

Any additional AI details you'd like to share: Generated via back-and-forth with @njbrake; the diagnosis and decisions are his.

  • I am an AI Agent filling out this form (check box if true)

Control-plane resources called the generated client directly, so HTTP errors
surfaced as the raw ApiException instead of a typed OtariError (the inference
path already maps these in client.py). Extract the mapping into module-level
map_api_exception/extract_detail helpers and route every control-plane
ergonomic alias through a _translate decorator; the raw escape hatch is left
unwrapped. Adds a parametrized unit test across all five resources.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

Control-plane methods leak the raw generated ApiException instead of a typed OtariError

1 participant