Skip to content

Error mapping: 422 / 409 / 408 / 410 fall through to bare KalshiError (+ tests for fallback) #201

Closed

Description

@TexasCoding

Summary

_map_error only branches on 400 → KalshiValidationError, 401/403 → KalshiAuthError, 404 → KalshiNotFoundError, 429 → KalshiRateLimitError, and >=500 → KalshiServerError. Everything else falls through to bare KalshiError:

  • 422 Unprocessable Entity — semantic validation (e.g. count > position_limit on order create). Kalshi uses 422 for the same recovery flow as 400 but callers except KalshiValidationError will miss it.
  • 409 Conflict — duplicate client_order_id, double-cancel races. No way to retry the right way without parsing the message.
  • 408 Request Timeout — server-side timeout signal, arguably retryable.
  • 410 Gone — resource deleted permanently; same handling as 404 in many flows.

No test asserts the fallthrough (type(err) is KalshiError, not a subclass), so a future refactor that re-classifies (e.g. 422 → KalshiValidationError) breaks no test.

_map_error body fallback chain (body.get('message') → body.get('error') → response.text → f'HTTP {status}') also has no test for the terminal 'HTTP {status}' branch (empty body AND no JSON).

Location

  • kalshi/_base_client.py:36-74 — _map_error
  • tests/test_client.py:46-156 — TestErrorMapping

Recommended fix

Code

  1. Route 422 to KalshiValidationError (same shape as 400).
  2. Add KalshiConflictError(KalshiError) for 409.
  3. Either route 408 to KalshiServerError with status_code=408 (so the retry policy can retry it under the separate "Cloudflare 5xx" change) or document the current pass-through behavior.
  4. Document the full 1:1 status → exception table in docs/errors.md.

Tests

@pytest.mark.parametrize("status,exc_type", [
    (400, KalshiValidationError),
    (422, KalshiValidationError),   # new
    (409, KalshiConflictError),     # new
    (401, KalshiAuthError),
    (403, KalshiAuthError),
    (404, KalshiNotFoundError),
    (429, KalshiRateLimitError),
    (408, KalshiError),             # pin current behavior or assert new
    (410, KalshiError),
    (451, KalshiError),
    (500, KalshiServerError),
    (599, KalshiServerError),
])
def test_status_maps_to_typed_exception(status, exc_type): ...

# Body fallback chain
def test_empty_body_falls_back_to_http_status_message(): ...
def test_html_body_propagates_as_message(): ...
def test_body_error_secondary_key_used(): ...

Severity & category

medium / correctness, testing

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions