-
Notifications
You must be signed in to change notification settings - Fork 6
Add convert_object_type method #554
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
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Add `convert_object_type` method to allow converting an object to another type. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| from __future__ import annotations | ||
|
|
||
| from typing import Any | ||
|
|
||
| from pydantic import BaseModel, model_validator | ||
|
|
||
| CONVERT_OBJECT_MUTATION = """ | ||
| mutation($node_id: String!, $target_kind: String!, $fields_mapping: GenericScalar!) { | ||
| ConvertObjectType(data: { | ||
| node_id: $node_id, | ||
| target_kind: $target_kind, | ||
| fields_mapping: $fields_mapping | ||
| }) { | ||
| ok | ||
| node | ||
| } | ||
| } | ||
| """ | ||
|
|
||
|
|
||
| class ConversionFieldValue(BaseModel): # Only one of these fields can be not None | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll make a follow up PR infrahub side to use these classes from the SDK instead of having them duplicated |
||
| """ | ||
| Holds the new value of the destination field during an object conversion. | ||
| Use `attribute_value` to specify the new raw value of an attribute. | ||
| Use `peer_id` to specify new peer of a cardinality one relationship. | ||
| Use `peer_ids` to specify new peers of a cardinality many relationship. | ||
| Only one of `attribute_value`, `peer_id` and `peers_ids` can be specified. | ||
| """ | ||
|
|
||
| attribute_value: Any | None = None | ||
| peer_id: str | None = None | ||
| peers_ids: list[str] | None = None | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the docstring says There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Going for |
||
|
|
||
| @model_validator(mode="after") | ||
| def check_only_one_field(self) -> ConversionFieldValue: | ||
| fields = [self.attribute_value, self.peer_id, self.peers_ids] | ||
| set_fields = [f for f in fields if f is not None] | ||
| if len(set_fields) != 1: | ||
| raise ValueError("Exactly one of attribute_value, peer_id, or peers_ids must be set") | ||
| return self | ||
|
|
||
|
|
||
| class ConversionFieldInput(BaseModel): | ||
| """ | ||
| Indicates how to fill in the value of the destination field during an object conversion. | ||
| Use `source_field` to reuse the value of the corresponding field of the object being converted. | ||
| Use `data` to specify the new value for the field. | ||
| Only one of `source_field` or `data` can be specified. | ||
| """ | ||
|
|
||
| source_field: str | None = None | ||
| data: ConversionFieldValue | None = None | ||
|
|
||
| @model_validator(mode="after") | ||
| def check_only_one_field(self) -> ConversionFieldInput: | ||
| if self.source_field is not None and self.data is not None: | ||
| raise ValueError("Only one of source_field or data can be set") | ||
| if self.source_field is None and self.data is None: | ||
| raise ValueError("Either source_field or data must be set") | ||
| return self | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| CLIENT_TYPE_ASYNC = "standard" | ||
| CLIENT_TYPE_SYNC = "sync" | ||
| CLIENT_TYPES = [CLIENT_TYPE_ASYNC, CLIENT_TYPE_SYNC] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| from __future__ import annotations | ||
|
|
||
| import uuid | ||
| from typing import Any | ||
|
|
||
| import pytest | ||
|
|
||
| from infrahub_sdk.convert_object_type import ConversionFieldInput, ConversionFieldValue | ||
| from infrahub_sdk.testing.docker import TestInfrahubDockerClient | ||
| from tests.constants import CLIENT_TYPE_ASYNC, CLIENT_TYPES | ||
|
|
||
| SCHEMA: dict[str, Any] = { | ||
| "version": "1.0", | ||
| "generics": [ | ||
| { | ||
| "name": "PersonGeneric", | ||
| "namespace": "Testconv", | ||
| "human_friendly_id": ["name__value"], | ||
| "attributes": [ | ||
| {"name": "name", "kind": "Text", "unique": True}, | ||
| ], | ||
| }, | ||
| ], | ||
| "nodes": [ | ||
| { | ||
| "name": "Person1", | ||
| "namespace": "Testconv", | ||
| "inherit_from": ["TestconvPersonGeneric"], | ||
| }, | ||
| { | ||
| "name": "Person2", | ||
| "namespace": "Testconv", | ||
| "inherit_from": ["TestconvPersonGeneric"], | ||
| "attributes": [ | ||
| {"name": "age", "kind": "Number"}, | ||
| ], | ||
| "relationships": [ | ||
| { | ||
| "name": "my_car", | ||
| "peer": "TestconvCar", | ||
| "cardinality": "one", | ||
| "identifier": "person__mandatory_owner", | ||
| }, | ||
| { | ||
| "name": "fastest_cars", | ||
| "peer": "TestconvCar", | ||
| "cardinality": "many", | ||
| "identifier": "person__fastest_cars", | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| "name": "Car", | ||
| "namespace": "Testconv", | ||
| "human_friendly_id": ["name__value"], | ||
| "attributes": [ | ||
| {"name": "name", "kind": "Text"}, | ||
| ], | ||
| }, | ||
| ], | ||
| } | ||
|
|
||
|
|
||
| class TestConvertObjectType(TestInfrahubDockerClient): | ||
| @pytest.mark.parametrize("client_type", CLIENT_TYPES) | ||
| async def test_convert_object_type(self, client, client_sync, client_type) -> None: | ||
| resp = await client.schema.load(schemas=[SCHEMA], wait_until_converged=True) | ||
| assert not resp.errors | ||
|
|
||
| person_1 = await client.create(kind="TestconvPerson1", name=f"person_{uuid.uuid4()}") | ||
| await person_1.save() | ||
| car_1 = await client.create(kind="TestconvCar", name=f"car_{uuid.uuid4()}") | ||
| await car_1.save() | ||
|
|
||
| new_age = 25 | ||
| fields_mapping = { | ||
| "name": ConversionFieldInput(source_field="name"), | ||
| "age": ConversionFieldInput(data=ConversionFieldValue(attribute_value=new_age)), | ||
| "worst_car": ConversionFieldInput(data=ConversionFieldValue(peer_id=car_1.id)), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this in here to test that it is ignored? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, I updated schema to replace |
||
| "fastest_cars": ConversionFieldInput(data=ConversionFieldValue(peers_ids=[car_1.id])), | ||
| } | ||
LucasG0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| if client_type == CLIENT_TYPE_ASYNC: | ||
| person_2 = await client.convert_object_type( | ||
| node_id=person_1.id, | ||
| target_kind="TestconvPerson2", | ||
| branch=client.default_branch, | ||
| fields_mapping=fields_mapping, | ||
| ) | ||
| else: | ||
| person_2 = client_sync.convert_object_type( | ||
| node_id=person_1.id, | ||
| target_kind="TestconvPerson2", | ||
| branch=client.default_branch, | ||
| fields_mapping=fields_mapping, | ||
| ) | ||
|
|
||
| assert person_2.get_kind() == "TestconvPerson2" | ||
| assert person_2.name.value == person_1.name.value | ||
| assert person_2.age.value == new_age | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe worth testing that |
||
Uh oh!
There was an error while loading. Please reload this page.