Skip to content

feat: generate user-facing write/read schema models + offline validation#1135

Draft
dgarros wants to merge 6 commits into
developfrom
dga/user-schema-infp-234
Draft

feat: generate user-facing write/read schema models + offline validation#1135
dgarros wants to merge 6 commits into
developfrom
dga/user-schema-infp-234

Conversation

@dgarros

@dgarros dgarros commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds generated, user-facing write and read schema models to the SDK plus an offline validate_schema() so a schema can be checked for correctness with no running Infrahub server. Part of the user-facing schema separation (opsmill/infrahub, INFP-234).

Key Changes

  • New generated models infrahub_sdk/schema/generated/{write,read}.py — the write model is exactly what /api/schema/load accepts (constrained fields carry their allowed values as Literal[...]; extra="forbid"); read = write + visible-but-not-settable fields.
  • validate_schema() (infrahub_sdk/schema/validate.py) — field-level offline validation with dotted error locations; also gates schema extensions.
  • Drift/presence guard test so the generated models can't silently go stale.
  • Regenerated protocols.py (pre-existing drift vs current backend schema).

Related Context

Generated from the backend's schema definitions; consumed by the backend PR (opsmill/infrahub). Depends on nothing else in this repo.

Test Plan

uv run pytest tests/unit/test_schema_offline_validation.py tests/unit/test_schema_generated_models.py

14 offline-validation cases + generated-model guard pass, pydantic-only (no server).

Notes

Draft: the backend still ships a parallel hand-written model set; full consolidation of the SDK's hand-written schema models is tracked as a follow-up.

🤖 Generated with Claude Code


Summary by cubic

Adds generated user-facing write/read schema models and an offline validate_schema() so users can check schemas without a running server. Addresses INFP-234 by cleanly separating settable fields and blocking non-settable or unknown fields early.

  • New Features
    • Generated models in infrahub_sdk/schema/generated/{write,read}.py; write matches /api/schema/load with extra="forbid" and Literal[...] constraints; read = write + visible-but-not-settable fields.
    • Offline validator validate_schema() returns SchemaValidationResult with dotted field errors; also validates extensions.nodes[*].attributes|relationships against the same write contract; exported from infrahub_sdk.schema.
    • Added drift guard for generated models and unit tests for offline validation.

Written for commit ce6e067. Summary will update on new commits.

Review in cubic

ogenstad and others added 6 commits June 25, 2026 09:05
Add committed, generated write and read schema model variants under
infrahub_sdk/schema/generated/. They are produced by the backend generator
from the single source of truth in internal.py, filtered by a new field
visibility axis (write ⊆ read ⊆ internal). The models are self-contained
(pydantic + typing only) so they import with only the SDK installed, the
write model retains extra="forbid", and constrained fields carry their
allowed-value set as Literal[...] so the emitted JSON-schema is complete.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ft guard [INFP-234]

Add validate_schema() to validate a schema payload against the generated
write models with only the SDK installed (no server): it returns a
field-level verdict rejecting non-settable/unknown fields and out-of-enum
values. Add SDK unit tests for offline validation and a drift guard that
asserts the generated write/read models are present, carry the do-not-edit
header, and satisfy the write(extra=forbid)/read-superset invariants.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Regenerated by `invoke backend.generate`; picks up CoreIPPool generic,
Transformation dependencies attributes, and CoreAccount origin that had
not yet been reflected in the committed SDK protocols.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extend the offline write-contract validation so the attributes and
relationships nested under extensions.nodes[*] are held to the same
generated write models as node/generic-level ones. Previously only
top-level nodes and generics were gated, letting read-level, unknown,
and out-of-enum fields slip through on extension payloads.

Add SDK offline tests covering extension attribute/relationship
rejection with dotted error locations, plus breadth coverage for
out-of-enum relationship cardinality/kind and read-level fields on
relationships, generics, and nodes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying infrahub-sdk-python with  Cloudflare Pages  Cloudflare Pages

Latest commit: ce6e067
Status: ✅  Deploy successful!
Preview URL: https://4a26995d.infrahub-sdk-python.pages.dev
Branch Preview URL: https://dga-user-schema-infp-234.infrahub-sdk-python.pages.dev

View logs

@codecov

codecov Bot commented Jul 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.29730% with 7 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
infrahub_sdk/schema/validate.py 89.70% 3 Missing and 4 partials ⚠️
@@             Coverage Diff             @@
##           develop    #1135      +/-   ##
===========================================
+ Coverage    82.15%   82.48%   +0.32%     
===========================================
  Files          138      142       +4     
  Lines        11896    12152     +256     
  Branches      1784     1802      +18     
===========================================
+ Hits          9773    10023     +250     
  Misses        1575     1575              
