Skip to content

chore: regen API types + 5 DevEx round-3 SDK fixes#23

Merged
caballeto merged 7 commits into
mainfrom
chore/update-api-types-20260505205920
May 5, 2026
Merged

chore: regen API types + 5 DevEx round-3 SDK fixes#23
caballeto merged 7 commits into
mainfrom
chore/update-api-types-20260505205920

Conversation

@github-actions
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot commented May 5, 2026

Builds on the auto-regenerated Pydantic types (which pick up the new MCP and API values on MonitorManagedBy) with five DevEx round-3 ergonomics fixes that surfaced while exercising the SDK from a fresh laptop.

Summary

# Round-3 ID Fix Commit
1 (auto-regen) Pick up MCP / API on MonitorManagedBy f8eb9eb
2 P1.Bug5 Allow snake_case kwargs via populate_by_name=True 97cbc9f
3 P0.Bug4 Pydantic Field(discriminator=…) on tagged unions — 161 errors → 1 19b182f
4 P1.Bug14 Expose devhelm.__version__ 3bcc9f6
5 P1.Bug7 client.monitors.list(enabled=…, type=…, …) filter kwargs 66bec7b
6 P1.Bug9 Banner on MonitorDto noting currentStatus removal (in-scope half) cc358fa

Detail

#2populate_by_name=True

extra='forbid' plus validation_alias=camelCase rejected snake_case kwargs, so CreateMonitorRequest(frequency_seconds=60, managed_by='API') raised ValidationError. Patched the inject_strict_config.py template + every generated model_config block so models accept both spellings now.

#3 — Discriminator on tagged unions

A typo on operator='eq' (should be 'equals') inside a status_code assertion previously emitted 161 validation errors because Pydantic walked every arm of the 41-member assertion union. Extended inject_strict_config.py with a post-pass that detects classes whose first payload field is a single-member Literal[…] and wraps qualifying parenthesized unions in Annotated[Union[…], Field(discriminator=…)]. Applies to CreateAssertionRequest.config, UpdateAssertionRequest.config, alert-channel configs in Create/Test/UpdateAlertChannelRequest, and MonitorTestRequest.config. Same input now emits 1 error.

#4devhelm.__version__

python -c "import devhelm; print(devhelm.__version__)" raised AttributeError. Resolves it from importlib.metadata.version("devhelm") at import time (with "unknown" fallback for editable installs) and lists it in __all__. Backed by TestSdkVersionExposed in tests/test_client.py.

#5client.monitors.list(...) filters

Surfaces every documented GET /api/v1/monitors query param (enabled, type, managed_by, tags, search, environment_id) as keyword-only arguments on Monitors.list() and Monitors.list_page(). Threaded through _pagination.fetch_all_pages / fetch_page via a new extra_params keyword so future filter additions land in one place. Pagination keys still win in the merge so the iterator's invariants stay intact. Backed by TestMonitorsListFilters in tests/test_client.py.

#6currentStatus deprecation banner

The docs-quickstart shows monitor.currentStatus, but that field was removed from the DTO and never came back. Added a class-level docstring banner via inject_strict_config.py (keyed by class name in a CLASS_BANNERS table) so the note shows in IDE hovers and survives regen. Restoring current_status in the spec is upstream mono work, tracked separately.

Verification

uv run pytest -q              # 723 passed
uv run mypy src/              # Success: no issues found in 24 source files
uv run ruff check src/ tests/ # All checks passed!
uv run ruff format --check    # 34 files already formatted
$ grep -A 6 'class ManagedBy' src/devhelm/_generated.py
class ManagedBy(StrEnum):
    dashboard = "DASHBOARD"
    cli = "CLI"
    terraform = "TERRAFORM"
    mcp = "MCP"
    api = "API"
$ uv run python -c "
from devhelm._generated import CreateAssertionRequest
try:
    CreateAssertionRequest.model_validate({'config': {'type': 'status_code', 'expected': '200', 'operator': 'eq'}})
except Exception as e:
    print(f'errors: {e.error_count()}')
"
errors: 1

Out of scope

  • No pyproject.toml version bump — that ships with the next release.
  • Restoring current_status to MonitorDto requires editing mono; tracked as upstream follow-up.

github-actions Bot and others added 6 commits May 5, 2026 20:59
Generated Pydantic models reject snake_case kwargs because
extra='forbid' + validation_alias=camelCase is set without
populate_by_name=True. Callers had to write
CreateMonitorRequest(frequencySeconds=60, managedBy='API') even
though the rest of the SDK is snake_case Python.

Patch every model_config block (and the inject_strict_config.py
template that emits them) so models accept both
frequency_seconds=60 and frequencySeconds=60. Round-3 DevEx fix
P1.Bug5.

Co-authored-by: Cursor <cursoragent@cursor.com>
Generated unions like CreateAssertionRequest.config (41 members) had
no discriminator, so a typo on operator='eq' (should be 'equals')
emitted 161 validation errors as Pydantic tried every union arm.

Extend inject_strict_config.py with a post-pass that:

  1. Identifies classes whose first payload field is a single-member
     Literal[...] (the OpenAPI ``type``/``channel_type``/``check_type``
     polymorphic-tag pattern), and
  2. Rewrites parenthesized union fields whose every member shares
     the same tag into Annotated[Union[...], Field(discriminator=...)].

After the patch, the same typo emits 1 error instead of 161, and
``ruff format`` reflows the long Annotated lines so the file stays
formatter-clean. Round-3 DevEx fix P0.Bug4.

Co-authored-by: Cursor <cursoragent@cursor.com>
``python -c 'import devhelm; print(devhelm.__version__)'`` previously
raised ``AttributeError`` because the top-level package didn't define
the attribute. Resolve it from ``importlib.metadata.version("devhelm")``
at import time so a single source of truth (pyproject.toml) flows
through to user code, and add ``"__version__"`` to ``__all__`` for
``from devhelm import *`` consumers. Round-3 DevEx fix P1.Bug14.

Co-authored-by: Cursor <cursoragent@cursor.com>
``client.monitors.list()`` previously took no parameters, forcing
users to drop down to ``httpx`` to use the documented
``GET /api/v1/monitors`` filters. Surface ``enabled``, ``type``,
``managed_by``, ``tags``, ``search``, and ``environment_id`` as
keyword-only arguments on both ``list()`` and ``list_page()``,
projecting snake_case onto the camelCase wire names the API expects.

Threaded through ``fetch_all_pages`` and ``fetch_page`` via a new
``extra_params`` keyword so future filter additions land in one place;
pagination keys still win in the merge so the iterator's invariants
stay intact. Round-3 DevEx fix P1.Bug7.

Co-authored-by: Cursor <cursoragent@cursor.com>
The docs-quickstart at https://docs.devhelm.io/sdk/python/quickstart
shows ``monitor.currentStatus`` but the field was removed from the
DTO and never came back. Pre-existing SDK users hit this on upgrade
and are stuck guessing how to recover the live status of a monitor.

Inject a class-level docstring banner via ``inject_strict_config.py``
so the note shows up in IDE hovers and survives regeneration. The
banner table is keyed by class name so future runtime-affecting
upstream changes can land here without bloating the typegen.

Restoring ``current_status`` properly is upstream work in mono;
tracked separately. Round-3 DevEx fix P1.Bug9 (in-scope half).

Co-authored-by: Cursor <cursoragent@cursor.com>
@caballeto caballeto changed the title chore: update generated API types chore: regen API types + 5 DevEx round-3 SDK fixes May 5, 2026
@caballeto caballeto merged commit c6b1e52 into main May 5, 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