Skip to content

214 implement usethis tool pyproject #325

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Feb 21, 2025
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
16 changes: 16 additions & 0 deletions src/usethis/_core/tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
uninstall_pre_commit_hooks,
)
from usethis._integrations.pre_commit.hooks import add_placeholder_hook, get_hook_names
from usethis._integrations.pyproject.remove import remove_pyproject_toml
from usethis._integrations.pyproject.valid import ensure_pyproject_validity
from usethis._integrations.pytest.core import add_pytest_dir, remove_pytest_dir
from usethis._integrations.ruff.rules import (
deselect_ruff_rules,
Expand All @@ -37,6 +39,7 @@
DeptryTool,
PreCommitTool,
PyprojectFmtTool,
PyprojectTOMLTool,
PytestTool,
RequirementsTxtTool,
RuffTool,
Expand Down Expand Up @@ -209,6 +212,19 @@ def use_pyproject_fmt(*, remove: bool = False) -> None:
remove_deps_from_group(tool.dev_deps, "dev")


def use_pyproject_toml(*, remove: bool = False) -> None:
tool = PyprojectTOMLTool()

ensure_pyproject_toml()

if not remove:
ensure_pyproject_toml()
ensure_pyproject_validity()
tool.print_how_to_use()
else:
remove_pyproject_toml()


def use_pytest(*, remove: bool = False) -> None:
tool = PytestTool()

Expand Down
13 changes: 13 additions & 0 deletions src/usethis/_integrations/pyproject/remove.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from pathlib import Path

from usethis._console import tick_print
from usethis._integrations.pyproject.io_ import pyproject_toml_io_manager


def remove_pyproject_toml() -> None:
path = Path.cwd() / "pyproject.toml"
if path.exists() and path.is_file():
tick_print("Removing 'pyproject.toml' file")
pyproject_toml_io_manager._opener.write_file()
pyproject_toml_io_manager._opener._set = False
path.unlink()
19 changes: 18 additions & 1 deletion src/usethis/_interface/tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use_deptry,
use_pre_commit,
use_pyproject_fmt,
use_pyproject_toml,
use_pytest,
use_requirements_txt,
use_ruff,
Expand Down Expand Up @@ -40,7 +41,7 @@ def codespell(
_run_tool(use_codespell, remove=remove)


@app.command(help="Use the coverage code coverage measurement tool.")
@app.command(help="Use coverage: a code coverage measurement tool.")
def coverage(
remove: bool = remove_opt,
offline: bool = offline_opt,
Expand Down Expand Up @@ -99,6 +100,22 @@ def pyproject_fmt(
_run_tool(use_pyproject_fmt, remove=remove)


@app.command(
name="pyproject.toml", help="Use a pyproject.toml file to configure the project."
)
def pyproject_toml(
remove: bool = remove_opt,
offline: bool = offline_opt,
quiet: bool = quiet_opt,
frozen: bool = frozen_opt,
) -> None:
with (
usethis_config.set(offline=offline, quiet=quiet, frozen=frozen),
pyproject_toml_io_manager.open(),
):
_run_tool(use_pyproject_toml, remove=remove)


@app.command(help="Use the pytest testing framework.")
def pytest(
remove: bool = remove_opt,
Expand Down
23 changes: 22 additions & 1 deletion src/usethis/_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from pathlib import Path
from typing import Protocol

from usethis._console import box_print, tick_print
from usethis._console import box_print, info_print, tick_print
from usethis._integrations.bitbucket.anchor import (
ScriptItemAnchor as BitbucketScriptItemAnchor,
)
Expand Down Expand Up @@ -439,6 +439,27 @@ def get_bitbucket_steps(self) -> list[BitbucketStep]:
]


class PyprojectTOMLTool(Tool):
@property
def name(self) -> str:
return "pyproject.toml"

@property
def dev_deps(self) -> list[Dependency]:
return []

def print_how_to_use(self) -> None:
box_print("Populate 'pyproject.toml' with the project configuration.")
info_print(
"Learn more at https://packaging.python.org/en/latest/guides/writing-pyproject-toml/"
)

def get_managed_files(self) -> list[Path]:
return [
Path("pyproject.toml"),
]


class PytestTool(Tool):
@property
def name(self) -> str:
Expand Down
23 changes: 23 additions & 0 deletions tests/usethis/_integrations/pyproject/test_remove.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from pathlib import Path

import pytest

from usethis._integrations.pyproject.remove import remove_pyproject_toml
from usethis._test import change_cwd


class TestRemovePyprojectTOML:
def test_removed(self, tmp_path: Path, capfd: pytest.CaptureFixture[str]):
# Arrange
pyproject_path = tmp_path / "pyproject.toml"
pyproject_path.touch()

# Act
with change_cwd(tmp_path):
remove_pyproject_toml()

# Assert
assert not pyproject_path.exists()
out, err = capfd.readouterr()
assert not err
assert out == "✔ Removing 'pyproject.toml' file\n"
42 changes: 31 additions & 11 deletions tests/usethis/_interface/test_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@
from usethis._test import change_cwd


class TestCodespell:
def test_add(self, tmp_path: Path):
# Act
runner = CliRunner()
with change_cwd(tmp_path):
result = runner.invoke(app, ["codespell"])

# Assert
assert result.exit_code == 0, result.output


class TestDeptry:
@pytest.mark.usefixtures("_vary_network_conn")
def test_cli(self, uv_init_dir: Path):
Expand All @@ -31,6 +42,26 @@ def test_cli_not_frozen(self, uv_init_dir: Path):
assert (uv_init_dir / ".venv").exists()


class TestPyprojectTOML:
def test_add(self, tmp_path: Path):
# Act
runner = CliRunner()
with change_cwd(tmp_path):
result = runner.invoke(app, ["pyproject.toml"])

# Assert
assert result.exit_code == 0, result.output

def test_remove(self, tmp_path: Path):
# Act
runner = CliRunner()
with change_cwd(tmp_path):
result = runner.invoke(app, ["pyproject.toml", "--remove"])

# Assert
assert result.exit_code == 0, result.output


class TestPreCommit:
@pytest.mark.usefixtures("_vary_network_conn")
def test_cli_pass(self, uv_init_repo_dir: Path):
Expand Down Expand Up @@ -92,17 +123,6 @@ def test_add(self, tmp_path: Path):
assert result.exit_code == 0, result.output


class TestCodespell:
def test_add(self, tmp_path: Path):
# Act
runner = CliRunner()
with change_cwd(tmp_path):
result = runner.invoke(app, ["codespell"])

# Assert
assert result.exit_code == 0, result.output


@pytest.mark.benchmark
def test_several_tools_add_and_remove(tmp_path: Path):
runner = CliRunner()
Expand Down
57 changes: 56 additions & 1 deletion tests/usethis/test_usethis_tool.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from pathlib import Path

import pytest
import requests

from usethis._config import usethis_config
from usethis._console import box_print
from usethis._integrations.pre_commit.hooks import _PLACEHOLDER_ID, get_hook_names
from usethis._integrations.pre_commit.schema import HookDefinition, LocalRepo, UriRepo
Expand All @@ -10,7 +12,7 @@
from usethis._integrations.pyproject.io_ import pyproject_toml_io_manager
from usethis._integrations.uv.deps import Dependency, add_deps_to_group
from usethis._test import change_cwd
from usethis._tool import ALL_TOOLS, DeptryTool, Tool
from usethis._tool import ALL_TOOLS, DeptryTool, PyprojectTOMLTool, Tool


class DefaultTool(Tool):
Expand Down Expand Up @@ -772,3 +774,56 @@ def test_all_tools_config_keys_are_subkeys_of_id_keys(tool: Tool):
assert any(config.id_keys[: len(id_key)] == id_key for id_key in id_keys), (
f"Config keys {config.id_keys} not covered by ID keys in {tool.name}"
)


class TestPyprojectTOMLTool:
class TestPrintHowToUse:
@pytest.mark.usefixtures("_vary_network_conn")
def test_link_isnt_dead(self):
"""A regression test."""

# Arrange
url = (
"https://packaging.python.org/en/latest/guides/writing-pyproject-toml/"
)

if not usethis_config.offline:
# Act
result = requests.head(url)

# Assert
assert result.status_code == 200

def test_some_output(self, capfd: pytest.CaptureFixture[str]):
# Arrange
tool = PyprojectTOMLTool()

# Act
tool.print_how_to_use()

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

class TestName:
def test_value(self):
# Arrange
tool = PyprojectTOMLTool()

# Act
result = tool.name

# Assert
assert result == "pyproject.toml"

class TestDevDeps:
def test_none(self):
# Arrange
tool = PyprojectTOMLTool()

# Act
result = tool.dev_deps

# Assert
assert result == []