Skip to content

Commit f2e2284

Browse files
authored
Merge pull request #4902 from opsmill/dga-20241109-fix-computed-attr-serialization
Fix serialization for ComputedAttribute in schema
2 parents 6ca55cc + 7dcd640 commit f2e2284

File tree

8 files changed

+77
-31
lines changed

8 files changed

+77
-31
lines changed

backend/infrahub/core/schema/attribute_schema.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,19 @@ def update_from_generic(self, other: AttributeSchema) -> None:
9292
if getattr(self, name) != getattr(other, name):
9393
setattr(self, name, getattr(other, name))
9494

95+
def to_node(self) -> dict[str, Any]:
96+
fields_to_exclude = {"id", "state", "filters"}
97+
fields_to_json = {"computed_attribute"}
98+
data = self.model_dump(exclude=fields_to_exclude | fields_to_json)
99+
100+
for field_name in fields_to_json:
101+
if field := getattr(self, field_name):
102+
data[field_name] = {"value": field.model_dump()}
103+
else:
104+
data[field_name] = None
105+
106+
return data
107+
95108
async def get_query_filter(
96109
self,
97110
name: str,

backend/infrahub/core/schema/computed_attribute.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
from typing import Optional
1+
from typing import Any, Optional
22

3-
from pydantic import ConfigDict, Field
3+
from pydantic import ConfigDict, Field, model_serializer
44

55
from infrahub.core.constants import ComputedAttributeKind
66
from infrahub.core.models import HashableModel
@@ -15,6 +15,10 @@ class ComputedAttribute(HashableModel):
1515
default=None, description="The Python Transform name or ID, required when assignment_type=transform"
1616
)
1717

