feat: spec-level Postel's-Law tolerant readers + codegen-stable enum aliases#29
Merged
Merged
Conversation
…aliases Pipeline rewrite. `scripts/typegen.sh` now runs through the shared `@devhelm/openapi-tools` preprocessor (which calls `relaxResponseEnumsInSpec`) before `datamodel-codegen` sees the spec. Response-DTO multi-value enum fields decode as plain `str` — so `MonitorDto.type`, `IncidentDto.status`, etc. accept future API values added after this SDK version was built. Request DTOs keep their StrEnum classes and `Field(discriminator=…)` wiring for strict authoring-time validation. `scripts/emit_response_enums.py` (new) emits `_enums.py` from the *un-relaxed* spec, with one `Literal[...]` alias per `(SchemaName, propertyName)` pair (164 aliases). Names are stable across spec evolution because they don't depend on `datamodel-codegen`'s suffixed names (`Status1`…`Status15`, `Type1`…`Type6`) which shifted on every spec change. `types.py` now imports every public enum alias (`IncidentStatus`, `MonitorType`, `CustomDomainStatus`, `ConfirmationPolicyType`, …) from `_enums.py` so the public API stays stable while the runtime stays Postel-tolerant. Hand-coded `ConfirmationPolicyType` literal removed in favour of the auto-generated alias (caught a wrong value in the process — the literal is `"multi_region"`, not `"confirmation"`). Negative tests around response-DTO enum rejection are flipped to assert acceptance (tolerance). Request-DTO negative tests stay strict. See `mini/runbooks/api-contract.md` § 3.2 for the cross-surface design and `_enums.py` rationale. Coverage: 742 / 742 tests pass; ruff + mypy clean (26 source files). Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
devhelmSDK now decodes response-DTO multi-value enums as plainstr—MonitorDto.type,IncidentDto.status, etc. accept futureAPI values added after this SDK version was built. Request DTOs keep
their
StrEnumclasses andField(discriminator=…)wiring for strictauthoring-time validation.
Implementation
Two layers:
Spec-level relaxation —
scripts/typegen.shruns the shared@devhelm/openapi-toolspreprocessor (relaxResponseEnumsInSpec)before
datamodel-codegensees the spec. Response-DTO enum fieldsland in
_generated.pyas plainstrannotations.Stable public enum aliases —
scripts/emit_response_enums.py(new) reads the un-relaxed spec and emits
src/devhelm/_enums.pywith one
Literal[...]alias per(SchemaName, propertyName)pair. Names are stable across spec evolution because they don't
depend on
datamodel-codegen's suffixed names(
Status1…Status15,Type1…Type6) which used to shift onevery spec change.
types.pyis rewritten to import every public alias(
IncidentStatus,MonitorType,CustomDomainStatus,ConfirmationPolicyType, …) from_enums.py. The public API surfaceis unchanged.
Customer impact: the public type names are unchanged but they are
now
Literal[...]aliases rather thanStrEnumclasses. Typenarrowing behavior is equivalent for static checkers; runtime
comparisons like
x == AlertSensitivity.ALLneed to be rewrittento plain string comparisons (
x == \"ALL\").Cross-surface design:
mini/runbooks/api-contract.md§ 3.2.Tests
ruff checkclean,ruff format --checkclean.mypy src/clean (26 source files)._generated.pyand_enums.py.Test plan
src/devhelm/_generated.py: response DTOs (e.g.MonitorDto.type) are typedstr; request DTOs (e.g.CreateMonitorRequest.type) keep theirStrEnumfield types.src/devhelm/_enums.py: 164 aliases includingMonitorDtoType,IncidentDtoStatus,ConfirmationPolicyType.Made with Cursor