devhelm 0.7.2__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.
Files changed (55) hide show
  1. {devhelm-0.7.2 → devhelm-1.1.0}/PKG-INFO +1 -1
  2. {devhelm-0.7.2 → devhelm-1.1.0}/docs/openapi/monitoring-api.json +1 -1
  3. {devhelm-0.7.2 → devhelm-1.1.0}/pyproject.toml +1 -1
  4. devhelm-1.1.0/scripts/emit_response_enums.py +166 -0
  5. {devhelm-0.7.2 → devhelm-1.1.0}/scripts/typegen.sh +10 -0
  6. {devhelm-0.7.2 → devhelm-1.1.0}/src/devhelm/__init__.py +2 -0
  7. devhelm-1.1.0/src/devhelm/_enums.py +569 -0
  8. {devhelm-0.7.2 → devhelm-1.1.0}/src/devhelm/_generated.py +108 -342
  9. devhelm-1.1.0/src/devhelm/types.py +310 -0
  10. {devhelm-0.7.2 → devhelm-1.1.0}/tests/test_negative_validation.py +38 -40
  11. {devhelm-0.7.2 → devhelm-1.1.0}/tests/test_schemas.py +2 -2
  12. {devhelm-0.7.2 → devhelm-1.1.0}/uv.lock +1 -1
  13. devhelm-0.7.2/src/devhelm/types.py +0 -374
  14. {devhelm-0.7.2 → devhelm-1.1.0}/.github/workflows/ci.yml +0 -0
  15. {devhelm-0.7.2 → devhelm-1.1.0}/.github/workflows/release.yml +0 -0
  16. {devhelm-0.7.2 → devhelm-1.1.0}/.github/workflows/spec-check.yml +0 -0
  17. {devhelm-0.7.2 → devhelm-1.1.0}/.gitignore +0 -0
  18. {devhelm-0.7.2 → devhelm-1.1.0}/LICENSE +0 -0
  19. {devhelm-0.7.2 → devhelm-1.1.0}/Makefile +0 -0
  20. {devhelm-0.7.2 → devhelm-1.1.0}/README.md +0 -0
  21. {devhelm-0.7.2 → devhelm-1.1.0}/scripts/inject_strict_config.py +0 -0
  22. {devhelm-0.7.2 → devhelm-1.1.0}/scripts/regen-from.sh +0 -0
  23. {devhelm-0.7.2 → devhelm-1.1.0}/scripts/release.sh +0 -0
  24. {devhelm-0.7.2 → devhelm-1.1.0}/src/devhelm/_errors.py +0 -0
  25. {devhelm-0.7.2 → devhelm-1.1.0}/src/devhelm/_http.py +0 -0
  26. {devhelm-0.7.2 → devhelm-1.1.0}/src/devhelm/_pagination.py +0 -0
  27. {devhelm-0.7.2 → devhelm-1.1.0}/src/devhelm/_validation.py +0 -0
  28. {devhelm-0.7.2 → devhelm-1.1.0}/src/devhelm/client.py +0 -0
  29. {devhelm-0.7.2 → devhelm-1.1.0}/src/devhelm/py.typed +0 -0
  30. {devhelm-0.7.2 → devhelm-1.1.0}/src/devhelm/resources/__init__.py +0 -0
  31. {devhelm-0.7.2 → devhelm-1.1.0}/src/devhelm/resources/alert_channels.py +0 -0
  32. {devhelm-0.7.2 → devhelm-1.1.0}/src/devhelm/resources/api_keys.py +0 -0
  33. {devhelm-0.7.2 → devhelm-1.1.0}/src/devhelm/resources/dependencies.py +0 -0
  34. {devhelm-0.7.2 → devhelm-1.1.0}/src/devhelm/resources/deploy_lock.py +0 -0
  35. {devhelm-0.7.2 → devhelm-1.1.0}/src/devhelm/resources/environments.py +0 -0
  36. {devhelm-0.7.2 → devhelm-1.1.0}/src/devhelm/resources/forensics.py +0 -0
  37. {devhelm-0.7.2 → devhelm-1.1.0}/src/devhelm/resources/incidents.py +0 -0
  38. {devhelm-0.7.2 → devhelm-1.1.0}/src/devhelm/resources/maintenance_windows.py +0 -0
  39. {devhelm-0.7.2 → devhelm-1.1.0}/src/devhelm/resources/monitors.py +0 -0
  40. {devhelm-0.7.2 → devhelm-1.1.0}/src/devhelm/resources/notification_policies.py +0 -0
  41. {devhelm-0.7.2 → devhelm-1.1.0}/src/devhelm/resources/resource_groups.py +0 -0
  42. {devhelm-0.7.2 → devhelm-1.1.0}/src/devhelm/resources/secrets.py +0 -0
  43. {devhelm-0.7.2 → devhelm-1.1.0}/src/devhelm/resources/status.py +0 -0
  44. {devhelm-0.7.2 → devhelm-1.1.0}/src/devhelm/resources/status_pages.py +0 -0
  45. {devhelm-0.7.2 → devhelm-1.1.0}/src/devhelm/resources/tags.py +0 -0
  46. {devhelm-0.7.2 → devhelm-1.1.0}/src/devhelm/resources/webhooks.py +0 -0
  47. {devhelm-0.7.2 → devhelm-1.1.0}/tests/__init__.py +0 -0
  48. {devhelm-0.7.2 → devhelm-1.1.0}/tests/run_sdk.py +0 -0
  49. {devhelm-0.7.2 → devhelm-1.1.0}/tests/test_client.py +0 -0
  50. {devhelm-0.7.2 → devhelm-1.1.0}/tests/test_errors.py +0 -0
  51. {devhelm-0.7.2 → devhelm-1.1.0}/tests/test_http.py +0 -0
  52. {devhelm-0.7.2 → devhelm-1.1.0}/tests/test_maintenance_windows.py +0 -0
  53. {devhelm-0.7.2 → devhelm-1.1.0}/tests/test_spec_parity.py +0 -0
  54. {devhelm-0.7.2 → devhelm-1.1.0}/tests/test_typing.py +0 -0
  55. {devhelm-0.7.2 → 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: 0.7.2
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
@@ -5187,7 +5187,7 @@
5187
5187
  "Invites"
5188
5188
  ],
5189
5189
  "summary": "Resend invite",
5190
- "operationId": "resend",
5190
+ "operationId": "resend_1",
5191
5191
  "parameters": [
5192
5192
  {
5193
5193
  "name": "inviteId",
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "devhelm"
3
- version = "0.7.2"
3
+ version = "1.1.0"
4
4
  description = "DevHelm SDK for Python — typed client for monitors, incidents, alerting, and more"
5
5
  authors = [{ name = "DevHelm", email = "hello@devhelm.io" }]
6
6
  license = "MIT"
@@ -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",