Skip to content

Commit

Permalink
Add ability to construct HFIDs from payload for upsert mutations
Browse files Browse the repository at this point in the history
  • Loading branch information
ogenstad committed Sep 18, 2024
1 parent f3b8c0f commit 573efc2
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 15 deletions.
70 changes: 59 additions & 11 deletions backend/infrahub/graphql/mutations/node_getter/by_hfid.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from infrahub.core.branch import Branch
from infrahub.core.manager import NodeManager
from infrahub.core.node import Node
from infrahub.core.registry import registry
from infrahub.core.schema import MainSchemaTypes
from infrahub.database import InfrahubDatabase

Expand All @@ -23,14 +24,61 @@ async def get_node(
branch: Branch,
at: str,
) -> Optional[Node]:
node = None
if not node_schema.human_friendly_id or "hfid" not in data:
return node

return await self.node_manager.get_one_by_hfid(
db=self.db,
hfid=data["hfid"],
kind=node_schema.kind,
branch=branch,
at=at,
)
if not node_schema.human_friendly_id:
return None

if "hfid" in data:
return await self.node_manager.get_one_by_hfid(
db=self.db,
hfid=data["hfid"],
kind=node_schema.kind,
branch=branch,
at=at,
)

for component in node_schema.human_friendly_id:
name = component.split("__")[0]
if name not in data.keys():
# The update neither includes "hfid" or all components to form an hfid:
return None

schema_branch = registry.schema.get_schema_branch(name=branch.name)
hfid: list[str] = []
for component in node_schema.human_friendly_id:
attribute_path = node_schema.parse_schema_path(path=component, schema=schema_branch)

if attribute_path.is_type_attribute and attribute_path.attribute_schema:
hfid_component = data[attribute_path.attribute_schema.name].get(attribute_path.attribute_property_name)
if hfid_component is not None:
hfid.append(hfid_component)
if (
attribute_path.relationship_schema
and attribute_path.related_schema
and attribute_path.attribute_property_name
and attribute_path.attribute_schema
):
related_node = await self.node_manager.find_object(
db=self.db,
kind=attribute_path.relationship_schema.peer,
branch=branch,
at=at,
id=data[attribute_path.relationship_schema.name].get("id"),
hfid=data[attribute_path.relationship_schema.name].get("hfid"),
)
relationship_attribute = getattr(related_node, attribute_path.attribute_schema.name)
relationship_attribute_value = getattr(relationship_attribute, attribute_path.attribute_property_name)
if relationship_attribute_value is None:
return None

hfid.append(str(relationship_attribute_value))

if len(hfid) == len(node_schema.human_friendly_id):
return await self.node_manager.get_one_by_hfid(
db=self.db,
hfid=hfid,
kind=node_schema.kind,
branch=branch,
at=at,
)

return None
1 change: 1 addition & 0 deletions backend/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ async def animal_person_schema_unregistered(db: InfrahubDatabase, node_group_sch
"name": "Person",
"namespace": "Test",
"display_labels": ["name__value"],
"default_filter": "name__value",
"human_friendly_id": ["name__value"],
"attributes": [
{"name": "name", "kind": "Text", "unique": True},
Expand Down
83 changes: 83 additions & 0 deletions backend/tests/unit/graphql/test_mutation_upsert.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,3 +310,86 @@ async def test_with_hfid_new(db: InfrahubDatabase, default_branch, animal_person
"id": new_id,
"name": {"value": "Bella"},
}


async def test_with_constructed_hfid(db: InfrahubDatabase, default_branch, animal_person_schema) -> None:
"""Validate that we can construct an HFID out of the payload without specifying all parts."""
person_schema = animal_person_schema.get(name="TestPerson")

person1 = await Node.init(db=db, schema=person_schema, branch=default_branch)
await person1.new(db=db, name="John Snow")
await person1.save(db=db)

query = """
mutation UpsertWolf($owner: String!, $weight: BigInt!) {
TestDogUpsert(data: {
name: { value: "Ghost" },
breed: { value: "Direwolf" },
color: { value: "White" },
owner: { id: $owner },
weight: { value: $weight }
}) {
ok
object {
id
name {
value
}
color {
value
}
breed {
value
}
weight {
value
}
}
}
}
"""
gql_params = prepare_graphql_params(db=db, include_subscription=False, branch=default_branch)

# Create initial node
initial_weight = 14
create_result = await graphql(
schema=gql_params.schema,
source=query,
context_value=gql_params.context,
root_value=None,
variable_values={"owner": "John Snow", "weight": initial_weight},
)

# Update previously created node
updated_weight = 68
update_result = await graphql(
schema=gql_params.schema,
source=query,
context_value=gql_params.context,
root_value=None,
variable_values={"owner": "John Snow", "weight": updated_weight},
)

assert create_result.errors is None
assert create_result.data
assert create_result.data["TestDogUpsert"]["ok"] is True
ghost_id = create_result.data["TestDogUpsert"]["object"]["id"]
assert create_result.data["TestDogUpsert"]["object"] == {
"breed": {"value": "Direwolf"},
"color": {"value": "White"},
"id": ghost_id,
"name": {"value": "Ghost"},
"weight": {"value": initial_weight},
}

assert update_result.errors is None
assert update_result.data
assert update_result.data["TestDogUpsert"]["ok"] is True
assert ghost_id == update_result.data["TestDogUpsert"]["object"]["id"]
assert update_result.data["TestDogUpsert"]["object"] == {
"breed": {"value": "Direwolf"},
"color": {"value": "White"},
"id": ghost_id,
"name": {"value": "Ghost"},
"weight": {"value": updated_weight},
}
1 change: 1 addition & 0 deletions changelog/4167.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add ability to construct HFIDs from payload for upsert mutations
7 changes: 3 additions & 4 deletions python_sdk/infrahub_sdk/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -725,12 +725,11 @@ def get_human_friendly_id(self) -> Optional[list[str]]:
if not self._schema.human_friendly_id:
return None

# If all components of an HFID are null, we cannot identify a single node
# If an HFID component is missing we assume that it is invalid and not usable for this node
hfid_components = [self.get_path_value(path=item) for item in self._schema.human_friendly_id]
if all(c is None for c in hfid_components):
if None in hfid_components:
return None

return [str(c) for c in hfid_components]
return [str(hfid) for hfid in hfid_components]

def get_human_friendly_id_as_string(self, include_kind: bool = False) -> Optional[str]:
hfid = self.get_human_friendly_id()
Expand Down

0 comments on commit 573efc2

Please sign in to comment.