Skip to content

Commit aa401e0

Browse files
dgarrosclaude
andcommitted
refactor(schema): name generated models InfrahubSchema{Write,Read} + per-family Write/Read
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 7d61683 commit aa401e0

6 files changed

Lines changed: 73 additions & 41 deletions

File tree

infrahub_sdk/schema/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
from ..protocols_base import CoreNodeBase
2525
from ..queries import SCHEMA_HASH_SYNC_STATUS
2626
from .export import RESTRICTED_NAMESPACES, NamespaceExport, SchemaExport, schema_to_export_dict
27+
from .generated.read import InfrahubSchemaRead
28+
from .generated.write import InfrahubSchemaWrite
2729
from .main import (
2830
AttributeSchema,
2931
AttributeSchemaAPI,
@@ -57,6 +59,8 @@
5759
"BranchSupportType",
5860
"GenericSchema",
5961
"GenericSchemaAPI",
62+
"InfrahubSchemaRead",
63+
"InfrahubSchemaWrite",
6064
"NamespaceExport",
6165
"NodeSchema",
6266
"NodeSchemaAPI",

infrahub_sdk/schema/generated/read.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from pydantic import BaseModel, ConfigDict, Field
88

99

10-
class GeneratedAttributeSchema(BaseModel):
10+
class AttributeSchemaRead(BaseModel):
1111
model_config = ConfigDict()
1212
id: str | None = Field(
1313
default=None,
@@ -132,7 +132,7 @@ class GeneratedAttributeSchema(BaseModel):
132132
)
133133

134134

135-
class GeneratedRelationshipSchema(BaseModel):
135+
class RelationshipSchemaRead(BaseModel):
136136
model_config = ConfigDict()
137137
id: str | None = Field(
138138
default=None,
@@ -241,7 +241,7 @@ class GeneratedRelationshipSchema(BaseModel):
241241
)
242242

243243

244-
class GeneratedBaseNodeSchema(BaseModel):
244+
class BaseNodeSchemaRead(BaseModel):
245245
model_config = ConfigDict()
246246
id: str | None = Field(
247247
default=None,
@@ -320,17 +320,17 @@ class GeneratedBaseNodeSchema(BaseModel):
320320
default="present",
321321
description="Expected state of the node/generic after loading the schema",
322322
)
323-
attributes: list[GeneratedAttributeSchema] = Field(
323+
attributes: list[AttributeSchemaRead] = Field(
324324
default_factory=list,
325325
description="Node attributes",
326326
)
327-
relationships: list[GeneratedRelationshipSchema] = Field(
327+
relationships: list[RelationshipSchemaRead] = Field(
328328
default_factory=list,
329329
description="Node Relationships",
330330
)
331331

332332

333-
class GeneratedNodeSchema(GeneratedBaseNodeSchema):
333+
class NodeSchemaRead(BaseNodeSchemaRead):
334334
model_config = ConfigDict()
335335
inherit_from: list[str] = Field(
336336
default_factory=list,
@@ -358,7 +358,7 @@ class GeneratedNodeSchema(GeneratedBaseNodeSchema):
358358
)
359359

360360

361-
class GeneratedGenericSchema(GeneratedBaseNodeSchema):
361+
class GenericSchemaRead(BaseNodeSchemaRead):
362362
model_config = ConfigDict()
363363
hierarchical: bool = Field(
364364
default=False,
@@ -376,3 +376,8 @@ class GeneratedGenericSchema(GeneratedBaseNodeSchema):
376376
default=None,
377377
description="Nodes inheriting from this Generic schema must belong to one of the listed namespaces",
378378
)
379+
380+
381+
class InfrahubSchemaRead(BaseModel):
382+
nodes: list[NodeSchemaRead] = Field(default_factory=list)
383+
generics: list[GenericSchemaRead] = Field(default_factory=list)

infrahub_sdk/schema/generated/write.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from pydantic import BaseModel, ConfigDict, Field
88

99

10-
class GeneratedAttributeSchema(BaseModel):
10+
class AttributeSchemaWrite(BaseModel):
1111
model_config = ConfigDict(extra="forbid")
1212
id: str | None = Field(
1313
default=None,
@@ -128,7 +128,7 @@ class GeneratedAttributeSchema(BaseModel):
128128
)
129129

130130

131-
class GeneratedRelationshipSchema(BaseModel):
131+
class RelationshipSchemaWrite(BaseModel):
132132
model_config = ConfigDict(extra="forbid")
133133
id: str | None = Field(
134134
default=None,
@@ -229,7 +229,7 @@ class GeneratedRelationshipSchema(BaseModel):
229229
)
230230

231231

232-
class GeneratedBaseNodeSchema(BaseModel):
232+
class BaseNodeSchemaWrite(BaseModel):
233233
model_config = ConfigDict(extra="forbid")
234234
id: str | None = Field(
235235
default=None,
@@ -308,17 +308,17 @@ class GeneratedBaseNodeSchema(BaseModel):
308308
default="present",
309309
description="Expected state of the node/generic after loading the schema",
310310
)
311-
attributes: list[GeneratedAttributeSchema] = Field(
311+
attributes: list[AttributeSchemaWrite] = Field(
312312
default_factory=list,
313313
description="Node attributes",
314314
)
315-
relationships: list[GeneratedRelationshipSchema] = Field(
315+
relationships: list[RelationshipSchemaWrite] = Field(
316316
default_factory=list,
317317
description="Node Relationships",
318318
)
319319

320320

321-
class GeneratedNodeSchema(GeneratedBaseNodeSchema):
321+
class NodeSchemaWrite(BaseNodeSchemaWrite):
322322
model_config = ConfigDict(extra="forbid")
323323
inherit_from: list[str] = Field(
324324
default_factory=list,
@@ -342,7 +342,7 @@ class GeneratedNodeSchema(GeneratedBaseNodeSchema):
342342
)
343343

344344

345-
class GeneratedGenericSchema(GeneratedBaseNodeSchema):
345+
class GenericSchemaWrite(BaseNodeSchemaWrite):
346346
model_config = ConfigDict(extra="forbid")
347347
hierarchical: bool = Field(
348348
default=False,
@@ -356,3 +356,9 @@ class GeneratedGenericSchema(GeneratedBaseNodeSchema):
356356
default=None,
357357
description="Nodes inheriting from this Generic schema must belong to one of the listed namespaces",
358358
)
359+
360+
361+
class InfrahubSchemaWrite(BaseModel):
362+
version: str | None = None
363+
nodes: list[NodeSchemaWrite] = Field(default_factory=list)
364+
generics: list[GenericSchemaWrite] = Field(default_factory=list)

infrahub_sdk/schema/validate.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,16 @@
1616
from pydantic import ValidationError as PydanticValidationError
1717

1818
from .generated.write import (
19-
GeneratedAttributeSchema,
20-
GeneratedGenericSchema,
21-
GeneratedNodeSchema,
22-
GeneratedRelationshipSchema,
19+
AttributeSchemaWrite,
20+
GenericSchemaWrite,
21+
NodeSchemaWrite,
22+
RelationshipSchemaWrite,
2323
)
2424

2525
# Maps each collection in a schema-root payload to the write model its items must satisfy.
2626
_WRITE_MODELS_BY_COLLECTION: dict[str, type[BaseModel]] = {
27-
"nodes": GeneratedNodeSchema,
28-
"generics": GeneratedGenericSchema,
27+
"nodes": NodeSchemaWrite,
28+
"generics": GenericSchemaWrite,
2929
}
3030

3131

@@ -109,7 +109,7 @@ def _validate_extensions(extensions: Any, errors: list[SchemaValidationErrorDeta
109109
if isinstance(attributes, list):
110110
for attr_index, attribute in enumerate(attributes):
111111
_validate_item(
112-
model=GeneratedAttributeSchema,
112+
model=AttributeSchemaWrite,
113113
item=attribute,
114114
prefix=f"{node_prefix}.attributes[{attr_index}]",
115115
errors=errors,
@@ -118,7 +118,7 @@ def _validate_extensions(extensions: Any, errors: list[SchemaValidationErrorDeta
118118
if isinstance(relationships, list):
119119
for rel_index, relationship in enumerate(relationships):
120120
_validate_item(
121-
model=GeneratedRelationshipSchema,
121+
model=RelationshipSchemaWrite,
122122
item=relationship,
123123
prefix=f"{node_prefix}.relationships[{rel_index}]",
124124
errors=errors,

tests/unit/test_schema_generated_models.py

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,23 @@
1515

1616
import pytest
1717

18+
from infrahub_sdk.schema import InfrahubSchemaRead, InfrahubSchemaWrite
1819
from infrahub_sdk.schema.generated import read as read_module
1920
from infrahub_sdk.schema.generated import write as write_module
2021

2122
if TYPE_CHECKING:
2223
from pydantic import BaseModel
2324

2425
_GENERATED_DIR = Path(write_module.__file__).parent
25-
_EXPECTED_FAMILIES = [
26-
"GeneratedAttributeSchema",
27-
"GeneratedRelationshipSchema",
28-
"GeneratedBaseNodeSchema",
29-
"GeneratedNodeSchema",
30-
"GeneratedGenericSchema",
26+
# Each family pairs its write-variant class name with its read-variant class name.
27+
_FAMILY_PAIRS = [
28+
("AttributeSchemaWrite", "AttributeSchemaRead"),
29+
("RelationshipSchemaWrite", "RelationshipSchemaRead"),
30+
("BaseNodeSchemaWrite", "BaseNodeSchemaRead"),
31+
("NodeSchemaWrite", "NodeSchemaRead"),
32+
("GenericSchemaWrite", "GenericSchemaRead"),
3133
]
34+
_WRITE_FAMILIES = [write for write, _ in _FAMILY_PAIRS]
3235

3336

3437
@pytest.mark.parametrize("filename", ["write.py", "read.py"])
@@ -38,31 +41,39 @@ def test_generated_files_present_with_do_not_edit_header(filename: str) -> None:
3841
assert "do not edit" in path.read_text().splitlines()[0].lower()
3942

4043

41-
@pytest.mark.parametrize("family", _EXPECTED_FAMILIES)
42-
def test_expected_model_families_present_in_both_variants(family: str) -> None:
43-
assert hasattr(write_module, family), f"write variant is missing {family}"
44-
assert hasattr(read_module, family), f"read variant is missing {family}"
44+
@pytest.mark.parametrize(("write_family", "read_family"), _FAMILY_PAIRS)
45+
def test_expected_model_families_present_in_each_variant(write_family: str, read_family: str) -> None:
46+
assert hasattr(write_module, write_family), f"write variant is missing {write_family}"
47+
assert hasattr(read_module, read_family), f"read variant is missing {read_family}"
4548

4649

47-
@pytest.mark.parametrize("family", _EXPECTED_FAMILIES)
50+
@pytest.mark.parametrize("family", _WRITE_FAMILIES)
4851
def test_write_variant_forbids_extra_fields(family: str) -> None:
4952
model: type[BaseModel] = getattr(write_module, family)
5053
assert model.model_config.get("extra") == "forbid", (
5154
f"write variant {family} must set extra='forbid' so non-settable fields are rejected"
5255
)
5356

5457

55-
@pytest.mark.parametrize("family", _EXPECTED_FAMILIES)
56-
def test_read_variant_is_superset_of_write_variant(family: str) -> None:
57-
write_model: type[BaseModel] = getattr(write_module, family)
58-
read_model: type[BaseModel] = getattr(read_module, family)
58+
@pytest.mark.parametrize(("write_family", "read_family"), _FAMILY_PAIRS)
59+
def test_read_variant_is_superset_of_write_variant(write_family: str, read_family: str) -> None:
60+
write_model: type[BaseModel] = getattr(write_module, write_family)
61+
read_model: type[BaseModel] = getattr(read_module, read_family)
5962
write_fields = set(write_model.model_fields)
6063
read_fields = set(read_model.model_fields)
6164
missing = write_fields - read_fields
62-
assert not missing, f"read variant {family} must expose every write field; missing: {sorted(missing)}"
65+
assert not missing, f"read variant {read_family} must expose every write field; missing: {sorted(missing)}"
6366

6467

6568
def test_write_variant_carries_read_level_fields_absent() -> None:
6669
# `inherited` is a read-level attribute field and must not exist on the write variant.
67-
assert "inherited" not in write_module.GeneratedAttributeSchema.model_fields
68-
assert "inherited" in read_module.GeneratedAttributeSchema.model_fields
70+
assert "inherited" not in write_module.AttributeSchemaWrite.model_fields
71+
assert "inherited" in read_module.AttributeSchemaRead.model_fields
72+
73+
74+
def test_document_root_models_import_with_nodes_and_generics() -> None:
75+
for root in (InfrahubSchemaWrite, InfrahubSchemaRead):
76+
assert "nodes" in root.model_fields, f"{root.__name__} must expose a 'nodes' field"
77+
assert "generics" in root.model_fields, f"{root.__name__} must expose a 'generics' field"
78+
# The write root is not extra=forbid so schema-level keys outside the model are tolerated.
79+
assert InfrahubSchemaWrite.model_config.get("extra") != "forbid"

tests/unit/test_schema_offline_validation.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import pytest
1010

11-
from infrahub_sdk.schema import validate_schema
11+
from infrahub_sdk.schema import InfrahubSchemaRead, InfrahubSchemaWrite, validate_schema
1212
from infrahub_sdk.schema.validate import SchemaValidationResult
1313

1414

@@ -34,6 +34,12 @@ def _valid_schema() -> dict:
3434
}
3535

3636

37+
def test_schema_root_models_are_importable_with_nodes_and_generics() -> None:
38+
for root in (InfrahubSchemaWrite, InfrahubSchemaRead):
39+
assert "nodes" in root.model_fields
40+
assert "generics" in root.model_fields
41+
42+
3743
def test_valid_payload_passes() -> None:
3844
result = validate_schema(schema=_valid_schema())
3945
assert isinstance(result, SchemaValidationResult)

0 commit comments

Comments
 (0)