Skip to content

Commit 729e8ef

Browse files
authored
Move object commands into own package (#935)
The CRUD command files (create, delete, get, update) and their shared utilities lived in a generic "commands" directory that didn't convey what they were for. Since they all belong to the "infrahubctl object" subgroup, it makes sense to colocate them as an "object" package. Test files follow the same restructuring.
1 parent 91d7c77 commit 729e8ef

16 files changed

Lines changed: 98 additions & 88 deletions

File tree

dev/knowledge/cli-architecture.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,23 @@ For a **root command**, define the function in the appropriate module and regist
2121
app.command(name="mycommand")(my_function)
2222
```
2323

24-
For a **subcommand**, add it to the relevant group's module. For example, object subcommands go in `infrahub_sdk/ctl/object.py` or in dedicated files under `infrahub_sdk/ctl/commands/` and are registered on the object app.
24+
For a **subcommand**, add it to the relevant group's package or module. For example, object subcommands live in `infrahub_sdk/ctl/object/` and are registered on the object app in `__init__.py`.
2525

26-
## The `commands/` directory
26+
## Group packages
2727

28-
`infrahub_sdk/ctl/commands/` contains modular command implementations that are imported and registered on a group app. This keeps individual command logic separated from the group wiring. Shared utilities live in `commands/utils.py`.
28+
When a subcommand group has multiple commands, it lives as a package (directory with `__init__.py`) rather than a single module file. The `object` group is the reference example:
29+
30+
```text
31+
infrahub_sdk/ctl/object/
32+
├── __init__.py # App, callback, load/validate commands, registers CRUD
33+
├── create.py # create subcommand
34+
├── delete.py # delete subcommand
35+
├── get.py # get subcommand
36+
├── update.py # update subcommand
37+
└── utils.py # Shared utilities (resolve_node, etc.)
38+
```
39+
40+
Each command file contains a single command function. Shared logic goes in `utils.py`. The `__init__.py` wires everything together by importing and registering commands on the group's `AsyncTyper` app. Other groups that grow beyond a single file should follow this same pattern.
2941

3042
## Decorators
3143

infrahub_sdk/ctl/commands/__init__.py

Lines changed: 0 additions & 3 deletions
This file was deleted.
Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,22 @@
44
import typer
55
from rich.console import Console
66

7-
from ..async_typer import AsyncTyper
8-
from ..ctl.client import initialize_client
9-
from ..ctl.utils import catch_exception, init_logging
10-
from ..exceptions import ObjectValidationError, ValidationError
11-
from ..spec.object import ObjectFile
12-
from .commands.create import create_command
13-
from .commands.delete import delete_command
14-
from .commands.get import get_command
15-
from .commands.update import update_command
16-
from .parameters import CONFIG_PARAM
17-
from .utils import (
7+
from infrahub_sdk.async_typer import AsyncTyper
8+
from infrahub_sdk.ctl.client import initialize_client
9+
from infrahub_sdk.ctl.object.create import create_command
10+
from infrahub_sdk.ctl.object.delete import delete_command
11+
from infrahub_sdk.ctl.object.get import get_command
12+
from infrahub_sdk.ctl.object.update import update_command
13+
from infrahub_sdk.ctl.parameters import CONFIG_PARAM
14+
from infrahub_sdk.ctl.utils import (
15+
catch_exception,
1816
display_object_validate_format_error,
1917
display_object_validate_format_success,
18+
init_logging,
2019
load_yamlfile_from_disk_and_exit,
2120
)
21+
from infrahub_sdk.exceptions import ObjectValidationError, ValidationError
22+
from infrahub_sdk.spec.object import ObjectFile
2223

2324
app = AsyncTyper()
2425
console = Console()
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from rich.console import Console
1414

1515
from infrahub_sdk.ctl.client import initialize_client
16-
from infrahub_sdk.ctl.commands.utils import derive_identifier, prepare_relationship_data, resolve_node
16+
from infrahub_sdk.ctl.object.utils import derive_identifier, prepare_relationship_data, resolve_node
1717
from infrahub_sdk.ctl.parameters import CONFIG_PARAM
1818
from infrahub_sdk.ctl.parsers import parse_set_args, validate_set_fields
1919
from infrahub_sdk.ctl.utils import catch_exception
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from rich.console import Console
1010

1111
from infrahub_sdk.ctl.client import initialize_client
12-
from infrahub_sdk.ctl.commands.utils import resolve_node
12+
from infrahub_sdk.ctl.object.utils import resolve_node
1313
from infrahub_sdk.ctl.parameters import CONFIG_PARAM
1414
from infrahub_sdk.ctl.utils import catch_exception
1515

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
from rich.console import Console
1515

1616
from infrahub_sdk.ctl.client import initialize_client
17-
from infrahub_sdk.ctl.commands.utils import resolve_node
1817
from infrahub_sdk.ctl.formatters import OutputFormat, detect_output_format, get_formatter
18+
from infrahub_sdk.ctl.object.utils import resolve_node
1919
from infrahub_sdk.ctl.parameters import CONFIG_PARAM
2020
from infrahub_sdk.ctl.parsers import parse_filter_args
2121
from infrahub_sdk.ctl.utils import catch_exception
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from rich.console import Console
1414

1515
from infrahub_sdk.ctl.client import initialize_client
16-
from infrahub_sdk.ctl.commands.utils import prepare_relationship_data, resolve_node
16+
from infrahub_sdk.ctl.object.utils import prepare_relationship_data, resolve_node
1717
from infrahub_sdk.ctl.parameters import CONFIG_PARAM
1818
from infrahub_sdk.ctl.parsers import parse_set_args, validate_set_fields
1919
from infrahub_sdk.ctl.utils import catch_exception

tests/unit/ctl/commands/test_object_create.py renamed to tests/unit/ctl/object/test_create.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ def test_create_with_set_args() -> None:
4949
mock_client.create = AsyncMock(return_value=mock_node)
5050

5151
with (
52-
patch("infrahub_sdk.ctl.commands.create.initialize_client", return_value=mock_client),
52+
patch("infrahub_sdk.ctl.object.create.initialize_client", return_value=mock_client),
5353
patch(
54-
"infrahub_sdk.ctl.commands.create.resolve_node",
54+
"infrahub_sdk.ctl.object.create.resolve_node",
5555
new_callable=AsyncMock,
5656
side_effect=NodeNotFoundError(identifier={"name": ["router1"]}),
5757
),
@@ -82,9 +82,9 @@ def test_create_with_set_args_and_branch() -> None:
8282
mock_client.create = AsyncMock(return_value=mock_node)
8383

8484
with (
85-
patch("infrahub_sdk.ctl.commands.create.initialize_client", return_value=mock_client),
85+
patch("infrahub_sdk.ctl.object.create.initialize_client", return_value=mock_client),
8686
patch(
87-
"infrahub_sdk.ctl.commands.create.resolve_node",
87+
"infrahub_sdk.ctl.object.create.resolve_node",
8888
new_callable=AsyncMock,
8989
side_effect=NodeNotFoundError(identifier={"name": ["router2"]}),
9090
),
@@ -110,9 +110,9 @@ def test_create_with_file() -> None:
110110
mock_client = MagicMock()
111111

112112
with (
113-
patch("infrahub_sdk.ctl.commands.create.initialize_client", return_value=mock_client),
113+
patch("infrahub_sdk.ctl.object.create.initialize_client", return_value=mock_client),
114114
patch(
115-
"infrahub_sdk.ctl.commands.create.ObjectFile.load_from_disk",
115+
"infrahub_sdk.ctl.object.create.ObjectFile.load_from_disk",
116116
return_value=[mock_file],
117117
),
118118
):
@@ -143,9 +143,9 @@ def make_obj_file(kind: str, count: int) -> MagicMock:
143143
mock_client = MagicMock()
144144

145145
with (
146-
patch("infrahub_sdk.ctl.commands.create.initialize_client", return_value=mock_client),
146+
patch("infrahub_sdk.ctl.object.create.initialize_client", return_value=mock_client),
147147
patch(
148-
"infrahub_sdk.ctl.commands.create.ObjectFile.load_from_disk",
148+
"infrahub_sdk.ctl.object.create.ObjectFile.load_from_disk",
149149
return_value=[file_a, file_b],
150150
),
151151
):
@@ -169,9 +169,9 @@ def test_create_with_file_kind_mismatch() -> None:
169169
mock_client = MagicMock()
170170

171171
with (
172-
patch("infrahub_sdk.ctl.commands.create.initialize_client", return_value=mock_client),
172+
patch("infrahub_sdk.ctl.object.create.initialize_client", return_value=mock_client),
173173
patch(
174-
"infrahub_sdk.ctl.commands.create.ObjectFile.load_from_disk",
174+
"infrahub_sdk.ctl.object.create.ObjectFile.load_from_disk",
175175
return_value=[mock_file],
176176
),
177177
):
@@ -191,7 +191,7 @@ def test_create_invalid_field() -> None:
191191
mock_client.schema = MagicMock()
192192
mock_client.schema.get = AsyncMock(return_value=mock_schema)
193193

194-
with patch("infrahub_sdk.ctl.commands.create.initialize_client", return_value=mock_client):
194+
with patch("infrahub_sdk.ctl.object.create.initialize_client", return_value=mock_client):
195195
result = runner.invoke(app, ["object", "create", "InfraDevice", "--set", "nonexistent_field=value"])
196196

197197
assert result.exit_code != 0
@@ -214,9 +214,9 @@ def test_create_multiple_set_args() -> None:
214214
mock_client.create = AsyncMock(return_value=mock_node)
215215

216216
with (
217-
patch("infrahub_sdk.ctl.commands.create.initialize_client", return_value=mock_client),
217+
patch("infrahub_sdk.ctl.object.create.initialize_client", return_value=mock_client),
218218
patch(
219-
"infrahub_sdk.ctl.commands.create.resolve_node",
219+
"infrahub_sdk.ctl.object.create.resolve_node",
220220
new_callable=AsyncMock,
221221
side_effect=NodeNotFoundError(identifier={"name": ["router3"]}),
222222
),
@@ -236,7 +236,7 @@ def test_create_malformed_set_arg(bad_arg: str) -> None:
236236
"""Malformed --set arguments (no ``=`` or empty key) exit with a non-zero code."""
237237
mock_client = MagicMock()
238238

239-
with patch("infrahub_sdk.ctl.commands.create.initialize_client", return_value=mock_client):
239+
with patch("infrahub_sdk.ctl.object.create.initialize_client", return_value=mock_client):
240240
result = runner.invoke(app, ["object", "create", "InfraDevice", "--set", bad_arg])
241241

242242
assert result.exit_code != 0

0 commit comments

Comments
 (0)