Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4,134 changes: 2,044 additions & 2,090 deletions docs/openapi/monitoring-api.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ dev = [
[tool.ruff.lint]
extend-select = ["C90", "I"]

[tool.ruff.lint.per-file-ignores]
"src/devhelm/_generated.py" = ["I001"]

[tool.ruff.format]
skip-magic-trailing-comma = true

Expand Down
106 changes: 106 additions & 0 deletions scripts/preprocess_spec.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#!/usr/bin/env python3
"""Preprocess the vendored OpenAPI spec before running datamodel-codegen.

Applies structural fixes that code generators need:
1. setRequiredFields — mark non-nullable fields as required
2. pushRequiredIntoAllOf — propagate required into allOf members
"""

from __future__ import annotations

import json
import sys
from pathlib import Path


_REQUEST_PREFIXES = (
"Create", "Update", "Add", "Acquire", "Resolve", "Reorder",
"Test", "Change", "Admin", "Bulk", "Monitor",
)


def set_required_fields(spec: dict) -> None:
schemas = spec.get("components", {}).get("schemas", {})
for schema_name, schema in schemas.items():
if schema.get("type") != "object" or "properties" not in schema:
continue

if isinstance(schema.get("required"), list):
is_req = schema_name.endswith("Request") and schema_name.startswith(
_REQUEST_PREFIXES
)
if not is_req:
for prop, prop_schema in schema.get("properties", {}).items():
if prop_schema.get("nullable"):
continue
if prop in schema["required"]:
continue
if prop_schema.get("allOf"):
continue
if "default" in prop_schema:
continue
schema["required"].append(prop)
continue

is_request = schema_name.endswith("Request") and schema_name.startswith(
_REQUEST_PREFIXES
)
if is_request:
continue

required = []
for prop, prop_schema in schema.get("properties", {}).items():
if prop_schema.get("nullable"):
continue
if prop_schema.get("allOf"):
continue
if "default" in prop_schema:
continue
required.append(prop)
if required:
schema["required"] = required


def push_required_into_all_of(spec: dict) -> None:
schemas = spec.get("components", {}).get("schemas", {})
for schema in schemas.values():
if not isinstance(schema.get("required"), list):
continue
if not isinstance(schema.get("allOf"), list):
continue
for member in schema["allOf"]:
if "properties" not in member:
continue
member_required = [f for f in schema["required"] if f in member["properties"]]
if member_required:
existing = member.get("required", [])
member["required"] = list(set(existing + member_required))



# fix_missing_nullable — REMOVED.
# The root cause (Lombok not copying @Nullable to getters) was fixed in the API
# by adding `jakarta.annotation.Nullable` to lombok.copyableAnnotations. The
# generated OpenAPI spec now correctly marks nullable fields via the existing
# PropertyCustomizer in OpenApiConfig.java. All DTO fields also have explicit
# @Nullable or @NotNull/@NotBlank annotations, enforced by DtoAnnotationTest.


def main() -> None:
if len(sys.argv) != 3:
print(f"Usage: {sys.argv[0]} <input.json> <output.json>", file=sys.stderr)
sys.exit(1)

input_path = Path(sys.argv[1])
output_path = Path(sys.argv[2])

spec = json.loads(input_path.read_text())
set_required_fields(spec)
push_required_into_all_of(spec)

output_path.write_text(json.dumps(spec, indent=2))
print(f"Preprocessed: {input_path} -> {output_path}")


if __name__ == "__main__":
main()
9 changes: 7 additions & 2 deletions scripts/typegen.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,21 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"

INPUT="$ROOT_DIR/docs/openapi/monitoring-api.json"
PREPROCESSED="$ROOT_DIR/.openapi-preprocessed.json"
OUTPUT="$ROOT_DIR/src/devhelm/_generated.py"

if [[ ! -f "$INPUT" ]]; then
echo "error: OpenAPI spec not found at $INPUT" >&2
exit 1
fi

echo "=> Generating Pydantic models from OpenAPI spec..."
echo "=> Preprocessing OpenAPI spec..."
python3 "$SCRIPT_DIR/preprocess_spec.py" "$INPUT" "$PREPROCESSED"

echo "=> Generating Pydantic models from preprocessed spec..."

uv run datamodel-codegen \
--input "$INPUT" \
--input "$PREPROCESSED" \
--output "$OUTPUT" \
--output-model-type pydantic_v2.BaseModel \
--target-python-version 3.11 \
Expand All @@ -28,4 +32,5 @@ uv run datamodel-codegen \
--input-file-type openapi \
--formatters ruff-format

rm -f "$PREPROCESSED"
echo "=> Generated: $OUTPUT"
10 changes: 10 additions & 0 deletions src/devhelm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from devhelm.types import (
AcquireDeployLockRequest,
AddCustomDomainRequest,
AddResourceGroupMemberRequest,
AdminAddSubscriberRequest,
AlertChannelDto,
ApiKeyCreateResponse,
Expand Down Expand Up @@ -49,6 +50,8 @@
MonitorDto,
MonitorVersionDto,
NotificationPolicyDto,
ReorderComponentsRequest,
ResolveIncidentRequest,
ResourceGroupDto,
ResourceGroupMemberDto,
SecretDto,
Expand All @@ -63,6 +66,7 @@
StatusPageIncidentUpdateDto,
StatusPageSubscriberDto,
TagDto,
TestChannelResult,
UpdateAlertChannelRequest,
UpdateEnvironmentRequest,
UpdateMonitorRequest,
Expand All @@ -76,6 +80,7 @@
UpdateTagRequest,
UpdateWebhookEndpointRequest,
WebhookEndpointDto,
WebhookTestResult,
)

__all__ = [
Expand Down Expand Up @@ -131,6 +136,8 @@
"DashboardOverviewDto",
"DeployLockDto",
"AssertionTestResultDto",
"TestChannelResult",
"WebhookTestResult",
# Request types
"CreateStatusPageRequest",
"UpdateStatusPageRequest",
Expand All @@ -143,9 +150,11 @@
"CreateStatusPageIncidentUpdateRequest",
"AddCustomDomainRequest",
"AdminAddSubscriberRequest",
"ReorderComponentsRequest",
"CreateMonitorRequest",
"UpdateMonitorRequest",
"CreateManualIncidentRequest",
"ResolveIncidentRequest",
"CreateAlertChannelRequest",
"UpdateAlertChannelRequest",
"CreateNotificationPolicyRequest",
Expand All @@ -158,6 +167,7 @@
"UpdateTagRequest",
"CreateResourceGroupRequest",
"UpdateResourceGroupRequest",
"AddResourceGroupMemberRequest",
"CreateWebhookEndpointRequest",
"UpdateWebhookEndpointRequest",
"CreateApiKeyRequest",
Expand Down
Loading
Loading