Skip to content

Commit 93eebf1

Browse files
Implement usethis arch command (#1451)
* Initial plan * Implement usethis arch command for architecture tools (Import Linter) Co-authored-by: nathanjmcdougall <18602289+nathanjmcdougall@users.noreply.github.com> Agent-Logs-Url: https://github.com/usethis-python/usethis-python/sessions/92db8a8e-3c40-4dd8-b880-e8edb1ba6739 * Add arch module to .importlinter ui_interface contract Co-authored-by: nathanjmcdougall <18602289+nathanjmcdougall@users.noreply.github.com> Agent-Logs-Url: https://github.com/usethis-python/usethis-python/sessions/92db8a8e-3c40-4dd8-b880-e8edb1ba6739 * Replace "architecture tools" with "architecture analysis tools" everywhere Co-authored-by: nathanjmcdougall <18602289+nathanjmcdougall@users.noreply.github.com> Agent-Logs-Url: https://github.com/usethis-python/usethis-python/sessions/de1c1413-5ad8-48d8-ad0a-c891ad648a87 * Add usethis arch entry to docs/cli/overview.md to match README Co-authored-by: nathanjmcdougall <18602289+nathanjmcdougall@users.noreply.github.com> Agent-Logs-Url: https://github.com/usethis-python/usethis-python/sessions/91cebf61-4055-4b9a-bcfe-bb20736ea740 * Add test for usethis init --arch Co-authored-by: nathanjmcdougall <18602289+nathanjmcdougall@users.noreply.github.com> Agent-Logs-Url: https://github.com/usethis-python/usethis-python/sessions/10727609-7afc-4268-91ea-8bdc4e5e7c52 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: nathanjmcdougall <18602289+nathanjmcdougall@users.noreply.github.com> Co-authored-by: Nathan McDougall <nathan.j.mcdougall@gmail.com>
1 parent b8281ae commit 93eebf1

10 files changed

Lines changed: 193 additions & 1 deletion

File tree

.importlinter

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ type = layers
130130
containers =
131131
usethis._ui.interface
132132
layers =
133-
author | badge | browse | ci | doc | docstyle | format_ | init | lint | list | readme | rule | show | spellcheck | status | test | tool | typecheck | version
133+
arch | author | badge | browse | ci | doc | docstyle | format_ | init | lint | list | readme | rule | show | spellcheck | status | test | tool | typecheck | version
134134
exhaustive = true
135135

136136
[importlinter:contract:pipeweld]

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ Additionally, the command line reference documentation can be viewed with `useth
7070

7171
### Manage Tooling
7272

73+
- [`usethis arch`](https://usethis.readthedocs.io/en/stable/cli/reference#usethis-arch) — Add/Configure recommended architecture analysis tools (namely, [Import Linter](https://import-linter.readthedocs.io/en/stable/)).
7374
- [`usethis doc`](https://usethis.readthedocs.io/en/stable/cli/reference#usethis-doc) — Add/Configure recommended documentation tools (namely, [MkDocs](https://www.mkdocs.org/)).
7475
- [`usethis format`](https://usethis.readthedocs.io/en/stable/cli/reference#usethis-format) — Add/Configure recommended formatters (namely, [Ruff](https://docs.astral.sh/ruff/formatter/) and [pyproject-fmt](https://pyproject-fmt.readthedocs.io/en/latest/)).
7576
- [`usethis lint`](https://usethis.readthedocs.io/en/stable/cli/reference#usethis-lint) — Add/Configure recommended linters (namely, [Ruff](https://docs.astral.sh/ruff/linter) and [deptry](https://github.com/fpgmaas/deptry)).

docs/cli/overview.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
## Manage Tooling
88

9+
- [`usethis arch`](reference.md#usethis-arch) — Add/Configure recommended architecture analysis tools (namely, [Import Linter](https://import-linter.readthedocs.io/en/stable/)).
910
- [`usethis doc`](reference.md#usethis-doc) — Add/Configure recommended documentation tools (namely, [MkDocs](https://www.mkdocs.org/)).
1011
- [`usethis format`](reference.md#usethis-format) — Add/Configure recommended formatters (namely, [Ruff](https://docs.astral.sh/ruff/formatter/) and [pyproject-fmt](https://pyproject-fmt.readthedocs.io/en/latest/)).
1112
- [`usethis lint`](reference.md#usethis-lint) — Add/Configure recommended linters (namely, [Ruff](https://docs.astral.sh/ruff/linter) and [deptry](https://github.com/fpgmaas/deptry)).

docs/cli/reference.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Initialize a new Python project with recommended defaults, including:
1414

1515
Supported options:
1616

17+
- `--arch` to add recommended architecture analysis tools (but the default is `--no-arch`)
1718
- `--doc` to add recommended documentation tools (default; or `--no-doc` to opt-out)
1819
- `--format` to add recommended formatters (default; or `--no-format` to opt-out)
1920
- `--lint` to add recommended linters (default; or `--no-lint` to opt-out)
@@ -51,6 +52,32 @@ Supported options:
5152
- `uv` to use the [uv](https://docs.astral.sh/uv) package manager
5253
- `none` to not use a package manager backend and display messages for some operations.
5354

55+
## `usethis arch`
56+
57+
Add recommended architecture analysis tools to the project (namely, [Import Linter](https://import-linter.readthedocs.io/en/stable/)), including:
58+
59+
- declared & installed dependencies via `uv add`,
60+
- relevant configuration, and
61+
- any other relevant directories or tool-bespoke configuration files.
62+
63+
Note if `pyproject.toml` is not present, it will be created, since this is required for declaring dependencies via `uv add`.
64+
65+
Supported options:
66+
67+
- `--remove` to remove the tool instead of adding it
68+
- `--how` to only print how to use the tool, with no other side effects
69+
- `--offline` to disable network access and rely on caches
70+
- `--frozen` to leave the virtual environment and lockfile unchanged
71+
- `--quiet` to suppress output
72+
- `--backend` to specify a package manager backend to use. The default is to auto-detect.
73+
74+
Possible values:
75+
- `auto` to auto-detect the backend (default)
76+
- `uv` to use the [uv](https://docs.astral.sh/uv) package manager
77+
- `none` to not use a package manager backend and display messages for some operations.
78+
79+
See [`usethis tool`](#usethis-tool) for more information.
80+
5481
## `usethis doc`
5582

5683
Add recommended documentation tools to the project (namely, [MkDocs](https://www.mkdocs.org/)), including:

src/usethis/_toolset/arch.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from usethis._core.tool import use_import_linter
2+
3+
4+
def use_arch_tools(remove: bool = False, how: bool = False):
5+
use_import_linter(remove=remove, how=how)

src/usethis/_ui/app.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import typer
44

5+
import usethis._ui.interface.arch
56
import usethis._ui.interface.author
67
import usethis._ui.interface.badge
78
import usethis._ui.interface.browse
@@ -40,6 +41,12 @@
4041
)
4142

4243
rich_help_panel = "Manage Tooling"
44+
app.command(
45+
help="Add or configure recommended architecture analysis tools.",
46+
rich_help_panel=rich_help_panel,
47+
)(
48+
usethis._ui.interface.arch.arch,
49+
)
4350
app.command(
4451
help="Add or configure recommended documentation tools.",
4552
rich_help_panel=rich_help_panel,

src/usethis/_ui/interface/arch.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import typer
2+
3+
from usethis._config import usethis_config
4+
from usethis._types.backend import BackendEnum
5+
from usethis._ui.options import (
6+
backend_opt,
7+
frozen_opt,
8+
how_opt,
9+
offline_opt,
10+
quiet_opt,
11+
remove_opt,
12+
)
13+
14+
15+
def arch(
16+
remove: bool = remove_opt,
17+
how: bool = how_opt,
18+
offline: bool = offline_opt,
19+
quiet: bool = quiet_opt,
20+
frozen: bool = frozen_opt,
21+
backend: BackendEnum = backend_opt,
22+
) -> None:
23+
"""Add recommended architecture analysis tools to the project."""
24+
from usethis._config_file import files_manager
25+
from usethis._console import err_print
26+
from usethis._toolset.arch import use_arch_tools
27+
from usethis.errors import UsethisError
28+
29+
assert isinstance(backend, BackendEnum)
30+
31+
with (
32+
usethis_config.set(
33+
offline=offline, quiet=quiet, frozen=frozen, backend=backend
34+
),
35+
files_manager(),
36+
):
37+
try:
38+
use_arch_tools(remove=remove, how=how)
39+
except UsethisError as err:
40+
err_print(err)
41+
raise typer.Exit(code=1) from None

src/usethis/_ui/interface/init.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414

1515

1616
def init(
17+
arch: bool = typer.Option(
18+
False, "--arch/--no-arch", help="Add recommended architecture analysis tools."
19+
),
1720
doc: bool = typer.Option(
1821
True, "--doc/--no-doc", help="Add a recommended documentation framework."
1922
),
@@ -93,6 +96,7 @@ def init(
9396
):
9497
try:
9598
_init(
99+
arch=arch,
96100
doc=doc,
97101
format_=format_,
98102
lint=lint,
@@ -111,6 +115,7 @@ def init(
111115

112116
def _init( # noqa: PLR0915
113117
*,
118+
arch: bool,
114119
doc: bool,
115120
format_: bool,
116121
lint: bool,
@@ -129,6 +134,7 @@ def _init( # noqa: PLR0915
129134
from usethis._core.status import use_development_status
130135
from usethis._core.tool import use_pre_commit
131136
from usethis._init import project_init
137+
from usethis._toolset.arch import use_arch_tools
132138
from usethis._toolset.doc import use_doc_frameworks
133139
from usethis._toolset.format_ import use_formatters
134140
from usethis._toolset.lint import use_linters
@@ -183,6 +189,11 @@ def _init( # noqa: PLR0915
183189
with usethis_config.set(instruct_only=True):
184190
use_typecheckers()
185191
use_typecheckers(how=True)
192+
if arch:
193+
tick_print("Adding recommended architecture analysis tools.")
194+
with usethis_config.set(instruct_only=True):
195+
use_arch_tools()
196+
use_arch_tools(how=True)
186197

187198
if ci is not None:
188199
assert isinstance(ci, CIServiceEnum)
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
from pathlib import Path
2+
3+
from usethis._test import CliRunner, change_cwd
4+
from usethis._ui.app import app
5+
6+
7+
class TestArch:
8+
def test_how_option(self, tmp_path: Path):
9+
# Act
10+
runner = CliRunner()
11+
with change_cwd(tmp_path):
12+
result = runner.invoke_safe(app, ["arch", "--how"])
13+
14+
# Assert
15+
assert result.exit_code == 0, result.output
16+
assert "lint-imports" in result.output
17+
18+
def test_none_backend_pyproject_toml(self, tmp_path: Path):
19+
# Arrange
20+
(tmp_path / "pyproject.toml").write_text(
21+
'[project]\nname = "myproject"\n\n[tool.usethis]\n'
22+
)
23+
(tmp_path / "src" / "myproject").mkdir(parents=True)
24+
(tmp_path / "src" / "myproject" / "__init__.py").touch()
25+
(tmp_path / "src" / "myproject" / "a.py").touch()
26+
(tmp_path / "src" / "myproject" / "b.py").touch()
27+
(tmp_path / "src" / "myproject" / "c.py").touch()
28+
29+
# Act
30+
runner = CliRunner()
31+
with change_cwd(tmp_path):
32+
result = runner.invoke_safe(app, ["arch", "--backend", "none"])
33+
34+
# Assert
35+
assert result.exit_code == 0, result.output
36+
assert "☐ Add the dev dependency 'import-linter'." in result.output
37+
assert "Adding Import Linter config to 'pyproject.toml'." in result.output
38+
assert "lint-imports" in result.output
39+
40+
def test_add_then_remove(self, tmp_path: Path):
41+
# Arrange
42+
(tmp_path / "pyproject.toml").write_text(
43+
'[project]\nname = "myproject"\n\n[tool.usethis]\n'
44+
)
45+
(tmp_path / "src" / "myproject").mkdir(parents=True)
46+
(tmp_path / "src" / "myproject" / "__init__.py").touch()
47+
(tmp_path / "src" / "myproject" / "a.py").touch()
48+
(tmp_path / "src" / "myproject" / "b.py").touch()
49+
(tmp_path / "src" / "myproject" / "c.py").touch()
50+
51+
runner = CliRunner()
52+
53+
with change_cwd(tmp_path):
54+
# Act: Add arch
55+
result = runner.invoke_safe(app, ["arch", "--backend", "none"])
56+
assert result.exit_code == 0, result.output
57+
58+
# Act: Remove arch
59+
result = runner.invoke_safe(app, ["arch", "--remove", "--backend", "none"])
60+
61+
# Assert
62+
assert result.exit_code == 0, result.output
63+
assert "Removing" in result.output

tests/usethis/_ui/interface/test_init.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,42 @@ def test_bitbucket_docstyle_and_status(self, tmp_path: Path):
139139
"☐ Run your pipeline via the Bitbucket website.\n"
140140
)
141141

142+
def test_arch_included(self, tmp_path: Path):
143+
# Act
144+
runner = CliRunner()
145+
with change_cwd(tmp_path):
146+
result = runner.invoke_safe(app, ["init", "--arch"])
147+
148+
# Assert
149+
assert result.exit_code == 0, result.output
150+
assert (tmp_path / "pyproject.toml").exists()
151+
assert result.output == (
152+
"✔ Writing 'pyproject.toml' and initializing project.\n"
153+
"✔ Writing 'README.md'.\n"
154+
"☐ Populate 'README.md' to help users understand the project.\n"
155+
"✔ Setting the development status to '1 - Planning'.\n"
156+
"✔ Adding recommended documentation tools.\n"
157+
"☐ Run 'uv run mkdocs build' to build the documentation.\n"
158+
"☐ Run 'uv run mkdocs serve' to serve the documentation locally.\n"
159+
"✔ Adding recommended linters.\n"
160+
"☐ Run 'uv run deptry src' to run deptry.\n"
161+
"☐ Run 'uv run ruff check --fix' to run the Ruff linter with autofixes.\n"
162+
"✔ Adding recommended formatters.\n"
163+
"☐ Run 'uv run ruff format' to run the Ruff formatter.\n"
164+
"☐ Run 'uv run pyproject-fmt pyproject.toml' to run pyproject-fmt.\n"
165+
"✔ Adding recommended spellcheckers.\n"
166+
"☐ Run 'uv run codespell' to run the Codespell spellchecker.\n"
167+
"✔ Adding recommended test frameworks.\n"
168+
"☐ Add test files to the '/tests' directory with the format 'test_*.py'.\n"
169+
"☐ Add test functions with the format 'test_*()'.\n"
170+
"☐ Run 'uv run pytest' to run the tests.\n"
171+
"☐ Run 'uv run pytest --cov' to run your tests with Coverage.py.\n"
172+
"✔ Adding recommended type checkers.\n"
173+
"☐ Run 'uv run ty check' to run the ty type checker.\n"
174+
"✔ Adding recommended architecture analysis tools.\n"
175+
"☐ Run 'uv run lint-imports' to run Import Linter.\n"
176+
)
177+
142178
def test_none_backend(self, tmp_path: Path):
143179
# Act
144180
runner = CliRunner()

0 commit comments

Comments
 (0)