Skip to content

chore: prune dead code, tighten typing, add (method, path) parity test#3

Merged
caballeto merged 10 commits into
mainfrom
fix/end-1162-strict-typing-spec-parity
Apr 20, 2026
Merged

chore: prune dead code, tighten typing, add (method, path) parity test#3
caballeto merged 10 commits into
mainfrom
fix/end-1162-strict-typing-spec-parity

Conversation

@caballeto
Copy link
Copy Markdown
Member

Summary

  • END-1082 Remove dead unwrap_single helper and stop generating the unused CursorPage model (the API now returns concrete cursor pages per resource so the generic was unreachable).
  • _http.py / _pagination.py: tighten typing — explicit dict[str, Any] annotations, drop the implicit-Any return paths flagged by mypy strict.
  • Mirror the SDK-JS spec-paths test in tests/test_spec_parity.py with a (method, path) check for every resource the SDK calls. Same fix applied: incidents.delete() (non-existent endpoint) is removed; the rest of the table now lines up with the OpenAPI spec.
  • Refresh test_negative_validation.py / test_schemas.py to reflect the optional/nullable fields the API actually emits (ResolveIncidentRequest, CreateNotificationPolicyRequest, etc.) so previously stale assertions pass again. New tests/test_typing.py covers shared helpers.
  • Refresh docs/openapi/monitoring-api.json to match the new mini spec (incl. the new /api/v1/alert-channels/{id} GET endpoint).
  • Regenerate _generated.py from the refreshed spec.

Test plan

  • uv run --frozen ruff format --check
  • uv run --frozen ruff check
  • uv run --frozen mypy src tests (strict)
  • uv run --frozen pytest -q (681 tests pass)
  • CI green
  • Surface integration test (mono#246 runs make test-surface SURFACE=sdk-python against this branch)

Made with Cursor

Replace Python preprocess_spec.py with call to shared Node.js CLI
tool. Fixes missing setRequiredOnAllOfMembers and flattenCircularOneOf
steps, and removes divergent _REQUEST_PREFIXES exemption logic.

Made-with: Cursor
Every resource method that accepts a body now validates it through
the Pydantic model before sending. Raw dicts are rejected in
_serialize_body to prevent bypassing validation. Dicts can still
be used by passing them through validate_request() first.

Made-with: Cursor
- Replace all Any return/param types in _http.py with concrete types (END-1078)
- Remove dead unwrap_single function
- Add 31 descriptive enum aliases in types.py (END-1079)
- Re-export all enum aliases from __init__.py for stable public API

Made-with: Cursor
Resolves END-1162.

Resource method signatures previously accepted only the typed Pydantic
model (e.g. CreateMonitorRequest), but the runtime ``validate_request``
helper coerces dicts via ``model_validate`` and the README/client
docstrings show dict-style usage. Under ``mypy --strict`` those examples
were type errors.

Introduce ``RequestBody[T] = T | Mapping[str, Any]`` in ``_validation``
and apply it to every mutating resource method body parameter so dict
literals type-check while typed model instances remain a first-class
option. Re-export ``RequestBody`` and the previously omitted
``AddIncidentUpdateRequest`` from the package root and ``types`` module.

Add ``tests/test_spec_parity.py`` to lock the SDK against logical drift
from ``docs/openapi/monitoring-api.json``:

* every public DTO must exist in the spec (catches removals/renames),
* every request DTO's required fields must be a superset of the spec's,
* every mutating method body annotation must include ``Mapping[str, Any]``
  (catches future regressions to the dict ergonomics fix), and
* every top-level path the SDK calls must exist in the spec.

All 681 tests pass; ``mypy --strict src/`` is clean; manual smoke check
confirms dict-style ``client.monitors.create({...})`` now type-checks.

Made-with: Cursor
- END-1082 Remove dead unwrap_single helper and stop generating the
  unused CursorPage model (the API now returns concrete cursor pages
  per resource so the generic was unreachable). Regenerate _generated.py
  from the refreshed vendored spec.
- _http.py / _pagination.py: tighten typing — explicit dict[str, Any]
  annotations, drop the implicit Any return paths flagged by mypy.
- Mirror the SDK-JS spec-paths test in tests/test_spec_parity.py with
  a (method, path) check for every resource the SDK calls. Same fix
  applied: incidents.delete() (non-existent endpoint) is removed; the
  rest of the table now lines up with the OpenAPI spec.
- Refresh test_negative_validation.py / test_schemas.py to reflect the
  optional/nullable fields the API actually emits (ResolveIncidentRequest,
  CreateNotificationPolicyRequest, etc.) so previously stale assertions
  pass again. Add tests/test_typing.py for shared helpers.
- Refresh docs/openapi/monitoring-api.json to match the new mini spec
  (incl. /api/v1/alert-channels/{id} GET endpoint).

ruff format --check, ruff check, mypy --strict (src + tests), pytest
(681 tests) all green.

Made-with: Cursor
Updates docs/openapi/monitoring-api.json after the upstream API change
that reverted the explicit oneOf annotation on MonitorConfig — the
property-level inlined oneOf preserves subtype validation in the
generated models.

Made-with: Cursor
…details

Picks up the upstream @devhelm/openapi-tools update that inlines
discriminator subtypes and rewrites parents as oneOf+discriminator. The
generated Pydantic models now use:

  type: Literal["bearer"]   (and "basic", "header", "api_key")
  check_type: Literal["http"]   (and "tcp", "icmp", "dns", "mcp_server")

with `Annotated[Union[...], Field(discriminator="type")]` on the parent
union (MonitorAuthConfig and CheckTypeDetailsDto). Pydantic's
discriminator-aware union resolution now picks the right subtype in O(1)
on validation, vs the previous "try every member, keep the first that
parses" fallback that misclassified shape-equivalent subtypes (e.g.
BearerAuthConfig vs BasicAuthConfig — both `{vault_secret_id}` only).

Spec refresh also propagates the regular field-level fixes from the
upstream typing audit. ruff, mypy strict, and pytest (681 tests) pass.

Made-with: Cursor
Surface the typed request model so downstream surfaces (MCP server, tests)
can import it from `devhelm.types` directly instead of reaching into
`devhelm._generated`.

Made-with: Cursor
Regenerate the Pydantic models against the updated OpenAPI spec. The
mono-side preprocessor now inlines nullable references to deduction-
based polymorphic parents, so `UpdateMonitorRequest.config` resolves
to a proper `DnsMonitorConfig | HttpMonitorConfig | ... | None` union
instead of a vacuous reference to the empty `MonitorConfig` base.

No source changes; generated code only.

Made-with: Cursor
@caballeto caballeto merged commit 4c83e7f into main Apr 20, 2026
4 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.

1 participant