18+
@model_serializer
19+
def ser_model(self) -> dict[str, Any]:
20+
return {"kind": self.kind.value, "jinja2_template": self.jinja2_template, "transform": self.transform}
21+
1822
model_config = ConfigDict(
1923
extra="forbid",
2024
json_schema_extra={

backend/infrahub/core/schema/manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -537,7 +537,7 @@ async def create_attribute_in_db(
537537
schema: NodeSchema, item: AttributeSchema, branch: Branch, parent: Node, db: InfrahubDatabase
538538
) -> AttributeSchema:
539539
obj = await Node.init(schema=schema, branch=branch, db=db)
540-
await obj.new(**item.model_dump(exclude={"id", "state", "filters"}), node=parent, db=db)
540+
await obj.new(**item.to_node(), node=parent, db=db)
541541
await obj.save(db=db)
542542
new_item = item.duplicate()
543543
new_item.id = obj.id

backend/tests/helpers/schema/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,17 @@
2020
CAR_SCHEMA = SchemaRoot(nodes=[CAR, MANUFACTURER, PERSON])
2121

2222

23-
async def load_schema(db: InfrahubDatabase, schema: SchemaRoot, branch_name: str | None = None) -> None:
23+
async def load_schema(
24+
db: InfrahubDatabase, schema: SchemaRoot, branch_name: str | None = None, update_db: bool = False
25+
) -> None:
2426
default_branch_name = registry.default_branch
2527
branch_schema = registry.schema.get_schema_branch(name=branch_name or default_branch_name)
2628
tmp_schema = branch_schema.duplicate()
2729
tmp_schema.load_schema(schema=schema)
2830
tmp_schema.process()
2931

3032
await registry.schema.update_schema_branch(
31-
schema=tmp_schema, db=db, branch=branch_name or default_branch_name, update_db=True
33+
schema=tmp_schema, db=db, branch=branch_name or default_branch_name, update_db=update_db
3234
)
3335

3436

backend/tests/integration/diff/test_diff_rebase.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ async def initial_dataset(
7878
client: InfrahubClient,
7979
bus_simulator: BusSimulator,
8080
) -> dict[str, Node]:
81-
await load_schema(db, schema=CAR_SCHEMA)
81+
await load_schema(db, schema=CAR_SCHEMA, update_db=True)
8282
john = await Node.init(schema=TestKind.PERSON, db=db)
8383
await john.new(db=db, name="John", height=175, description="The famous Joe Doe")
8484
await john.save(db=db)

backend/tests/unit/core/schema_manager/test_manager_schema.py

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,20 @@
1818
SchemaPathType,
1919
)
2020
from infrahub.core.schema import (
21+
AttributeSchema,
2122
GenericSchema,
2223
NodeSchema,
24+
RelationshipSchema,
2325
SchemaRoot,
2426
core_models,
2527
internal_schema,
2628
)
29+
from infrahub.core.schema.computed_attribute import ComputedAttribute
2730
from infrahub.core.schema.manager import SchemaManager
2831
from infrahub.core.schema.schema_branch import SchemaBranch
2932
from infrahub.database import InfrahubDatabase
3033
from infrahub.exceptions import SchemaNotFoundError, ValidationError
34+
from tests.helpers.schema import CHILD, THING
3135

3236
from .conftest import _get_schema_by_kind
3337

@@ -1416,10 +1420,11 @@ async def test_schema_branch_validate_count_against_cardinality_invalid(relation
14161420

14171421

14181422
async def test_schema_branch_from_dict_schema_object():
1419-
schema = SchemaRoot(**core_models)
1420-
14211423
schema_branch = SchemaBranch(cache={}, name="test")
1422-
schema_branch.load_schema(schema=schema)
1424+
1425+
# Load the core models and a model with a computed_attribute
1426+
schema_branch.load_schema(schema=SchemaRoot(**core_models))
1427+
schema_branch.load_schema(schema=SchemaRoot(nodes=[CHILD, THING]))
14231428

14241429
exported = schema_branch.to_dict_schema_object()
14251430

@@ -2073,31 +2078,32 @@ async def test_load_node_to_db_node_schema(db: InfrahubDatabase, default_branch:
20732078
registry.schema = SchemaManager()
20742079
registry.schema.register_schema(schema=SchemaRoot(**internal_schema), branch=default_branch.name)
20752080

2076-
SCHEMA = {
2077-
"name": "Criticality",
2078-
"namespace": "Builtin",
2079-
"default_filter": "name__value",
2080-
"attributes": [
2081-
{"name": "name", "kind": "Text", "unique": True},
2082-
{"name": "level", "kind": "Number"},
2083-
{"name": "color", "kind": "Text", "default_value": "#444444"},
2084-
{"name": "description", "kind": "Text", "optional": True},
2085-
],
2086-
"relationships": [
2087-
{"name": "others", "peer": "BuiltinCriticality", "optional": True, "cardinality": "many"},
2081+
node = NodeSchema(
2082+
name="Criticality",
2083+
namespace="Builtin",
2084+
default_filter="name__value",
2085+
attributes=[
2086+
AttributeSchema(name="name", kind="Text", unique=True),
2087+
AttributeSchema(name="level", kind="Number"),
2088+
AttributeSchema(name="color", kind="Text", default_value="default_value"),
2089+
AttributeSchema(
2090+
name="description",
2091+
kind="Text",
2092+
optional=True,
2093+
computed_attribute=ComputedAttribute(kind="Jinja2", jinja2_template="{{ name__value }}"),
2094+
),
20882095
],
2089-
}
2090-
node = NodeSchema(**SCHEMA)
2091-
2096+
relationships=[RelationshipSchema(name="others", peer="BuiltinCriticality", optional=True, cardinality="many")],
2097+
)
20922098
await registry.schema.load_node_to_db(node=node, db=db, branch=default_branch)
20932099

20942100
node2 = registry.schema.get(name=node.kind, branch=default_branch)
20952101
assert node2.id
20962102
assert node2.relationships[0].id
20972103
assert node2.attributes[0].id
20982104

2099-
results = await SchemaManager.query(schema="SchemaNode", branch=default_branch, db=db)
2100-
assert len(results) == 1
2105+
node_from_db = await SchemaManager.get_one(db=db, id=node2.id, branch=default_branch)
2106+
assert node_from_db
21012107

21022108

21032109
async def test_load_node_to_db_generic_schema(db: InfrahubDatabase, default_branch):
@@ -2249,7 +2255,14 @@ async def test_load_schema_from_db(
22492255
{"name": "name", "kind": "Text", "label": "Name", "unique": True},
22502256
{"name": "level", "kind": "Number", "label": "Level"},
22512257
{"name": "color", "kind": "Text", "label": "Color", "default_value": "#444444"},
2252-
{"name": "description", "kind": "Text", "label": "Description", "optional": True},
2258+
{
2259+
"name": "description",
2260+
"kind": "Text",
2261+
"label": "Description",
2262+
"optional": True,
2263+
"read_only": True,
2264+
"computed_attribute": {"kind": "Jinja2", "jinja2_template": "{{ name__value }}"},
2265+
},
22532266
],
22542267
"relationships": [
22552268
{
@@ -2320,6 +2333,10 @@ async def test_load_schema_from_db(
23202333
assert schema11.get(name=InfrahubKind.TAG).get_hash() == schema2.get(name="BuiltinTag").get_hash()
23212334
assert schema11.get(name="TestGenericInterface").get_hash() == schema2.get(name="TestGenericInterface").get_hash()
23222335

2336+
description_schema = crit_schema.get_attribute("description")
2337+
assert description_schema.computed_attribute is not None
2338+
assert description_schema.computed_attribute.jinja2_template == "{{ name__value }}"
2339+
23232340

23242341
async def test_load_schema(
23252342
db: InfrahubDatabase, reset_registry, default_branch: Branch, register_internal_models_schema

backend/tests/unit/core/test_node.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,17 @@ async def test_node_create_local_attrs(db: InfrahubDatabase, default_branch: Bra
445445
assert obj.json_no_default.value is None
446446

447447
obj = await Node.init(db=db, schema=criticality_schema)
448-
await obj.new(db=db, name="medium", level=3, description="My desc", is_true=False, is_false=True, color="#333333")
448+
await obj.new(
449+
db=db,
450+
name="medium",
451+
level=3,
452+
description="My desc",
453+
is_true=False,
454+
is_false=True,
455+
color="#333333",
456+
json_default={"value": {"value": "xxxxx"}},
457+
json_no_default={"value": {"testing": True}},
458+
)
449459
await obj.save(db=db)
450460

451461
assert obj.id
@@ -460,6 +470,8 @@ async def test_node_create_local_attrs(db: InfrahubDatabase, default_branch: Bra
460470
assert obj.color.id
461471
assert obj.is_true.value is False
462472
assert obj.is_false.value is True
473+
assert obj.json_default.value == {"value": "xxxxx"}
474+
assert obj.json_no_default.value == {"testing": True}
463475

464476

465477
async def test_node_create_attribute_with_source(

backend/tests/unit/graphql/test_mutation_create_macros.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,7 @@ async def test_create_related_macro(
2424
default_branch.update_schema_hash()
2525
await default_branch.save(db=db)
2626
await load_schema(db, schema=SchemaRoot(nodes=[CHILD, THING]))
27-
# FIXME: This is a workaround hack as it's not posible to have Schema attributes in JSON format
28-
# on creation at the moment so we need to get the schema updated after initial creation..
29-
await load_schema(db, schema=SchemaRoot(nodes=[CHILD, THING]))
27+
3028
fred = await Node.init(schema=TestKind.CHILD, db=db)
3129
await fred.new(db=db, name="Fred", height=110)
3230
await fred.save(db=db)

0 commit comments

Comments
 (0)