- Partials       548      554       +6     
Flag Coverage Δ
integration-tests 40.40% <5.40%> (-0.72%) ⬇️
python-3.10 54.53% <21.62%> (-0.74%) ⬇️
python-3.11 54.51% <21.62%> (-0.74%) ⬇️
python-3.12 54.53% <21.62%> (-0.74%) ⬇️
python-3.13 54.51% <21.62%> (-0.74%) ⬇️
python-3.14 54.53% <21.62%> (-0.72%) ⬇️
python-filler-3.12 23.88% <75.67%> (+1.14%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
infrahub_sdk/protocols.py 100.00% <100.00%> (ø)
infrahub_sdk/schema/__init__.py 73.62% <100.00%> (+0.06%) ⬆️
infrahub_sdk/schema/generated/__init__.py 100.00% <100.00%> (ø)
infrahub_sdk/schema/generated/read.py 100.00% <100.00%> (ø)
infrahub_sdk/schema/generated/write.py 100.00% <100.00%> (ø)
infrahub_sdk/schema/repository.py 92.43% <100.00%> (+0.04%) ⬆️
infrahub_sdk/schema/validate.py 89.70% <89.70%> (ø)

... and 4 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 11 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="infrahub_sdk/schema/validate.py">

<violation number="1" location="infrahub_sdk/schema/validate.py:79">
P2: Non-dict items in schema payload lists (nodes, generics, extensions, extension attributes/relationships) are silently skipped rather than validated against the write model. The `isinstance(item, dict)` guard in `_validate_item` returns early without any validation, and `_validate_extensions` has the same guard with `continue`. This means a malformed entry — e.g., a string where a node dict was expected — would produce `valid=True` even though the server would reject it. Since the stated goal is purely-offline validation with no server round-trip, this gap could give callers a false sense of correctness. Consider removing the `isinstance` guard so pydantic validates the item directly and produces a proper field-level error.</violation>

<violation number="2" location="infrahub_sdk/schema/validate.py:108">
P1: Extension-node payloads with forbidden top-level fields can be reported as valid offline, then fail on schema load. Adding an explicit unknown-field check on `extensions.nodes[*]` would keep offline validation behavior consistent with server rejection paths.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

if not isinstance(node, dict):
continue
node_prefix = f"extensions.nodes[{node_index}]"
attributes = node.get("attributes")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Extension-node payloads with forbidden top-level fields can be reported as valid offline, then fail on schema load. Adding an explicit unknown-field check on extensions.nodes[*] would keep offline validation behavior consistent with server rejection paths.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At infrahub_sdk/schema/validate.py, line 108:

<comment>Extension-node payloads with forbidden top-level fields can be reported as valid offline, then fail on schema load. Adding an explicit unknown-field check on `extensions.nodes[*]` would keep offline validation behavior consistent with server rejection paths.</comment>

<file context>
@@ -0,0 +1,158 @@
+        if not isinstance(node, dict):
+            continue
+        node_prefix = f"extensions.nodes[{node_index}]"
+        attributes = node.get("attributes")
+        if isinstance(attributes, list):
+            for attr_index, attribute in enumerate(attributes):
</file context>


def _validate_item(model: type[BaseModel], item: Any, prefix: str, errors: list[SchemaValidationErrorDetail]) -> None:
"""Validate a single mapping against a write model, appending field-level errors under ``prefix``."""
if not isinstance(item, dict):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Non-dict items in schema payload lists (nodes, generics, extensions, extension attributes/relationships) are silently skipped rather than validated against the write model. The isinstance(item, dict) guard in _validate_item returns early without any validation, and _validate_extensions has the same guard with continue. This means a malformed entry — e.g., a string where a node dict was expected — would produce valid=True even though the server would reject it. Since the stated goal is purely-offline validation with no server round-trip, this gap could give callers a false sense of correctness. Consider removing the isinstance guard so pydantic validates the item directly and produces a proper field-level error.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At infrahub_sdk/schema/validate.py, line 79:

<comment>Non-dict items in schema payload lists (nodes, generics, extensions, extension attributes/relationships) are silently skipped rather than validated against the write model. The `isinstance(item, dict)` guard in `_validate_item` returns early without any validation, and `_validate_extensions` has the same guard with `continue`. This means a malformed entry — e.g., a string where a node dict was expected — would produce `valid=True` even though the server would reject it. Since the stated goal is purely-offline validation with no server round-trip, this gap could give callers a false sense of correctness. Consider removing the `isinstance` guard so pydantic validates the item directly and produces a proper field-level error.</comment>

<file context>
@@ -0,0 +1,158 @@
+
+def _validate_item(model: type[BaseModel], item: Any, prefix: str, errors: list[SchemaValidationErrorDetail]) -> None:
+    """Validate a single mapping against a write model, appending field-level errors under ``prefix``."""
+    if not isinstance(item, dict):
+        return
+    try:
</file context>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants