Skip to content

Commit c3315c7

Browse files
authored
Merge pull request #217 from opsmill/stable
Merge stable into develop (1.4.1)
2 parents c067262 + 9d9b99f commit c3315c7

File tree

14 files changed

+812
-365
lines changed

14 files changed

+812
-365
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ jobs:
141141
python-version: ${{ matrix.python-version }}
142142
- name: "Setup environment"
143143
run: |
144-
pipx install poetry
144+
pipx install poetry==1.8.5
145145
poetry config virtualenvs.prefer-active-python true
146146
pip install invoke toml codecov
147147
- name: "Install Package"
@@ -192,7 +192,7 @@ jobs:
192192
python-version: "3.12"
193193
- name: "Setup environment"
194194
run: |
195-
pipx install poetry
195+
pipx install poetry==1.8.5
196196
poetry config virtualenvs.prefer-active-python true
197197
pip install invoke toml codecov
198198
- name: "Install Package"

.github/workflows/publish-pypi.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ jobs:
4141
- name: "Install Poetry"
4242
uses: "snok/install-poetry@v1"
4343
with:
44+
version: 1.8.5
4445
virtualenvs-create: true
4546
virtualenvs-in-project: true
4647
installer-parallel: true

.github/workflows/release.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ jobs:
3030
- name: "Install Poetry"
3131
uses: "snok/install-poetry@v1"
3232
with:
33+
version: 1.8.5
3334
virtualenvs-create: true
3435
virtualenvs-in-project: true
3536
installer-parallel: true
@@ -56,7 +57,7 @@ jobs:
5657
| jq -r '.tag_name') >> "$GITHUB_OUTPUT"
5758
5859
- name: Check tag version
59-
if: github.event.release.tag_name != format('infrahub-v{0}', steps.release.outputs.version)
60+
if: github.event.release.tag_name != format('v{0}', steps.release.outputs.version)
6061
run: |
6162
echo "Tag version does not match python project version"
6263
exit 1

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ This project uses [*towncrier*](https://towncrier.readthedocs.io/) and the chang
1111

1212
<!-- towncrier release notes start -->
1313

14+
## [1.4.1](https://github.com/opsmill/infrahub-sdk-python/tree/v1.3.0) - 2025-01-05
15+
16+
### Fixed
17+
18+
- Fixes an issue introduced in 1.4 that would prevent a node with relationship of cardinality one from being updated.
19+
1420
## [1.4.0](https://github.com/opsmill/infrahub-sdk-python/tree/v1.4.0) - 2025-01-03
1521

1622
### Changed

infrahub_sdk/branch.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class BranchData(BaseModel):
4040

4141

4242
MUTATION_QUERY_DATA = {"ok": None, "object": BRANCH_DATA}
43+
MUTATION_QUERY_TASK = {"ok": None, "task": {"id": None}}
4344

4445
QUERY_ALL_BRANCHES_DATA = {"Branch": BRANCH_DATA}
4546

@@ -119,13 +120,14 @@ async def create(
119120
},
120121
}
121122

122-
query = Mutation(mutation="BranchCreate", input_data=input_data, query=MUTATION_QUERY_DATA)
123+
mutation_query = MUTATION_QUERY_TASK if background_execution else MUTATION_QUERY_DATA
124+
query = Mutation(mutation="BranchCreate", input_data=input_data, query=mutation_query)
123125
response = await self.client.execute_graphql(query=query.render(), tracker="mutation-branch-create")
124126

125127
# Make sure server version is recent enough to support background execution, as previously
126128
# using background_execution=True had no effect.
127129
if background_execution and "task" in response["BranchCreate"]:
128-
return BranchData(**response["BranchCreate"]["task"]["id"])
130+
return response["BranchCreate"]["task"]["id"]
129131
return BranchData(**response["BranchCreate"]["object"])
130132

131133
async def delete(self, branch_name: str) -> bool:

