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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ $ uvx usethis tool ruff
✔ Adding Ruff config to 'pyproject.toml'.
✔ Selecting Ruff rules 'A', 'C4', 'E4', 'E7', 'E9', 'F', 'FLY', 'FURB', 'I', 'PLE', 'PLR', 'RUF', 'SIM', 'UP' in 'pyproject.toml'.
✔ Ignoring Ruff rules 'PLR2004', 'SIM108' in 'pyproject.toml'.
✔ Running the Ruff formatter.
☐ Run 'uv run ruff check --fix' to run the Ruff linter with autofixes.
☐ Run 'uv run ruff format' to run the Ruff formatter.
```
Expand Down
9 changes: 6 additions & 3 deletions docs/start/detailed-example.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ The output from usethis is chatty. If you know what you're doing, you can suppre
$ uvx usethis tool ruff
✔ Adding dependency 'ruff' to the 'dev' group in 'pyproject.toml'.
✔ Adding Ruff config to 'pyproject.toml'.
✔ Selecting Ruff rules 'A', 'C4', 'E4', 'E7', 'E9', 'F', 'FLY', 'FURB', 'I', 'PLE', 'PLR', 'RUF', 'SIM'4, 'UP' in 'pyproject.toml'.
✔ Selecting Ruff rules 'A', 'C4', 'E4', 'E7', 'E9', 'F', 'FLY', 'FURB', 'I', 'PLE', 'PLR', 'RUF', 'SIM', 'UP' in 'pyproject.toml'.
✔ Ignoring Ruff rules 'PLR2004', 'SIM108' in 'pyproject.toml'.
✔ Running the Ruff formatter.
☐ Run 'uv run ruff check --fix' to run the Ruff linter with autofixes.
☐ Run 'uv run ruff format' to run the Ruff formatter.
```
Expand All @@ -22,9 +23,11 @@ Let's run through what each line of the output means:
This line indicates that a set of recommended Ruff rule sets has been selected and added to the `pyproject.toml` configuration. These rules determine what kinds of issues Ruff will check for in your code. The selected rules are based on best practices and are intended to help you maintain high code quality. Most of them have auto-fixes available. You can learn more about the specific rules in the [Ruff documentation](https://docs.astral.sh/ruff/rules).
4. `✔ Ignoring Ruff rules 'PLR2004', 'SIM108' in 'pyproject.toml'.`
This line indicates that certain Ruff rules have been explicitly ignored in the configuration. These rules were deemed less useful or potentially problematic for most projects, so usethis has chosen to disable them by default. You can always modify this list later if you find that you want to enable or disable additional rules.
5. `☐ Run 'uv run ruff check --fix' to run the Ruff linter with autofixes.`
5. `✔ Running the Ruff formatter.`
This line indicates that the Ruff formatter has been automatically applied your codebase.
6. `☐ Run 'uv run ruff check --fix' to run the Ruff linter with autofixes.`
This line is an instruction for you to run the Ruff linter on your codebase. It helps teach you how to use the tool which has just been installed and configured. You're ready to go and explore!
6. `☐ Run 'uv run ruff format' to run the Ruff formatter.`
7. `☐ Run 'uv run ruff format' to run the Ruff formatter.`
Ruff is also a code formatter. Similar to the previous line, this is an instruction for you to run the Ruff formatter on your codebase. This will help ensure that your code adheres to consistent formatting standards.

The key idea is that lines beginning with a check mark (✔) indicate actions that have been successfully completed by usethis, while lines beginning with an empty box (☐) are instructions for you to follow up on.
1 change: 1 addition & 0 deletions docs/start/example-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ $ uvx usethis tool ruff
✔ Adding Ruff config to 'pyproject.toml'.
✔ Selecting Ruff rules 'A', 'C4', 'E4', 'E7', 'E9', 'F', 'FLY', 'FURB', 'I', 'PLE', 'PLR', 'RUF', 'SIM', 'UP' in 'pyproject.toml'.
✔ Ignoring Ruff rules 'PLR2004', 'SIM108' in 'pyproject.toml'.
✔ Running the Ruff formatter.
☐ Run 'uv run ruff check --fix' to run the Ruff linter with autofixes.
☐ Run 'uv run ruff format' to run the Ruff formatter.
```
Expand Down
2 changes: 2 additions & 0 deletions src/usethis/_core/tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ def use_pyproject_fmt(*, remove: bool = False, how: bool = False) -> None:
tool.add_pre_commit_config()

tool.add_configs()
tool.apply()
tool.print_how_to_use()
else:
tool.remove_configs()
Expand Down Expand Up @@ -449,6 +450,7 @@ def use_ruff(
tool.apply_rule_config(rule_config)
tool.add_pre_commit_config()

tool.apply()
tool.print_how_to_use()
else:
tool = RuffTool(
Expand Down
7 changes: 7 additions & 0 deletions src/usethis/_tool/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,13 @@ def how_to_use_pre_commit_hook_id(self) -> str:

return hook_id

def apply(self) -> None:
"""Apply the tool's side effects to the project.

By default, this is a no-op. Tools that have side effects (e.g. formatters)
should override this method to invoke their command via subprocess.
"""

def is_used(self) -> bool:
"""Whether the tool is being used in the current project.

Expand Down
20 changes: 19 additions & 1 deletion src/usethis/_tool/impl/base/pyproject_fmt.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,30 @@

from __future__ import annotations

import contextlib
from typing import final

from typing_extensions import override

from usethis._backend.dispatch import get_backend
from usethis._backend.uv.call import call_uv_subprocess
from usethis._backend.uv.errors import UVSubprocessFailedError
from usethis._console import tick_print
from usethis._tool.base import Tool
from usethis._tool.impl.spec.pyproject_fmt import PyprojectFmtToolSpec
from usethis._types.backend import BackendEnum


@final
class PyprojectFmtTool(PyprojectFmtToolSpec, Tool):
pass
@override
def apply(self) -> None:
"""Run pyproject-fmt to format pyproject.toml."""
if get_backend() is not BackendEnum.uv:
return

tick_print("Running pyproject-fmt on 'pyproject.toml'.")
with contextlib.suppress(UVSubprocessFailedError):
call_uv_subprocess(
["run", "pyproject-fmt", "pyproject.toml"], change_toml=False
)
16 changes: 16 additions & 0 deletions src/usethis/_tool/impl/base/ruff.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@

from __future__ import annotations

import contextlib
from pathlib import Path
from typing import TYPE_CHECKING, Literal, final

from pydantic import TypeAdapter, ValidationError
from typing_extensions import assert_never, override

from usethis._backend.dispatch import get_backend
from usethis._backend.uv.call import call_uv_subprocess
from usethis._backend.uv.detect import is_uv_used
from usethis._backend.uv.errors import UVSubprocessFailedError
from usethis._config import usethis_config
from usethis._config_file import DotRuffTOMLManager, RuffTOMLManager
from usethis._console import how_print, tick_print
Expand Down Expand Up @@ -439,3 +442,16 @@ def is_no_subtool_config_present(self) -> bool:
not self.is_linter_config_present()
and not self.is_formatter_config_present()
)

@override
def apply(self) -> None:
"""Run Ruff formatter on the project."""
if get_backend() is not BackendEnum.uv:
return

if not self.is_formatter_used():
return

tick_print("Running the Ruff formatter.")
with contextlib.suppress(UVSubprocessFailedError):
call_uv_subprocess(["run", "ruff", "format"], change_toml=False)
7 changes: 6 additions & 1 deletion tests/usethis/_core/test_core_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -2276,6 +2276,7 @@ def test_added(self, uv_init_dir: Path, capfd: pytest.CaptureFixture[str]):
out, _ = capfd.readouterr()
assert out == (
"✔ Adding pyproject-fmt config to 'pyproject.toml'.\n"
"✔ Running pyproject-fmt on 'pyproject.toml'.\n"
"☐ Run 'uv run pyproject-fmt pyproject.toml' to run pyproject-fmt.\n"
)

Expand All @@ -2295,6 +2296,7 @@ def test_added(self, uv_init_dir: Path, capfd: pytest.CaptureFixture[str]):
"✔ Adding dependency 'pyproject-fmt' to the 'dev' group in 'pyproject.toml'.\n"
"☐ Install the dependency 'pyproject-fmt'.\n"
"✔ Adding pyproject-fmt config to 'pyproject.toml'.\n"
"✔ Running pyproject-fmt on 'pyproject.toml'.\n"
"☐ Run 'uv run pyproject-fmt pyproject.toml' to run pyproject-fmt.\n"
)

Expand Down Expand Up @@ -2328,6 +2330,7 @@ def test_pre_commit_integration(
"☐ Install the dependency 'pyproject-fmt'.\n"
"✔ Adding hook 'pyproject-fmt' to '.pre-commit-config.yaml'.\n"
"✔ Adding pyproject-fmt config to 'pyproject.toml'.\n"
"✔ Running pyproject-fmt on 'pyproject.toml'.\n"
"☐ Run 'uv run pyproject-fmt pyproject.toml' to run pyproject-fmt.\n"
)

Expand Down Expand Up @@ -3123,6 +3126,7 @@ def test_stdout(self, uv_init_dir: Path, capfd: pytest.CaptureFixture[str]):
"✔ Adding Ruff config to 'pyproject.toml'.\n"
"✔ Selecting Ruff rules 'A', 'C4', 'E4', 'E7', 'E9', 'F', 'FLY', 'FURB', 'I', 'PLE', 'PLR', 'RUF', 'SIM', 'UP' in 'pyproject.toml'.\n"
"✔ Ignoring Ruff rules 'PLR2004', 'SIM108' in 'pyproject.toml'.\n"
"✔ Running the Ruff formatter.\n"
"☐ Run 'uv run ruff check --fix' to run the Ruff linter with autofixes.\n"
"☐ Run 'uv run ruff format' to run the Ruff formatter.\n"
)
Expand Down Expand Up @@ -3182,6 +3186,7 @@ def test_existing_ruff_toml(
"✔ Adding dependency 'ruff' to the 'dev' group in 'pyproject.toml'.\n"
"☐ Install the dependency 'ruff'.\n"
"✔ Adding Ruff config to 'ruff.toml'.\n"
"✔ Running the Ruff formatter.\n"
"☐ Run 'uv run ruff check --fix' to run the Ruff linter with autofixes.\n"
"☐ Run 'uv run ruff format' to run the Ruff formatter.\n"
)
Expand Down Expand Up @@ -3274,10 +3279,10 @@ def test_only_add_formatter(
"✔ Adding dependency 'ruff' to the 'dev' group in 'pyproject.toml'.\n"
"☐ Install the dependency 'ruff'.\n"
"✔ Adding Ruff config to 'pyproject.toml'.\n"
"✔ Running the Ruff formatter.\n"
"☐ Run 'uv run ruff format' to run the Ruff formatter.\n"
)

class TestRemove:
@pytest.mark.usefixtures("_vary_network_conn")
def test_config_file(self, uv_init_dir: Path):
# Arrange
Expand Down
27 changes: 27 additions & 0 deletions tests/usethis/_tool/impl/base/test_pyproject_fmt.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

import pytest

from usethis._config import usethis_config
from usethis._config_file import files_manager
from usethis._test import change_cwd
from usethis._tool.impl.base.pyproject_fmt import PyprojectFmtTool
from usethis._types.backend import BackendEnum


class TestPyprojectFmtTool:
Expand All @@ -23,3 +25,28 @@ def test_uv_only(self, tmp_path: Path, capfd: pytest.CaptureFixture[str]):
assert out == (
"☐ Run 'uv run pyproject-fmt pyproject.toml' to run pyproject-fmt.\n"
)

class TestApply:
def test_uv_backend(self, uv_init_dir: Path, capfd: pytest.CaptureFixture[str]):
# Act
with change_cwd(uv_init_dir), files_manager():
PyprojectFmtTool().apply()

# Assert
out, err = capfd.readouterr()
assert not err
assert out == "✔ Running pyproject-fmt on 'pyproject.toml'.\n"

def test_none_backend(self, tmp_path: Path, capfd: pytest.CaptureFixture[str]):
# Act
with (
change_cwd(tmp_path),
usethis_config.set(backend=BackendEnum.none),
files_manager(),
):
PyprojectFmtTool().apply()

# Assert
out, err = capfd.readouterr()
assert not err
assert out == ""
41 changes: 41 additions & 0 deletions tests/usethis/_tool/impl/base/test_ruff.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import pytest

from usethis._config import usethis_config
from usethis._config_file import DotRuffTOMLManager, RuffTOMLManager, files_manager
from usethis._file.pyproject_toml.io_ import PyprojectTOMLManager
from usethis._test import change_cwd
from usethis._tool.impl.base.ruff import RuffTool
from usethis._types.backend import BackendEnum


class TestRuffTool:
Expand Down Expand Up @@ -568,3 +570,42 @@ def test_pyproject_toml_exists(self, tmp_path: Path):
assert not (tmp_path / ".ruff.toml").exists()
assert (tmp_path / "pyproject.toml").exists()
assert not (tmp_path / "ruff.toml").exists()

class TestApply:
def test_uv_backend_formatter_used(
self, uv_init_dir: Path, capfd: pytest.CaptureFixture[str]
):
# Act
with change_cwd(uv_init_dir), files_manager():
RuffTool(formatter_detection="always").apply()

# Assert
out, err = capfd.readouterr()
assert not err
assert out == "✔ Running the Ruff formatter.\n"

def test_uv_backend_formatter_not_used(
self, uv_init_dir: Path, capfd: pytest.CaptureFixture[str]
):
# Act
with change_cwd(uv_init_dir), files_manager():
RuffTool(formatter_detection="never").apply()

# Assert
out, err = capfd.readouterr()
assert not err
assert out == ""

def test_none_backend(self, tmp_path: Path, capfd: pytest.CaptureFixture[str]):
# Act
with (
change_cwd(tmp_path),
usethis_config.set(backend=BackendEnum.none),
files_manager(),
):
RuffTool(formatter_detection="always").apply()

# Assert
out, err = capfd.readouterr()
assert not err
assert out == ""
1 change: 1 addition & 0 deletions tests/usethis/_ui/interface/test_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,7 @@ def test_readme_example(self, uv_init_dir: Path):
✔ Adding Ruff config to 'pyproject.toml'.
✔ Selecting Ruff rules 'A', 'C4', 'E4', 'E7', 'E9', 'F', 'FLY', 'FURB', 'I', 'PLE', 'PLR', 'RUF', 'SIM', 'UP' in 'pyproject.toml'.
✔ Ignoring Ruff rules 'PLR2004', 'SIM108' in 'pyproject.toml'.
✔ Running the Ruff formatter.
☐ Run 'uv run ruff check --fix' to run the Ruff linter with autofixes.
☐ Run 'uv run ruff format' to run the Ruff formatter.
"""
Expand Down
Loading