Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .importlinter
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ type = layers
containers =
usethis._ui.interface
layers =
author | badge | browse | ci | doc | docstyle | format_ | init | lint | list | readme | rule | show | spellcheck | status | test | tool | typecheck | version
arch | author | badge | browse | ci | doc | docstyle | format_ | init | lint | list | readme | rule | show | spellcheck | status | test | tool | typecheck | version
exhaustive = true

[importlinter:contract:pipeweld]
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ Additionally, the command line reference documentation can be viewed with `useth

### Manage Tooling

- [`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/)).
- [`usethis doc`](https://usethis.readthedocs.io/en/stable/cli/reference#usethis-doc) — Add/Configure recommended documentation tools (namely, [MkDocs](https://www.mkdocs.org/)).
- [`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/)).
- [`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)).
Expand Down
1 change: 1 addition & 0 deletions docs/cli/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

## Manage Tooling

- [`usethis arch`](reference.md#usethis-arch) — Add/Configure recommended architecture analysis tools (namely, [Import Linter](https://import-linter.readthedocs.io/en/stable/)).
- [`usethis doc`](reference.md#usethis-doc) — Add/Configure recommended documentation tools (namely, [MkDocs](https://www.mkdocs.org/)).
- [`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/)).
- [`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)).
Expand Down
27 changes: 27 additions & 0 deletions docs/cli/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Initialize a new Python project with recommended defaults, including:

Supported options:

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

## `usethis arch`

Add recommended architecture analysis tools to the project (namely, [Import Linter](https://import-linter.readthedocs.io/en/stable/)), including:

- declared & installed dependencies via `uv add`,
- relevant configuration, and
- any other relevant directories or tool-bespoke configuration files.

Note if `pyproject.toml` is not present, it will be created, since this is required for declaring dependencies via `uv add`.

Supported options:

- `--remove` to remove the tool instead of adding it
- `--how` to only print how to use the tool, with no other side effects
- `--offline` to disable network access and rely on caches
- `--frozen` to leave the virtual environment and lockfile unchanged
- `--quiet` to suppress output
- `--backend` to specify a package manager backend to use. The default is to auto-detect.

Possible values:
- `auto` to auto-detect the backend (default)
- `uv` to use the [uv](https://docs.astral.sh/uv) package manager
- `none` to not use a package manager backend and display messages for some operations.

See [`usethis tool`](#usethis-tool) for more information.

## `usethis doc`

Add recommended documentation tools to the project (namely, [MkDocs](https://www.mkdocs.org/)), including:
Expand Down
5 changes: 5 additions & 0 deletions src/usethis/_toolset/arch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from usethis._core.tool import use_import_linter


def use_arch_tools(remove: bool = False, how: bool = False):
use_import_linter(remove=remove, how=how)
7 changes: 7 additions & 0 deletions src/usethis/_ui/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import typer

import usethis._ui.interface.arch
import usethis._ui.interface.author
import usethis._ui.interface.badge
import usethis._ui.interface.browse
Expand Down Expand Up @@ -40,6 +41,12 @@
)

rich_help_panel = "Manage Tooling"
app.command(
help="Add or configure recommended architecture analysis tools.",
rich_help_panel=rich_help_panel,
)(
usethis._ui.interface.arch.arch,
)
app.command(
help="Add or configure recommended documentation tools.",
rich_help_panel=rich_help_panel,
Expand Down
41 changes: 41 additions & 0 deletions src/usethis/_ui/interface/arch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import typer

from usethis._config import usethis_config
from usethis._types.backend import BackendEnum
from usethis._ui.options import (
backend_opt,
frozen_opt,
how_opt,
offline_opt,
quiet_opt,
remove_opt,
)


def arch(
remove: bool = remove_opt,
how: bool = how_opt,
offline: bool = offline_opt,
quiet: bool = quiet_opt,
frozen: bool = frozen_opt,
backend: BackendEnum = backend_opt,
) -> None:
"""Add recommended architecture analysis tools to the project."""
from usethis._config_file import files_manager
from usethis._console import err_print
from usethis._toolset.arch import use_arch_tools
from usethis.errors import UsethisError

assert isinstance(backend, BackendEnum)

with (
usethis_config.set(
offline=offline, quiet=quiet, frozen=frozen, backend=backend
),
files_manager(),
):
try:
use_arch_tools(remove=remove, how=how)
except UsethisError as err:
err_print(err)
raise typer.Exit(code=1) from None
11 changes: 11 additions & 0 deletions src/usethis/_ui/interface/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@


def init(
arch: bool = typer.Option(
False, "--arch/--no-arch", help="Add recommended architecture analysis tools."
),
doc: bool = typer.Option(
True, "--doc/--no-doc", help="Add a recommended documentation framework."
),
Expand Down Expand Up @@ -93,6 +96,7 @@ def init(
):
try:
_init(
arch=arch,
doc=doc,
format_=format_,
lint=lint,
Expand All @@ -111,6 +115,7 @@ def init(

def _init( # noqa: PLR0915
*,
arch: bool,
doc: bool,
format_: bool,
lint: bool,
Expand All @@ -129,6 +134,7 @@ def _init( # noqa: PLR0915
from usethis._core.status import use_development_status
from usethis._core.tool import use_pre_commit
from usethis._init import project_init
from usethis._toolset.arch import use_arch_tools
from usethis._toolset.doc import use_doc_frameworks
from usethis._toolset.format_ import use_formatters
from usethis._toolset.lint import use_linters
Expand Down Expand Up @@ -183,6 +189,11 @@ def _init( # noqa: PLR0915
with usethis_config.set(instruct_only=True):
use_typecheckers()
use_typecheckers(how=True)
if arch:
tick_print("Adding recommended architecture analysis tools.")
with usethis_config.set(instruct_only=True):
use_arch_tools()
use_arch_tools(how=True)

if ci is not None:
assert isinstance(ci, CIServiceEnum)
Expand Down
63 changes: 63 additions & 0 deletions tests/usethis/_ui/interface/test_arch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from pathlib import Path

from usethis._test import CliRunner, change_cwd
from usethis._ui.app import app


class TestArch:
def test_how_option(self, tmp_path: Path):
# Act
runner = CliRunner()
with change_cwd(tmp_path):
result = runner.invoke_safe(app, ["arch", "--how"])

# Assert
assert result.exit_code == 0, result.output
assert "lint-imports" in result.output

def test_none_backend_pyproject_toml(self, tmp_path: Path):
# Arrange
(tmp_path / "pyproject.toml").write_text(
'[project]\nname = "myproject"\n\n[tool.usethis]\n'
)
(tmp_path / "src" / "myproject").mkdir(parents=True)
(tmp_path / "src" / "myproject" / "__init__.py").touch()
(tmp_path / "src" / "myproject" / "a.py").touch()
(tmp_path / "src" / "myproject" / "b.py").touch()
(tmp_path / "src" / "myproject" / "c.py").touch()

# Act
runner = CliRunner()
with change_cwd(tmp_path):
result = runner.invoke_safe(app, ["arch", "--backend", "none"])

# Assert
assert result.exit_code == 0, result.output
assert "☐ Add the dev dependency 'import-linter'." in result.output
assert "Adding Import Linter config to 'pyproject.toml'." in result.output
assert "lint-imports" in result.output

def test_add_then_remove(self, tmp_path: Path):
# Arrange
(tmp_path / "pyproject.toml").write_text(
'[project]\nname = "myproject"\n\n[tool.usethis]\n'
)
(tmp_path / "src" / "myproject").mkdir(parents=True)
(tmp_path / "src" / "myproject" / "__init__.py").touch()
(tmp_path / "src" / "myproject" / "a.py").touch()
(tmp_path / "src" / "myproject" / "b.py").touch()
(tmp_path / "src" / "myproject" / "c.py").touch()

runner = CliRunner()

with change_cwd(tmp_path):
# Act: Add arch
result = runner.invoke_safe(app, ["arch", "--backend", "none"])
assert result.exit_code == 0, result.output

# Act: Remove arch
result = runner.invoke_safe(app, ["arch", "--remove", "--backend", "none"])

# Assert
assert result.exit_code == 0, result.output
assert "Removing" in result.output
36 changes: 36 additions & 0 deletions tests/usethis/_ui/interface/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,42 @@ def test_bitbucket_docstyle_and_status(self, tmp_path: Path):
"☐ Run your pipeline via the Bitbucket website.\n"
)

def test_arch_included(self, tmp_path: Path):
# Act
runner = CliRunner()
with change_cwd(tmp_path):
result = runner.invoke_safe(app, ["init", "--arch"])

# Assert
assert result.exit_code == 0, result.output
assert (tmp_path / "pyproject.toml").exists()
assert result.output == (
"✔ Writing 'pyproject.toml' and initializing project.\n"
"✔ Writing 'README.md'.\n"
"☐ Populate 'README.md' to help users understand the project.\n"
"✔ Setting the development status to '1 - Planning'.\n"
"✔ Adding recommended documentation tools.\n"
"☐ Run 'uv run mkdocs build' to build the documentation.\n"
"☐ Run 'uv run mkdocs serve' to serve the documentation locally.\n"
"✔ Adding recommended linters.\n"
"☐ Run 'uv run deptry src' to run deptry.\n"
"☐ Run 'uv run ruff check --fix' to run the Ruff linter with autofixes.\n"
"✔ Adding recommended formatters.\n"
"☐ Run 'uv run ruff format' to run the Ruff formatter.\n"
"☐ Run 'uv run pyproject-fmt pyproject.toml' to run pyproject-fmt.\n"
"✔ Adding recommended spellcheckers.\n"
"☐ Run 'uv run codespell' to run the Codespell spellchecker.\n"
"✔ Adding recommended test frameworks.\n"
"☐ Add test files to the '/tests' directory with the format 'test_*.py'.\n"
"☐ Add test functions with the format 'test_*()'.\n"
"☐ Run 'uv run pytest' to run the tests.\n"
"☐ Run 'uv run pytest --cov' to run your tests with Coverage.py.\n"
"✔ Adding recommended type checkers.\n"
"☐ Run 'uv run ty check' to run the ty type checker.\n"
"✔ Adding recommended architecture analysis tools.\n"
"☐ Run 'uv run lint-imports' to run Import Linter.\n"
)

def test_none_backend(self, tmp_path: Path):
# Act
runner = CliRunner()
Expand Down
Loading