Skip to content

Commit 1f1cb0c

Browse files
Copilotminitriga
andauthored
Fix optional relationships preventing object template defaults from applying (#634)
* Initial plan * Fix optional attributes not being set when using object templates - Modified _generate_input_data() to skip adding None for optional relationships on new nodes - For existing nodes, still include None to allow clearing relationships (preserves PR #515 behavior) - Updated tests to reflect new behavior - Added test to verify existing nodes still work correctly Co-authored-by: minitriga <[email protected]> * Add type annotations to test parameters and use BothClients fixture - Added BothClients import to TYPE_CHECKING block - Updated test_update_input_data_existing_node_with_optional_relationship to use clients fixture with proper type annotations - Changed from client fixture to clients fixture (clients.standard and clients.sync) - Added type annotation for client_type parameter Co-authored-by: minitriga <[email protected]> * Use __future__ annotations and add changelog for issue 630 - Added `from __future__ import annotations` to enable postponed evaluation - Changed `clients: "BothClients"` to `clients: BothClients` without string quotes - Moved GenericSchema, NodeSchemaAPI, and HTTPXMock imports to TYPE_CHECKING block - Added changelog/630.fixed.md documenting the fix Co-authored-by: minitriga <[email protected]> * Fix linting: correct import path in TYPE_CHECKING block Changed `from tests.unit.sdk.conftest import BothClients` to `from .conftest import BothClients` to fix ruff import sorting error after merge from stable branch. Co-authored-by: minitriga <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: minitriga <[email protected]> Co-authored-by: Alex Gittings <[email protected]>
1 parent eadf3f4 commit 1f1cb0c

File tree

3 files changed

+47
-7
lines changed

3 files changed

+47
-7
lines changed

changelog/630.fixed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed SDK including explicit `null` values for uninitialized optional relationships when creating nodes with object templates, which prevented the backend from applying template defaults.

infrahub_sdk/node/node.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ def is_resource_pool(self) -> bool:
210210
def get_raw_graphql_data(self) -> dict | None:
211211
return self._data
212212

213-
def _generate_input_data( # noqa: C901
213+
def _generate_input_data( # noqa: C901, PLR0915
214214
self,
215215
exclude_unmodified: bool = False,
216216
exclude_hfid: bool = False,
@@ -253,7 +253,10 @@ def _generate_input_data( # noqa: C901
253253
rel: RelatedNodeBase | RelationshipManagerBase = getattr(self, item_name)
254254

255255
if rel_schema.cardinality == RelationshipCardinality.ONE and rel_schema.optional and not rel.initialized:
256-
data[item_name] = None
256+
# Only include None for existing nodes to allow clearing relationships
257+
# For new nodes, omit the field to allow object template defaults to be applied
258+
if self._existing:
259+
data[item_name] = None
257260
continue
258261

259262
if rel is None or not rel.initialized:

tests/unit/sdk/test_node.py

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import annotations
2+
13
import inspect
24
import ipaddress
35
from typing import TYPE_CHECKING
@@ -16,11 +18,14 @@
1618
)
1719
from infrahub_sdk.node.constants import SAFE_VALUE
1820
from infrahub_sdk.node.related_node import RelatedNode, RelatedNodeSync
19-
from infrahub_sdk.schema import GenericSchema, NodeSchemaAPI
20-
from tests.unit.sdk.conftest import BothClients
2121

2222
if TYPE_CHECKING:
23+
from pytest_httpx import HTTPXMock
24+
2325
from infrahub_sdk.client import InfrahubClient, InfrahubClientSync
26+
from infrahub_sdk.schema import GenericSchema, NodeSchemaAPI
27+
28+
from .conftest import BothClients
2429

2530
# type: ignore[attr-defined]
2631

@@ -1366,7 +1371,6 @@ async def test_create_input_data(client, location_schema: NodeSchemaAPI, client_
13661371
"name": {"value": "JFK1"},
13671372
"description": {"value": "JFK Airport"},
13681373
"type": {"value": "SITE"},
1369-
"primary_tag": None,
13701374
}
13711375
}
13721376

@@ -1394,6 +1398,38 @@ async def test_create_input_data_with_dropdown(client, location_schema_with_drop
13941398
"description": {"value": "JFK Airport"},
13951399
"type": {"value": "SITE"},
13961400
"status": {"value": None},
1401+
}
1402+
}
1403+
1404+
1405+
@pytest.mark.parametrize("client_type", client_types)
1406+
async def test_update_input_data_existing_node_with_optional_relationship(
1407+
clients: BothClients, location_schema: NodeSchemaAPI, client_type: str
1408+
) -> None:
1409+
"""Validate that existing nodes include None for uninitialized optional relationships.
1410+
1411+
This ensures that we can explicitly clear optional relationships when updating existing nodes.
1412+
"""
1413+
# Simulate an existing node by including an id
1414+
data = {
1415+
"id": "existing-node-id",
1416+
"name": {"value": "JFK1"},
1417+
"description": {"value": "JFK Airport"},
1418+
"type": {"value": "SITE"},
1419+
}
1420+
1421+
if client_type == "standard":
1422+
node = InfrahubNode(client=clients.standard, schema=location_schema, data=data)
1423+
else:
1424+
node = InfrahubNodeSync(client=clients.sync, schema=location_schema, data=data)
1425+
1426+
# For existing nodes, optional uninitialized relationships should include None
1427+
assert node._generate_input_data()["data"] == {
1428+
"data": {
1429+
"id": "existing-node-id",
1430+
"name": {"value": "JFK1"},
1431+
"description": {"value": "JFK Airport"},
1432+
"type": {"value": "SITE"},
13971433
"primary_tag": None,
13981434
}
13991435
}
@@ -1642,7 +1678,7 @@ async def test_create_input_data_with_IPHost_attribute(client, ipaddress_schema,
16421678
ip_address = InfrahubNodeSync(client=client, schema=ipaddress_schema, data=data)
16431679

16441680
assert ip_address._generate_input_data()["data"] == {
1645-
"data": {"address": {"value": "1.1.1.1/24", "is_protected": True}, "interface": None}
1681+
"data": {"address": {"value": "1.1.1.1/24", "is_protected": True}}
16461682
}
16471683

16481684

@@ -1656,7 +1692,7 @@ async def test_create_input_data_with_IPNetwork_attribute(client, ipnetwork_sche
16561692
ip_network = InfrahubNodeSync(client=client, schema=ipnetwork_schema, data=data)
16571693

16581694
assert ip_network._generate_input_data()["data"] == {
1659-
"data": {"network": {"value": "1.1.1.0/24", "is_protected": True}, "site": None}
1695+
"data": {"network": {"value": "1.1.1.0/24", "is_protected": True}}
16601696
}
16611697

16621698

0 commit comments

Comments
 (0)