devhelm 1.0.0__tar.gz → 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {devhelm-1.0.0 → devhelm-1.1.0}/PKG-INFO +1 -1
- {devhelm-1.0.0 → devhelm-1.1.0}/docs/openapi/monitoring-api.json +1 -1
- {devhelm-1.0.0 → devhelm-1.1.0}/pyproject.toml +1 -1
- devhelm-1.1.0/scripts/emit_response_enums.py +166 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/scripts/typegen.sh +10 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/src/devhelm/__init__.py +2 -0
- devhelm-1.1.0/src/devhelm/_enums.py +569 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/src/devhelm/_generated.py +108 -342
- devhelm-1.1.0/src/devhelm/types.py +310 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/tests/test_negative_validation.py +38 -40
- {devhelm-1.0.0 → devhelm-1.1.0}/tests/test_schemas.py +2 -2
- {devhelm-1.0.0 → devhelm-1.1.0}/uv.lock +1 -1
- devhelm-1.0.0/src/devhelm/types.py +0 -374
- {devhelm-1.0.0 → devhelm-1.1.0}/.github/workflows/ci.yml +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/.github/workflows/release.yml +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/.github/workflows/spec-check.yml +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/.gitignore +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/LICENSE +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/Makefile +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/README.md +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/scripts/inject_strict_config.py +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/scripts/regen-from.sh +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/scripts/release.sh +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/src/devhelm/_errors.py +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/src/devhelm/_http.py +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/src/devhelm/_pagination.py +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/src/devhelm/_validation.py +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/src/devhelm/client.py +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/src/devhelm/py.typed +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/src/devhelm/resources/__init__.py +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/src/devhelm/resources/alert_channels.py +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/src/devhelm/resources/api_keys.py +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/src/devhelm/resources/dependencies.py +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/src/devhelm/resources/deploy_lock.py +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/src/devhelm/resources/environments.py +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/src/devhelm/resources/forensics.py +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/src/devhelm/resources/incidents.py +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/src/devhelm/resources/maintenance_windows.py +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/src/devhelm/resources/monitors.py +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/src/devhelm/resources/notification_policies.py +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/src/devhelm/resources/resource_groups.py +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/src/devhelm/resources/secrets.py +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/src/devhelm/resources/status.py +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/src/devhelm/resources/status_pages.py +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/src/devhelm/resources/tags.py +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/src/devhelm/resources/webhooks.py +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/tests/__init__.py +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/tests/run_sdk.py +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/tests/test_client.py +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/tests/test_errors.py +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/tests/test_http.py +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/tests/test_maintenance_windows.py +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/tests/test_spec_parity.py +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/tests/test_typing.py +0 -0
- {devhelm-1.0.0 → devhelm-1.1.0}/tests/test_validation_helpers.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devhelm
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: DevHelm SDK for Python — typed client for monitors, incidents, alerting, and more
|
|
5
5
|
Project-URL: Homepage, https://github.com/devhelmhq/sdk-python
|
|
6
6
|
Project-URL: Repository, https://github.com/devhelmhq/sdk-python.git
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Generate ``src/devhelm/_enums.py`` from the *un-relaxed* OpenAPI spec.
|
|
3
|
+
|
|
4
|
+
Why this exists
|
|
5
|
+
===============
|
|
6
|
+
|
|
7
|
+
Under the spec-level Postel's-Law relaxation
|
|
8
|
+
(see ``mini/runbooks/api-contract.md`` § 3 and the design notes at
|
|
9
|
+
the top of ``../mini/packages/openapi-tools/src/preprocess.ts``)
|
|
10
|
+
multi-value enums on response-shape DTOs are dropped from the
|
|
11
|
+
preprocessed spec before code generation. That makes the runtime
|
|
12
|
+
behaviour tolerant — ``MonitorDto.type`` decodes any string, including
|
|
13
|
+
new wire values added by the API after the SDK was built — but it also
|
|
14
|
+
removes the StrEnum classes that ``types.py`` historically re-exported
|
|
15
|
+
under public-facing names like ``IncidentStatus``,
|
|
16
|
+
``CustomDomainStatus``, and ``MonitorDtoType``.
|
|
17
|
+
|
|
18
|
+
We *also* don't want to depend on ``datamodel-codegen``'s numbered
|
|
19
|
+
suffixes (``Status1``…``Status15``, ``Type1``…``Type6``) for the
|
|
20
|
+
remaining request-side enums — those numbers shift whenever the spec
|
|
21
|
+
gains or loses an enum, which would force a churn of hand-written
|
|
22
|
+
imports in ``types.py`` on every schema evolution.
|
|
23
|
+
|
|
24
|
+
This script eliminates both problems by emitting one ``Literal[...]``
|
|
25
|
+
alias per ``(schemaName, propertyName)`` pair for every named
|
|
26
|
+
multi-value enum in the **un-relaxed** spec. The alias name is
|
|
27
|
+
``<SchemaName><PascalProperty>`` so it is stable across spec evolution
|
|
28
|
+
and independent of codegen numbering. ``types.py`` imports everything
|
|
29
|
+
from the resulting ``_enums.py`` and re-exports under the SDK's
|
|
30
|
+
public-facing aliases (``IncidentStatus``, ``MonitorType``, …).
|
|
31
|
+
|
|
32
|
+
Sequencing matters: the script is invoked from ``scripts/typegen.sh``
|
|
33
|
+
*after* datamodel-codegen has consumed the preprocessed spec, but it
|
|
34
|
+
reads the original (un-relaxed) spec directly so multi-value enums
|
|
35
|
+
survive even on response DTOs.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
from __future__ import annotations
|
|
39
|
+
|
|
40
|
+
import json
|
|
41
|
+
import re
|
|
42
|
+
import sys
|
|
43
|
+
from pathlib import Path
|
|
44
|
+
from typing import Any
|
|
45
|
+
|
|
46
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
47
|
+
SPEC_PATH = ROOT / "docs" / "openapi" / "monitoring-api.json"
|
|
48
|
+
OUTPUT = ROOT / "src" / "devhelm" / "_enums.py"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def pascal_property(name: str) -> str:
|
|
52
|
+
parts = re.split(r"[_\-]", name)
|
|
53
|
+
return "".join(p[:1].upper() + p[1:] for p in parts if p)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def collect_named_enums(
|
|
57
|
+
spec: dict[str, Any],
|
|
58
|
+
) -> dict[str, list[str]]:
|
|
59
|
+
"""Return ``{<SchemaName><PascalProperty>: [values]}`` for every
|
|
60
|
+
multi-value enum on any named schema's property — request, response,
|
|
61
|
+
or anywhere in between. Naming is uniform across request and
|
|
62
|
+
response shapes so consumers can reference a stable alias regardless
|
|
63
|
+
of which side a value travels on.
|
|
64
|
+
"""
|
|
65
|
+
schemas = (spec.get("components") or {}).get("schemas") or {}
|
|
66
|
+
out: dict[str, list[str]] = {}
|
|
67
|
+
|
|
68
|
+
def visit(schema_name: str, properties: dict[str, Any] | None) -> None:
|
|
69
|
+
if not properties:
|
|
70
|
+
return
|
|
71
|
+
for prop_name, prop in properties.items():
|
|
72
|
+
if not isinstance(prop, dict):
|
|
73
|
+
continue
|
|
74
|
+
enum = prop.get("enum")
|
|
75
|
+
# Emit length-1 enums too — those are discriminator tags
|
|
76
|
+
# installed by `inlineDiscriminatorSubtypesWithInfo` and the
|
|
77
|
+
# only way to source the canonical value from the spec
|
|
78
|
+
# rather than hand-coding it. Keeps types.py free of magic
|
|
79
|
+
# strings (e.g. ConfirmationPolicy.type = "multi_region").
|
|
80
|
+
if (
|
|
81
|
+
isinstance(enum, list)
|
|
82
|
+
and len(enum) >= 1
|
|
83
|
+
and all(isinstance(v, str) for v in enum)
|
|
84
|
+
):
|
|
85
|
+
alias = schema_name + pascal_property(prop_name)
|
|
86
|
+
out[alias] = list(enum)
|
|
87
|
+
items = prop.get("items")
|
|
88
|
+
if isinstance(items, dict):
|
|
89
|
+
items_enum = items.get("enum")
|
|
90
|
+
if (
|
|
91
|
+
isinstance(items_enum, list)
|
|
92
|
+
and len(items_enum) >= 1
|
|
93
|
+
and all(isinstance(v, str) for v in items_enum)
|
|
94
|
+
):
|
|
95
|
+
alias = schema_name + pascal_property(prop_name) + "Item"
|
|
96
|
+
out[alias] = list(items_enum)
|
|
97
|
+
|
|
98
|
+
for name, schema in schemas.items():
|
|
99
|
+
# Skip anonymous / lowercase schemas — those are inline types
|
|
100
|
+
# that don't have a stable name and we don't surface them.
|
|
101
|
+
if not name or not name[0].isupper():
|
|
102
|
+
continue
|
|
103
|
+
if not isinstance(schema, dict):
|
|
104
|
+
continue
|
|
105
|
+
visit(name, schema.get("properties"))
|
|
106
|
+
for member in schema.get("allOf") or []:
|
|
107
|
+
if isinstance(member, dict):
|
|
108
|
+
visit(name, member.get("properties"))
|
|
109
|
+
|
|
110
|
+
return out
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def render(aliases: dict[str, list[str]]) -> str:
|
|
114
|
+
lines = [
|
|
115
|
+
'"""Auto-generated enum literal aliases (uniform request + response).',
|
|
116
|
+
"",
|
|
117
|
+
"DO NOT EDIT — regenerated on every ``typegen.sh`` run from the",
|
|
118
|
+
"*un-relaxed* OpenAPI spec. See ``scripts/emit_response_enums.py``",
|
|
119
|
+
"and ``mini/runbooks/api-contract.md`` § 3 for the design.",
|
|
120
|
+
"",
|
|
121
|
+
"Each alias is a ``typing.Literal[...]`` of the wire-format values",
|
|
122
|
+
"the API currently accepts (request-side) or emits (response-side)",
|
|
123
|
+
"for the named ``<SchemaName><Property>`` field. Naming is",
|
|
124
|
+
"stable: it does not depend on ``datamodel-codegen``'s suffixed",
|
|
125
|
+
"names (``Status1``, ``Type5``…) which shift on every spec change.",
|
|
126
|
+
"",
|
|
127
|
+
"Response-DTO fields decode to plain ``str`` in ``_generated.py``",
|
|
128
|
+
"(Postel-tolerant on receive). Request-DTO fields keep strict",
|
|
129
|
+
"validation through the corresponding ``StrEnum`` in",
|
|
130
|
+
"``_generated.py`` (strict on send). These aliases give SDK",
|
|
131
|
+
"callers a single canonical name they can annotate against in",
|
|
132
|
+
"either direction.",
|
|
133
|
+
'"""',
|
|
134
|
+
"",
|
|
135
|
+
"from __future__ import annotations",
|
|
136
|
+
"",
|
|
137
|
+
"from typing import Literal",
|
|
138
|
+
"",
|
|
139
|
+
]
|
|
140
|
+
for alias in sorted(aliases):
|
|
141
|
+
values = aliases[alias]
|
|
142
|
+
rendered = ", ".join(f'"{v}"' for v in values)
|
|
143
|
+
lines.append(f"{alias} = Literal[{rendered}]")
|
|
144
|
+
lines.append("")
|
|
145
|
+
lines.append("__all__ = [")
|
|
146
|
+
for alias in sorted(aliases):
|
|
147
|
+
lines.append(f' "{alias}",')
|
|
148
|
+
lines.append("]")
|
|
149
|
+
lines.append("")
|
|
150
|
+
return "\n".join(lines)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def main() -> int:
|
|
154
|
+
if not SPEC_PATH.exists():
|
|
155
|
+
print(f"error: spec not found at {SPEC_PATH}", file=sys.stderr)
|
|
156
|
+
return 1
|
|
157
|
+
spec = json.loads(SPEC_PATH.read_text())
|
|
158
|
+
aliases = collect_named_enums(spec)
|
|
159
|
+
OUTPUT.parent.mkdir(parents=True, exist_ok=True)
|
|
160
|
+
OUTPUT.write_text(render(aliases))
|
|
161
|
+
print(f"emit_enums: wrote {len(aliases)} aliases → {OUTPUT}")
|
|
162
|
+
return 0
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
if __name__ == "__main__":
|
|
166
|
+
raise SystemExit(main())
|
|
@@ -81,5 +81,15 @@ uv run python "$SCRIPT_DIR/inject_strict_config.py" "$OUTPUT"
|
|
|
81
81
|
# child env (e.g. inherited VIRTUAL_ENV from a pytest parent).
|
|
82
82
|
uv run ruff format --quiet "$OUTPUT" || echo "warning: ruff format skipped" >&2
|
|
83
83
|
|
|
84
|
+
# Emit Literal aliases for every named multi-value enum from the
|
|
85
|
+
# *un-relaxed* spec. Provides ``types.py`` with codegen-stable public
|
|
86
|
+
# enum names (``IncidentStatus``, ``MonitorType`` …) that don't depend
|
|
87
|
+
# on ``datamodel-codegen``'s numbered suffixes. See
|
|
88
|
+
# ``mini/runbooks/api-contract.md`` § 3 and the script docstring.
|
|
89
|
+
echo "=> Emitting enum literal aliases..."
|
|
90
|
+
uv run python "$SCRIPT_DIR/emit_response_enums.py"
|
|
91
|
+
uv run ruff format --quiet "$ROOT_DIR/src/devhelm/_enums.py" \
|
|
92
|
+
|| echo "warning: ruff format on _enums.py skipped" >&2
|
|
93
|
+
|
|
84
94
|
rm -f "$PREPROCESSED"
|
|
85
95
|
echo "=> Generated: $OUTPUT"
|
|
@@ -120,6 +120,7 @@ from devhelm.types import (
|
|
|
120
120
|
StatusPageUpdateStatus,
|
|
121
121
|
TagDto,
|
|
122
122
|
TestChannelResult,
|
|
123
|
+
Tier,
|
|
123
124
|
TriggerRuleSeverity,
|
|
124
125
|
TriggerRuleType,
|
|
125
126
|
UpdateAlertChannelRequest,
|
|
@@ -289,6 +290,7 @@ __all__ = [
|
|
|
289
290
|
"StatusPageOverallStatus",
|
|
290
291
|
"StatusPageUpdateCreatedBy",
|
|
291
292
|
"StatusPageUpdateStatus",
|
|
293
|
+
"Tier",
|
|
292
294
|
"TriggerRuleSeverity",
|
|
293
295
|
"TriggerRuleType",
|
|
294
296
|
"UpdateAssertionSeverity",
|