Skip to content

Commit 379d288

Browse files
Copilotminitriga
andcommitted
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 4fd8376 commit 379d288

File tree

3 files changed

+47
-6
lines changed

3 files changed

+47
-6
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
@@ -192,7 +192,7 @@ def is_resource_pool(self) -> bool:
192192
def get_raw_graphql_data(self) -> dict | None:
193193
return self._data
194194

195-
def _generate_input_data( # noqa: C901
195+
def _generate_input_data( # noqa: C901, PLR0915
196196
self,
197197
exclude_unmodified: bool = False,
198198
exclude_hfid: bool = False,
@@ -235,7 +235,10 @@ def _generate_input_data( # noqa: C901
235235
rel: RelatedNodeBase | RelationshipManagerBase = getattr(self, item_name)
236236

237237
if rel_schema.cardinality == RelationshipCardinality.ONE and rel_schema.optional and not rel.initialized:
238-
data[item_name] = None
238+
# Only include None for existing nodes to allow clearing relationships
239+
# For new nodes, omit the field to allow object template defaults to be applied
240+
if self._existing:
241+
data[item_name] = None
239242
continue
240243

241244
if rel is None or not rel.initialized:

tests/unit/sdk/test_node.py

Lines changed: 41 additions & 4 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,10 +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
2021

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

2430
# type: ignore[attr-defined]
2531

@@ -1365,7 +1371,6 @@ async def test_create_input_data(client, location_schema: NodeSchemaAPI, client_
13651371
"name": {"value": "JFK1"},
13661372
"description": {"value": "JFK Airport"},
13671373
"type": {"value": "SITE"},
1368-
"primary_tag": None,
13691374
}
13701375
}
13711376

@@ -1393,6 +1398,38 @@ async def test_create_input_data_with_dropdown(client, location_schema_with_drop
13931398
"description": {"value": "JFK Airport"},
13941399
"type": {"value": "SITE"},
13951400
"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"},
13961433
"primary_tag": None,
13971434
}
13981435
}
@@ -1641,7 +1678,7 @@ async def test_create_input_data_with_IPHost_attribute(client, ipaddress_schema,
16411678
ip_address = InfrahubNodeSync(client=client, schema=ipaddress_schema, data=data)
16421679

16431680
assert ip_address._generate_input_data()["data"] == {
1644-
"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}}
16451682
}
16461683

16471684

@@ -1655,7 +1692,7 @@ async def test_create_input_data_with_IPNetwork_attribute(client, ipnetwork_sche
16551692
ip_network = InfrahubNodeSync(client=client, schema=ipnetwork_schema, data=data)
16561693

16571694
assert ip_network._generate_input_data()["data"] == {
1658-
"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}}
16591696
}
16601697

16611698

0 commit comments

Comments
 (0)