Skip to content

Commit

Permalink
Add implementation of usethis tool pyproject-fmt (#97)
Browse files Browse the repository at this point in the history
  • Loading branch information
nathanjmcdougall authored Oct 31, 2024
1 parent c1d09e6 commit a5a5543
Show file tree
Hide file tree
Showing 9 changed files with 520 additions and 374 deletions.
4 changes: 4 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ repos:
hooks:
- id: validate-pyproject
additional_dependencies: ["validate-pyproject-schema-store[all]"]
- repo: https://github.com/tox-dev/pyproject-fmt
rev: "v2.5.0"
hooks:
- id: pyproject-fmt
- repo: local
hooks:
- id: ruff-format
Expand Down
6 changes: 4 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ dev = [
"deptry>=0.20.0",
"import-linter>=2.1",
"pre-commit>=4.0.1",
"pyproject-fmt>=2.4.3",
"pyright>=1.1.387",
"ruff>=0.7.1",
]
Expand All @@ -74,7 +73,10 @@ line-length = 88

src = [ "src" ]
lint.select = [ "C4", "E4", "E7", "E9", "F", "FURB", "I", "PLE", "PLR", "PT", "RUF", "SIM", "UP" ]
lint.ignore = ["PT004", "PT005"]
lint.ignore = [ "PT004", "PT005" ]

[tool.pyproject-fmt]
keep_full_version = true

[tool.pytest.ini_options]
testpaths = [ "tests" ]
Expand Down
30 changes: 20 additions & 10 deletions src/usethis/_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,6 @@
import typer
from pydantic import BaseModel

_OFFLINE_DEFAULT = False
_QUIET_DEFAULT = False

offline_opt = typer.Option(_OFFLINE_DEFAULT, "--offline", help="Disable network access")
quiet_opt = typer.Option(_QUIET_DEFAULT, "--quiet", help="Suppress output")


class UsethisConfig(BaseModel):
"""Global-state for command options which affect low level behaviour."""
Expand All @@ -18,13 +12,29 @@ class UsethisConfig(BaseModel):
quiet: bool

@contextmanager
def set(self, *, offline: bool, quiet: bool) -> Generator[None, None, None]:
"""Temporarily set the console to quiet mode."""
def set(
self, *, offline: bool | None = None, quiet: bool | None = None
) -> Generator[None, None, None]:
"""Temporarily change command options."""
old_offline = self.offline
old_quiet = self.quiet

if offline is None:
offline = old_offline
if quiet is None:
quiet = old_quiet

self.offline = offline
self.quiet = quiet
yield
self.offline = _OFFLINE_DEFAULT
self.quiet = _QUIET_DEFAULT
self.offline = old_offline
self.quiet = old_quiet


_OFFLINE_DEFAULT = False
_QUIET_DEFAULT = False

usethis_config = UsethisConfig(offline=_OFFLINE_DEFAULT, quiet=_QUIET_DEFAULT)

offline_opt = typer.Option(_OFFLINE_DEFAULT, "--offline", help="Disable network access")
quiet_opt = typer.Option(_QUIET_DEFAULT, "--quiet", help="Suppress output")
2 changes: 1 addition & 1 deletion src/usethis/_integrations/pre_commit/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

class HookConfig(BaseModel):
id: str
name: str
name: str | None = None
entry: str | None = None
language: Literal["system", "python"] | None = None
always_run: bool | None = None
Expand Down
1 change: 1 addition & 0 deletions src/usethis/_integrations/pre_commit/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

_HOOK_ORDER = [
"validate-pyproject",
"pyproject-fmt",
"ruff-format",
"ruff-check",
"deptry",
Expand Down
148 changes: 101 additions & 47 deletions src/usethis/_interface/tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,47 @@
from usethis._integrations.pytest.core import add_pytest_dir, remove_pytest_dir
from usethis._integrations.ruff.rules import deselect_ruff_rules, select_ruff_rules
from usethis._integrations.uv.deps import add_deps_to_group, remove_deps_from_group
from usethis._tool import ALL_TOOLS, DeptryTool, PreCommitTool, PytestTool, RuffTool
from usethis._tool import (
ALL_TOOLS,
DeptryTool,
PreCommitTool,
PyprojectFmtTool,
PytestTool,
RuffTool,
)

app = typer.Typer(help="Add and configure development tools, e.g. linters.")


@app.command(
help="Use the deptry linter: avoid missing or superfluous dependency declarations."
)
def deptry(
remove: bool = typer.Option(
False, "--remove", help="Remove deptry instead of adding it."
),
offline: bool = offline_opt,
quiet: bool = quiet_opt,
) -> None:
with usethis_config.set(offline=offline, quiet=quiet):
_deptry(remove=remove)


def _deptry(*, remove: bool = False) -> None:
tool = DeptryTool()

if not remove:
add_deps_to_group(tool.dev_deps, "dev")
if PreCommitTool().is_used():
tool.add_pre_commit_repo_config()

box_print("Call the 'deptry src' command to run deptry.")
else:
if PreCommitTool().is_used():
tool.remove_pre_commit_repo_config()
remove_deps_from_group(tool.dev_deps, "dev")


@app.command(
help="Use the pre-commit framework to manage and maintain pre-commit hooks."
)
Expand Down Expand Up @@ -54,72 +90,51 @@ def _pre_commit(*, remove: bool = False) -> None:
remove_pre_commit_config()
remove_deps_from_group(tool.dev_deps, "dev")

# Need to add a new way of running some hooks manually if they are not dev
# dependencies yet
if PyprojectFmtTool().is_used():
_pyproject_fmt()


@app.command(
help="Use the deptry linter: avoid missing or superfluous dependency declarations."
help="Use the pyproject-fmt linter: opinionated formatting of 'pyproject.toml' files."
)
def deptry(
def pyproject_fmt(
remove: bool = typer.Option(
False, "--remove", help="Remove deptry instead of adding it."
False, "--remove", help="Remove pyproject-fmt instead of adding it."
),
offline: bool = offline_opt,
quiet: bool = quiet_opt,
) -> None:
with usethis_config.set(offline=offline, quiet=quiet):
_deptry(remove=remove)
_pyproject_fmt(remove=remove)


def _deptry(*, remove: bool = False) -> None:
tool = DeptryTool()
def _pyproject_fmt(*, remove: bool = False) -> None:
tool = PyprojectFmtTool()

if not remove:
add_deps_to_group(tool.dev_deps, "dev")
if PreCommitTool().is_used():
tool.add_pre_commit_repo_config()

box_print("Call the 'deptry src' command to run deptry.")
else:
if PreCommitTool().is_used():
tool.remove_pre_commit_repo_config()
remove_deps_from_group(tool.dev_deps, "dev")


@app.command(help="Use ruff: an extremely fast Python linter and code formatter.")
def ruff(
remove: bool = typer.Option(
False, "--remove", help="Remove ruff instead of adding it."
),
offline: bool = offline_opt,
quiet: bool = quiet_opt,
) -> None:
with usethis_config.set(offline=offline, quiet=quiet):
_ruff(remove=remove)

is_precommit = PreCommitTool().is_used()

def _ruff(*, remove: bool = False) -> None:
tool = RuffTool()

rules = []
for _tool in ALL_TOOLS:
if _tool.is_used() or _tool.name == "ruff":
with contextlib.suppress(NotImplementedError):
rules += _tool.get_associated_ruff_rules()
if not is_precommit:
add_deps_to_group(tool.dev_deps, "dev")
else:
tool.add_pre_commit_repo_config()

if not remove:
add_deps_to_group(tool.dev_deps, "dev")
tool.add_pyproject_configs()
select_ruff_rules(rules)
if PreCommitTool().is_used():
tool.add_pre_commit_repo_config()

box_print(
"Call the 'ruff check --fix' command to run the ruff linter with autofixes."
)
box_print("Call the 'ruff format' command to run the ruff formatter.")
if not is_precommit:
box_print(
"Call the 'pyproject-fmt pyproject.toml' command to run pyproject-fmt."
)
else:
box_print(
"Call the 'pre-commit run pyproject-fmt --all-files' command to run pyproject-fmt."
)
else:
tool.remove_pyproject_configs()
if PreCommitTool().is_used():
tool.remove_pre_commit_repo_config()
tool.remove_pyproject_configs() # N.B. this will remove the selected ruff rules
remove_deps_from_group(tool.dev_deps, "dev")


Expand Down Expand Up @@ -158,3 +173,42 @@ def _pytest(*, remove: bool = False) -> None:
tool.remove_pyproject_configs()
remove_deps_from_group(tool.dev_deps, "test")
remove_pytest_dir() # Last, since this is a manual step


@app.command(help="Use ruff: an extremely fast Python linter and code formatter.")
def ruff(
remove: bool = typer.Option(
False, "--remove", help="Remove ruff instead of adding it."
),
offline: bool = offline_opt,
quiet: bool = quiet_opt,
) -> None:
with usethis_config.set(offline=offline, quiet=quiet):
_ruff(remove=remove)


def _ruff(*, remove: bool = False) -> None:
tool = RuffTool()

rules = []
for _tool in ALL_TOOLS:
if _tool.is_used() or _tool.name == "ruff":
with contextlib.suppress(NotImplementedError):
rules += _tool.get_associated_ruff_rules()

if not remove:
add_deps_to_group(tool.dev_deps, "dev")
tool.add_pyproject_configs()
select_ruff_rules(rules)
if PreCommitTool().is_used():
tool.add_pre_commit_repo_config()

box_print(
"Call the 'ruff check --fix' command to run the ruff linter with autofixes."
)
box_print("Call the 'ruff format' command to run the ruff formatter.")
else:
if PreCommitTool().is_used():
tool.remove_pre_commit_repo_config()
tool.remove_pyproject_configs() # N.B. this will remove the selected ruff rules
remove_deps_from_group(tool.dev_deps, "dev")
Loading

0 comments on commit a5a5543

Please sign in to comment.