Skip to content

Commit 482c969

Browse files
authored
IHS-152 Allow unsetting one-to-one relationship (#515)
* Allow representing null values in graphql * Keep null values when stripping unmodified data
1 parent 11cf607 commit 482c969

File tree

5 files changed

+37
-17
lines changed

5 files changed

+37
-17
lines changed

changelog/479.fixed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Allow unsetting optional relationship of cardinality one by setting its value to `None`

infrahub_sdk/graphql.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
VARIABLE_TYPE_MAPPING = ((str, "String!"), (int, "Int!"), (float, "Float!"), (bool, "Boolean!"))
99

1010

11-
def convert_to_graphql_as_string(value: str | bool | list | BaseModel | Enum | Any, convert_enum: bool = False) -> str: # noqa: PLR0911
11+
def convert_to_graphql_as_string(value: Any, convert_enum: bool = False) -> str: # noqa: PLR0911
12+
if value is None:
13+
return "null"
1214
if isinstance(value, str) and value.startswith("$"):
1315
return value
1416
if isinstance(value, Enum):

infrahub_sdk/node/node.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -234,15 +234,10 @@ def _generate_input_data( # noqa: C901
234234

235235
rel: RelatedNodeBase | RelationshipManagerBase = getattr(self, item_name)
236236

237-
# BLOCKED by https://github.com/opsmill/infrahub/issues/330
238-
# if (
239-
# item is None
240-
# and item_name in self._relationships
241-
# and self._schema.get_relationship(item_name).cardinality == "one"
242-
# ):
243-
# data[item_name] = None
244-
# continue
245-
# el
237+
if rel_schema.cardinality == RelationshipCardinality.ONE and rel_schema.optional and not rel.initialized:
238+
data[item_name] = None
239+
continue
240+
246241
if rel is None or not rel.initialized:
247242
continue
248243

@@ -315,7 +310,16 @@ def _strip_unmodified_dict(data: dict, original_data: dict, variables: dict, ite
315310
variables.pop(variable_key)
316311

317312
# TODO: I do not feel _great_ about this
318-
if not data_item and data_item != [] and item in data:
313+
# -> I don't even know who you are (but this is not great indeed) -- gmazoyer (quoting Thanos)
314+
original_data_item = original_data.get(item)
315+
original_data_item_is_none = original_data_item is None
316+
if isinstance(original_data_item, dict):
317+
if "node" in original_data_item:
318+
original_data_item_is_none = original_data_item["node"] is None
319+
elif "id" not in original_data_item:
320+
original_data_item_is_none = True
321+
322+
if item in data and (data_item in ({}, []) or (data_item is None and original_data_item_is_none)):
319323
data.pop(item)
320324

321325
def _strip_unmodified(self, data: dict, variables: dict) -> tuple[dict, dict]:
@@ -324,7 +328,9 @@ def _strip_unmodified(self, data: dict, variables: dict) -> tuple[dict, dict]:
324328
relationship_property = getattr(self, relationship)
325329
if not relationship_property or relationship not in data:
326330
continue
327-
if not relationship_property.initialized:
331+
if not relationship_property.initialized and (
332+
not isinstance(relationship_property, RelatedNodeBase) or not relationship_property.schema.optional
333+
):
328334
data.pop(relationship)
329335
elif isinstance(relationship_property, RelationshipManagerBase) and not relationship_property.has_update:
330336
data.pop(relationship)

tests/integration/test_infrahub_client.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,12 @@ async def test_create_generic_rel_with_hfid(
170170
person_sophia = await client.get(kind=TESTING_PERSON, id=person_sophia.id, prefetch_relationships=True)
171171
assert person_sophia.favorite_animal.id == cat_luna.id
172172

173+
# Ensure that nullify it will remove the relationship related node
174+
person_sophia.favorite_animal = None
175+
await person_sophia.save()
176+
person_sophia = await client.get(kind=TESTING_PERSON, id=person_sophia.id, prefetch_relationships=True)
177+
assert not person_sophia.favorite_animal.id
178+
173179
async def test_task_query(self, client: InfrahubClient, base_dataset, set_pagination_size3) -> None:
174180
nbr_tasks = await client.task.count()
175181
assert nbr_tasks

tests/unit/sdk/test_node.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1330,7 +1330,12 @@ async def test_create_input_data(client, location_schema: NodeSchemaAPI, client_
13301330
node = InfrahubNodeSync(client=client, schema=location_schema, data=data)
13311331

13321332
assert node._generate_input_data()["data"] == {
1333-
"data": {"name": {"value": "JFK1"}, "description": {"value": "JFK Airport"}, "type": {"value": "SITE"}}
1333+
"data": {
1334+
"name": {"value": "JFK1"},
1335+
"description": {"value": "JFK Airport"},
1336+
"type": {"value": "SITE"},
1337+
"primary_tag": None,
1338+
}
13341339
}
13351340

13361341

@@ -1577,7 +1582,7 @@ async def test_create_input_data_with_IPHost_attribute(client, ipaddress_schema,
15771582
ip_address = InfrahubNodeSync(client=client, schema=ipaddress_schema, data=data)
15781583

15791584
assert ip_address._generate_input_data()["data"] == {
1580-
"data": {"address": {"value": "1.1.1.1/24", "is_protected": True}}
1585+
"data": {"address": {"value": "1.1.1.1/24", "is_protected": True}, "interface": None}
15811586
}
15821587

15831588

@@ -1591,7 +1596,7 @@ async def test_create_input_data_with_IPNetwork_attribute(client, ipnetwork_sche
15911596
ip_network = InfrahubNodeSync(client=client, schema=ipnetwork_schema, data=data)
15921597

15931598
assert ip_network._generate_input_data()["data"] == {
1594-
"data": {"network": {"value": "1.1.1.0/24", "is_protected": True}}
1599+
"data": {"network": {"value": "1.1.1.0/24", "is_protected": True}, "site": None}
15951600
}
15961601

15971602

@@ -1789,7 +1794,7 @@ async def test_update_input_data_empty_relationship(
17891794
"data": {
17901795
"id": "llllllll-llll-llll-llll-llllllllllll",
17911796
"name": {"value": "DFW"},
1792-
# "primary_tag": None,
1797+
"primary_tag": None,
17931798
"tags": [],
17941799
"type": {"value": "SITE"},
17951800
},
@@ -1798,7 +1803,7 @@ async def test_update_input_data_empty_relationship(
17981803
"data": {
17991804
"id": "llllllll-llll-llll-llll-llllllllllll",
18001805
"name": {"is_protected": True, "is_visible": True, "value": "DFW"},
1801-
# "primary_tag": None,
1806+
"primary_tag": None,
18021807
"tags": [],
18031808
"type": {"is_protected": True, "is_visible": True, "value": "SITE"},
18041809
},

0 commit comments

Comments
 (0)