Skip to content

Commit 760cf3a

Browse files
authored
Merge develop into infrahub-develop (#489)
2 parents 021a378 + 0cde3c6 commit 760cf3a

28 files changed

+587
-118
lines changed

CHANGELOG.md

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

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

14+
## [1.13.5](https://github.com/opsmill/infrahub-sdk-python/tree/v1.13.5) - 2025-07-23
15+
16+
### Fixed
17+
18+
- Respect ordering when loading files from a directory
19+
20+
## [1.13.4](https://github.com/opsmill/infrahub-sdk-python/tree/v1.13.4) - 2025-07-22
21+
22+
### Fixed
23+
24+
- Fix processing of relationshhip during nodes retrieval using the Sync Client, when prefecthing related_nodes. ([#461](https://github.com/opsmill/infrahub-sdk-python/issues/461))
25+
- Fix schema loading to ignore non-YAML files in folders. ([#462](https://github.com/opsmill/infrahub-sdk-python/issues/462))
26+
- Fix ignored node variable in filters(). ([#469](https://github.com/opsmill/infrahub-sdk-python/issues/469))
27+
- Fix use of parallel with filters for Infrahub Client Sync.
28+
- Avoid sending empty list to infrahub if no valids schemas are found.
29+
30+
## [1.13.3](https://github.com/opsmill/infrahub-sdk-python/tree/v1.13.3) - 2025-06-30
31+
32+
### Fixed
33+
34+
- Update InfrahubNode creation to include __typename, display_label, and kind from a RelatedNode ([#455](https://github.com/opsmill/infrahub-sdk-python/issues/455))
35+
1436
## [1.13.2](https://github.com/opsmill/infrahub-sdk-python/tree/v1.13.2) - 2025-06-27
1537

1638
### Fixed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
add support for NumberPool attributes in generated protocols

changelog/+batch.fixed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Create a new batch while fetching relationships instead of using the reusing the same one.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Update internal calls to `count` to include the branch parameter so that the query is performed on the correct branch

changelog/466.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added `infrahubctl repository init` command to allow the initialization of an Infrahub repository using [infrahub-template](https://github.com/opsmill/infrahub-template).

changelog/6882.fixed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix value lookup using a flat notation like `foo__bar__value` with relationships of cardinality one

docs/docs/infrahubctl/infrahubctl-repository.mdx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ $ infrahubctl repository [OPTIONS] COMMAND [ARGS]...
1919
**Commands**:
2020

2121
* `add`: Add a new repository.
22+
* `init`: Initialize a new Infrahub repository.
2223
* `list`
2324

2425
## `infrahubctl repository add`
@@ -47,6 +48,29 @@ $ infrahubctl repository add [OPTIONS] NAME LOCATION
4748
* `--config-file TEXT`: [env var: INFRAHUBCTL_CONFIG; default: infrahubctl.toml]
4849
* `--help`: Show this message and exit.
4950

51+
## `infrahubctl repository init`
52+
53+
Initialize a new Infrahub repository.
54+
55+
**Usage**:
56+
57+
```console
58+
$ infrahubctl repository init [OPTIONS] DIRECTORY
59+
```
60+
61+
**Arguments**:
62+
63+
* `DIRECTORY`: Directory path for the new project. [required]
64+
65+
**Options**:
66+
67+
* `--template TEXT`: Template to use for the new repository. Can be a local path or a git repository URL. [default: https://github.com/opsmill/infrahub-template.git]
68+
* `--data PATH`: Path to YAML file containing answers to CLI prompt.
69+
* `--vcs-ref TEXT`: VCS reference to use for the template. Defaults to HEAD. [default: HEAD]
70+
* `--trust / --no-trust`: Trust the template repository. If set, the template will be cloned without verification. [default: no-trust]
71+
* `--config-file TEXT`: [env var: INFRAHUBCTL_CONFIG; default: infrahubctl.toml]
72+
* `--help`: Show this message and exit.
73+
5074
## `infrahubctl repository list`
5175

5276
**Usage**:

infrahub_sdk/client.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -784,7 +784,6 @@ async def filters(
784784
if at:
785785
at = Timestamp(at)
786786

787-
node = InfrahubNode(client=self, schema=schema, branch=branch)
788787
filters = kwargs
789788
pagination_size = self.pagination_size
790789

@@ -825,12 +824,12 @@ async def process_batch() -> tuple[list[InfrahubNode], list[InfrahubNode]]:
825824
nodes = []
826825
related_nodes = []
827826
batch_process = await self.create_batch()
828-
count = await self.count(kind=schema.kind, partial_match=partial_match, **filters)
827+
count = await self.count(kind=schema.kind, branch=branch, partial_match=partial_match, **filters)
829828
total_pages = (count + pagination_size - 1) // pagination_size
830829

831830
for page_number in range(1, total_pages + 1):
832831
page_offset = (page_number - 1) * pagination_size
833-
batch_process.add(task=process_page, node=node, page_offset=page_offset, page_number=page_number)
832+
batch_process.add(task=process_page, page_offset=page_offset, page_number=page_number)
834833

835834
async for _, response in batch_process.execute():
836835
nodes.extend(response[1]["nodes"])
@@ -847,7 +846,7 @@ async def process_non_batch() -> tuple[list[InfrahubNode], list[InfrahubNode]]:
847846

848847
while has_remaining_items:
849848
page_offset = (page_number - 1) * pagination_size
850-
response, process_result = await process_page(page_offset, page_number)
849+
response, process_result = await process_page(page_offset=page_offset, page_number=page_number)
851850

852851
nodes.extend(process_result["nodes"])
853852
related_nodes.extend(process_result["related_nodes"])
@@ -1946,9 +1945,9 @@ def filters(
19461945
"""
19471946
branch = branch or self.default_branch
19481947
schema = self.schema.get(kind=kind, branch=branch)
1949-
node = InfrahubNodeSync(client=self, schema=schema, branch=branch)
19501948
if at:
19511949
at = Timestamp(at)
1950+
19521951
filters = kwargs
19531952
pagination_size = self.pagination_size
19541953

@@ -1990,12 +1989,12 @@ def process_batch() -> tuple[list[InfrahubNodeSync], list[InfrahubNodeSync]]:
19901989
related_nodes = []
19911990
batch_process = self.create_batch()
19921991

1993-
count = self.count(kind=schema.kind, partial_match=partial_match, **filters)
1992+
count = self.count(kind=schema.kind, branch=branch, partial_match=partial_match, **filters)
19941993
total_pages = (count + pagination_size - 1) // pagination_size
19951994

19961995
for page_number in range(1, total_pages + 1):
19971996
page_offset = (page_number - 1) * pagination_size
1998-
batch_process.add(task=process_page, node=node, page_offset=page_offset, page_number=page_number)
1997+
batch_process.add(task=process_page, page_offset=page_offset, page_number=page_number)
19991998

20001999
for _, response in batch_process.execute():
20012000
nodes.extend(response[1]["nodes"])
@@ -2012,7 +2011,7 @@ def process_non_batch() -> tuple[list[InfrahubNodeSync], list[InfrahubNodeSync]]
20122011

20132012
while has_remaining_items:
20142013
page_offset = (page_number - 1) * pagination_size
2015-
response, process_result = process_page(page_offset, page_number)
2014+
response, process_result = process_page(page_offset=page_offset, page_number=page_number)
20162015

20172016
nodes.extend(process_result["nodes"])
20182017
related_nodes.extend(process_result["related_nodes"])

infrahub_sdk/ctl/repository.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
from __future__ import annotations
22

3+
import asyncio
34
from pathlib import Path
45
from typing import Optional
56

67
import typer
78
import yaml
9+
from copier import run_copy
810
from pydantic import ValidationError
911
from rich.console import Console
1012
from rich.table import Table
@@ -165,3 +167,52 @@ async def list(
165167
)
166168

167169
console.print(table)
170+
171+
172+
@app.command()
173+
async def init(
174+
directory: Path = typer.Argument(help="Directory path for the new project."),
175+
template: str = typer.Option(
176+
default="https://github.com/opsmill/infrahub-template.git",
177+
help="Template to use for the new repository. Can be a local path or a git repository URL.",
178+
),
179+
data: Optional[Path] = typer.Option(default=None, help="Path to YAML file containing answers to CLI prompt."),
180+
vcs_ref: Optional[str] = typer.Option(
181+
default="HEAD",
182+
help="VCS reference to use for the template. Defaults to HEAD.",
183+
),
184+
trust: Optional[bool] = typer.Option(
185+
default=False,
186+
help="Trust the template repository. If set, the template will be cloned without verification.",
187+
),
188+
_: str = CONFIG_PARAM,
189+
) -> None:
190+
"""Initialize a new Infrahub repository."""
191+
192+
config_data = None
193+
if data:
194+
try:
195+
with Path.open(data, encoding="utf-8") as file:
196+
config_data = yaml.safe_load(file)
197+
typer.echo(f"Loaded config: {config_data}")
198+
except Exception as exc:
199+
typer.echo(f"Error loading YAML file: {exc}", err=True)
200+
raise typer.Exit(code=1)
201+
202+
# Allow template to be a local path or a URL
203+
template_source = template or ""
204+
if template and Path(template).exists():
205+
template_source = str(Path(template).resolve())
206+
207+
try:
208+
await asyncio.to_thread(
209+
run_copy,
210+
template_source,
211+
str(directory),
212+
data=config_data,
213+
vcs_ref=vcs_ref,
214+
unsafe=trust,
215+
)
216+
except Exception as e:
217+
typer.echo(f"Error running copier: {e}", err=True)
218+
raise typer.Exit(code=1)

infrahub_sdk/ctl/schema.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def validate_schema_content_and_exit(client: InfrahubClient, schemas: list[Schem
3636
has_error: bool = False
3737
for schema_file in schemas:
3838
try:
39-
client.schema.validate(data=schema_file.content)
39+
client.schema.validate(data=schema_file.payload)
4040
except ValidationError as exc:
4141
console.print(f"[red]Schema not valid, found '{len(exc.errors())}' error(s) in {schema_file.location}")
4242
has_error = True
@@ -48,7 +48,7 @@ def validate_schema_content_and_exit(client: InfrahubClient, schemas: list[Schem
4848
raise typer.Exit(1)
4949

5050

51-
def display_schema_load_errors(response: dict[str, Any], schemas_data: list[dict]) -> None:
51+
def display_schema_load_errors(response: dict[str, Any], schemas_data: list[SchemaFile]) -> None:
5252
console.print("[red]Unable to load the schema:")
5353
if "detail" not in response:
5454
handle_non_detail_errors(response=response)
@@ -87,7 +87,7 @@ def handle_non_detail_errors(response: dict[str, Any]) -> None:
8787
if "error" in response:
8888
console.print(f" {response.get('error')}")
8989
elif "errors" in response:
90-
for error in response.get("errors"):
90+
for error in response["errors"]:
9191
console.print(f" {error.get('message')}")
9292
else:
9393
console.print(f" '{response}'")
@@ -97,9 +97,9 @@ def valid_error_path(loc_path: list[Any]) -> bool:
9797
return len(loc_path) >= 6 and loc_path[0] == "body" and loc_path[1] == "schemas"
9898

9999

100-
def get_node(schemas_data: list[dict], schema_index: int, node_index: int) -> dict | None:
101-
if schema_index < len(schemas_data) and node_index < len(schemas_data[schema_index].content["nodes"]):
102-
return schemas_data[schema_index].content["nodes"][node_index]
100+
def get_node(schemas_data: list[SchemaFile], schema_index: int, node_index: int) -> dict | None:
101+
if schema_index < len(schemas_data) and node_index < len(schemas_data[schema_index].payload["nodes"]):
102+
return schemas_data[schema_index].payload["nodes"][node_index]
103103
return None
104104

105105

@@ -122,7 +122,7 @@ async def load(
122122
validate_schema_content_and_exit(client=client, schemas=schemas_data)
123123

124124
start_time = time.time()
125-
response = await client.schema.load(schemas=[item.content for item in schemas_data], branch=branch)
125+
response = await client.schema.load(schemas=[item.payload for item in schemas_data], branch=branch)
126126
loading_time = time.time() - start_time
127127

128128
if response.errors:
@@ -170,10 +170,10 @@ async def check(
170170
client = initialize_client()
171171
validate_schema_content_and_exit(client=client, schemas=schemas_data)
172172

173-
success, response = await client.schema.check(schemas=[item.content for item in schemas_data], branch=branch)
173+
success, response = await client.schema.check(schemas=[item.payload for item in schemas_data], branch=branch)
174174

175175
if not success:
176-
display_schema_load_errors(response=response, schemas_data=schemas_data)
176+
display_schema_load_errors(response=response or {}, schemas_data=schemas_data)
177177
else:
178178
for schema_file in schemas_data:
179179
console.print(f"[green] schema '{schema_file.location}' is Valid!")

0 commit comments

Comments
 (0)