Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to construct HFIDs from payload for upsert mutations #4339

Merged
merged 1 commit into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -345,6 +345,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
Loading