infrahub_sdk/node.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -904,7 +904,7 @@ def _strip_unmodified_dict(data: dict, original_data: dict, variables: dict, ite
904904
variables.pop(variable_key)
905905

906906
# TODO: I do not feel _great_ about this
907-
if not data_item and data_item != []:
907+
if not data_item and data_item != [] and item in data:
908908
data.pop(item)
909909

910910
def _strip_unmodified(self, data: dict, variables: dict) -> tuple[dict, dict]:

infrahub_sdk/schema/main.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,8 @@ class BaseSchema(BaseModel):
259259
description: str | None = None
260260
include_in_menu: bool | None = None
261261
menu_placement: str | None = None
262+
display_labels: list[str] | None = None
263+
human_friendly_id: list[str] | None = None
262264
icon: str | None = None
263265
uniqueness_constraints: list[list[str]] | None = None
264266
documentation: str | None = None
@@ -286,7 +288,6 @@ class BaseNodeSchema(BaseSchema):
286288
inherit_from: list[str] = Field(default_factory=list)
287289
branch: BranchSupportType | None = None
288290
default_filter: str | None = None
289-
human_friendly_id: list[str] | None = None
290291
generate_profile: bool | None = None
291292
parent: str | None = None
292293
children: str | None = None
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import pytest
2+
3+
from infrahub_sdk import InfrahubClient
4+
from infrahub_sdk.exceptions import GraphQLError
5+
from infrahub_sdk.node import InfrahubNode
6+
from infrahub_sdk.schema.main import (
7+
AttributeKind,
8+
GenericSchema,
9+
NodeSchema,
10+
RelationshipDirection,
11+
RelationshipKind,
12+
SchemaRoot,
13+
)
14+
from infrahub_sdk.schema.main import AttributeSchema as Attr
15+
from infrahub_sdk.schema.main import RelationshipSchema as Rel
16+
17+
NAMESPACE = "Testing"
18+
19+
TESTING_ANIMAL = f"{NAMESPACE}Animal"
20+
TESTING_CAT = f"{NAMESPACE}Cat"
21+
TESTING_DOG = f"{NAMESPACE}Dog"
22+
TESTING_PERSON = f"{NAMESPACE}Person"
23+
24+
25+
class SchemaAnimal:
26+
@pytest.fixture(scope="class")
27+
def schema_animal(self) -> GenericSchema:
28+
return GenericSchema(
29+
name="Animal",
30+
namespace=NAMESPACE,
31+
include_in_menu=True,
32+
human_friendly_id=["owner__name__value", "name__value"],
33+
uniqueness_constraints=[
34+
["owner", "name__value"],
35+
],
36+
attributes=[
37+
Attr(name="name", kind=AttributeKind.TEXT),
38+
Attr(name="weight", kind=AttributeKind.NUMBER, optional=True),
39+
],
40+
relationships=[
41+
Rel(
42+
name="owner",
43+
kind=RelationshipKind.GENERIC,
44+
optional=False,
45+
peer=TESTING_PERSON,
46+
cardinality="one",
47+
identifier="person__animal",
48+
direction=RelationshipDirection.OUTBOUND,
49+
),
50+
Rel(
51+
name="best_friend",
52+
kind=RelationshipKind.GENERIC,
53+
optional=True,
54+
peer=TESTING_PERSON,
55+
cardinality="one",
56+
identifier="person__animal_friend",
57+
direction=RelationshipDirection.OUTBOUND,
58+
),
59+
],
60+
)
61+
62+
@pytest.fixture(scope="class")
63+
def schema_dog(self) -> NodeSchema:
64+
return NodeSchema(
65+
name="Dog",
66+
namespace=NAMESPACE,
67+
include_in_menu=True,
68+
inherit_from=[TESTING_ANIMAL],
69+
display_labels=["name__value", "breed__value"],
70+
attributes=[
71+
Attr(name="breed", kind=AttributeKind.TEXT, optional=False),
72+
Attr(name="color", kind=AttributeKind.COLOR, default_value="#444444", optional=True),
73+
],
74+
)
75+
76+
@pytest.fixture(scope="class")
77+
def schema_cat(self) -> NodeSchema:
78+
return NodeSchema(
79+
name="Cat",
80+
namespace=NAMESPACE,
81+
include_in_menu=True,
82+
inherit_from=[TESTING_ANIMAL],
83+
display_labels=["name__value", "breed__value", "color__value"],
84+
attributes=[
85+
Attr(name="breed", kind=AttributeKind.TEXT, optional=False),
86+
Attr(name="color", kind=AttributeKind.COLOR, default_value="#555555", optional=True),
87+
],
88+
)
89+
90+
@pytest.fixture(scope="class")
91+
def schema_person(self) -> NodeSchema:
92+
return NodeSchema(
93+
name="Person",
94+
namespace=NAMESPACE,
95+
include_in_menu=True,
96+
display_labels=["name__value"],
97+
default_filter="name__value",
98+
human_friendly_id=["name__value"],
99+
attributes=[
100+
Attr(name="name", kind=AttributeKind.TEXT, unique=True),
101+
Attr(name="height", kind=AttributeKind.NUMBER, optional=True),
102+
],
103+
relationships=[
104+
Rel(
105+
name="animals",
106+
peer=TESTING_ANIMAL,
107+
identifier="person__animal",
108+
cardinality="many",
109+
direction=RelationshipDirection.INBOUND,
110+
),
111+
Rel(
112+
name="best_friends",
113+
peer=TESTING_ANIMAL,
114+
identifier="person__animal_friend",
115+
cardinality="many",
116+
direction=RelationshipDirection.INBOUND,
117+
),
118+
],
119+
)
120+
121+
@pytest.fixture(scope="class")
122+
def schema_base(
123+
self,
124+
schema_animal: GenericSchema,
125+
schema_person: NodeSchema,
126+
schema_cat: NodeSchema,
127+
schema_dog: NodeSchema,
128+
) -> SchemaRoot:
129+
return SchemaRoot(version="1.0", generics=[schema_animal], nodes=[schema_person, schema_cat, schema_dog])
130+
131+
@pytest.fixture(scope="class")
132+
async def load_schema(self, client: InfrahubClient, schema_base: SchemaRoot) -> None:
133+
resp = await client.schema.load(schemas=[schema_base.to_schema_dict()], wait_until_converged=True)
134+
if resp.errors:
135+
raise GraphQLError(errors=[resp.errors])
136+
137+
@pytest.fixture(scope="class")
138+
async def person_liam(self, client: InfrahubClient) -> InfrahubNode:
139+
obj = await client.create(kind=TESTING_PERSON, name="Liam Walker", height=178)
140+
await obj.save()
141+
return obj
142+
143+
@pytest.fixture(scope="class")
144+
async def person_sophia(self, client: InfrahubClient) -> InfrahubNode:
145+
obj = await client.create(kind=TESTING_PERSON, name="Sophia Walker", height=168)
146+
await obj.save()
147+
return obj
148+
149+
@pytest.fixture(scope="class")
150+
async def person_ethan(self, client: InfrahubClient) -> InfrahubNode:
151+
obj = await client.create(kind=TESTING_PERSON, name="Ethan Carter", height=185)
152+
await obj.save()
153+
return obj
154+
155+
@pytest.fixture(scope="class")
156+
async def cat_bella(self, client: InfrahubClient, person_ethan: InfrahubNode) -> InfrahubNode:
157+
obj = await client.create(kind=TESTING_CAT, name="Bella", breed="Bengal", color="#123456", owner=person_ethan)
158+
await obj.save()
159+
return obj
160+
161+
@pytest.fixture(scope="class")
162+
async def cat_luna(self, client: InfrahubClient, person_ethan: InfrahubNode) -> InfrahubNode:
163+
obj = await client.create(kind=TESTING_CAT, name="Luna", breed="Siamese", color="#FFFFFF", owner=person_ethan)
164+
await obj.save()
165+
return obj
166+
167+
@pytest.fixture(scope="class")
168+
async def dog_daisy(self, client: InfrahubClient, person_ethan: InfrahubNode) -> InfrahubNode:
169+
obj = await client.create(
170+
kind=TESTING_DOG, name="Daisy", breed="French Bulldog", color="#7B7D7D", owner=person_ethan
171+
)
172+
await obj.save()
173+
return obj
174+
175+
@pytest.fixture(scope="class")
176+
async def dog_rocky(self, client: InfrahubClient, person_sophia: InfrahubNode) -> InfrahubNode:
177+
obj = await client.create(
178+
kind=TESTING_DOG, name="Rocky", breed="German Shepherd", color="#784212", owner=person_sophia
179+
)
180+
await obj.save()
181+
return obj

0 commit comments

Comments
 (0)