Skip to content

Commit c9b2fa6

Browse files
Feature/12 implement usethis tool deptry (#13)
* Add deptry function which installs deptry via uv as a dev dependency. * Add progress message for usethis tool deptry. * Add tests for running deptry after calling `usethis tool deptry`. * Remove hello function. * Configure the package as a CLI app using typer. * Reword message for usethis tool deptry * Reword message for usethis tool deptry * Use rich for console output * Set PYTHONIOENCODING explicitly in CI config.
1 parent 957fb23 commit c9b2fa6

File tree

8 files changed

+317
-10
lines changed

8 files changed

+317
-10
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ on:
77
jobs:
88
tests:
99
runs-on: ${{ matrix.os }}
10+
env:
11+
PYTHONIOENCODING: utf-8
1012
steps:
1113
- name: Checkout code
1214
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7

pyproject.toml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@ version = "0.1.0"
44
description = "Add your description here"
55
readme = "README.md"
66
requires-python = ">=3.12"
7-
dependencies = []
7+
dependencies = [
8+
"rich>=13.8.1",
9+
"typer>=0.12.5",
10+
]
11+
12+
[project.scripts]
13+
usethis = "usethis.__main__:app"
814

915
[build-system]
1016
requires = ["hatchling"]
@@ -15,4 +21,7 @@ dev-dependencies = [
1521
"pytest>=8.3.2",
1622
"pytest-md>=0.2.0",
1723
"pytest-emoji>=0.2.0",
24+
"pydantic>=2.9.1",
25+
"tomlkit>=0.13.2",
26+
"deptry>=0.20.0",
1827
]

src/usethis/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
def hello() -> str:
2-
return "Hello from usethis!"
1+
from rich.console import Console
2+
3+
console = Console()

src/usethis/__main__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import typer
2+
3+
import usethis.tool
4+
5+
app = typer.Typer(
6+
help=(
7+
"🤖 Automate Python package and project setup tasks that are otherwise "
8+
"performed manually."
9+
)
10+
)
11+
app.add_typer(usethis.tool.app, name="tool")
12+
app(prog_name="usethis")

src/usethis/tool.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import subprocess
2+
3+
import typer
4+
5+
from usethis import console
6+
7+
app = typer.Typer(help="Add and configure development tools, e.g. linters")
8+
9+
10+
@app.command(
11+
help="Use the deptry linter: avoid missing or superfluous dependency declarations."
12+
)
13+
def deptry() -> None:
14+
console.print("✔ Ensuring deptry is a development dependency", style="green")
15+
subprocess.run(["uv", "add", "--dev", "--quiet", "deptry"], check=True)

tests/test_nothing.py

Lines changed: 0 additions & 2 deletions
This file was deleted.

tests/usethis/test_tool.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import os
2+
import subprocess
3+
from contextlib import contextmanager
4+
from pathlib import Path
5+
from typing import Generator
6+
7+
import pytest
8+
import tomlkit
9+
from pydantic import TypeAdapter
10+
11+
from usethis.tool import deptry
12+
13+
14+
@contextmanager
15+
def change_cwd(new_dir: Path) -> Generator[None, None, None]:
16+
"""Change the working directory temporarily."""
17+
old_dir = Path.cwd()
18+
os.chdir(new_dir)
19+
try:
20+
yield
21+
finally:
22+
os.chdir(old_dir)
23+
24+
25+
@pytest.fixture
26+
def uv_init_dir(tmp_path: Path) -> None:
27+
subprocess.run(["uv", "init"], cwd=tmp_path, check=True)
28+
return tmp_path
29+
30+
31+
class TestDeptry:
32+
def test_dependency_added(self, uv_init_dir: Path):
33+
# Act
34+
with change_cwd(uv_init_dir):
35+
deptry()
36+
37+
# Assert
38+
(dev_dep,) = _get_dev_deps(uv_init_dir)
39+
assert dev_dep.startswith("deptry>=")
40+
41+
def test_stdout(self, uv_init_dir: Path, capfd: pytest.CaptureFixture[str]):
42+
# Act
43+
with change_cwd(uv_init_dir):
44+
deptry()
45+
46+
# Assert
47+
out, _ = capfd.readouterr()
48+
assert out == "✔ Ensuring deptry is a development dependency\n"
49+
50+
def test_run_deptry_fail(self, uv_init_dir: Path):
51+
# Arrange
52+
f = uv_init_dir / "bad.py"
53+
f.write_text("import broken_dependency")
54+
55+
# Act
56+
with change_cwd(uv_init_dir):
57+
deptry()
58+
59+
# Assert
60+
with pytest.raises(subprocess.CalledProcessError):
61+
subprocess.run(["deptry", "."], cwd=uv_init_dir, check=True)
62+
63+
def test_run_deptry_pass(self, uv_init_dir: Path):
64+
# Arrange
65+
f = uv_init_dir / "good.py"
66+
f.write_text("import sys")
67+
68+
# Act
69+
with change_cwd(uv_init_dir):
70+
deptry()
71+
72+
# Assert
73+
subprocess.run(["deptry", "."], cwd=uv_init_dir, check=True)
74+
75+
def test_cli(self, uv_init_dir: Path):
76+
subprocess.run(["usethis", "tool", "deptry"], cwd=uv_init_dir, check=True)
77+
78+
79+
def _get_dev_deps(proj_dir: Path) -> list[str]:
80+
pyproject = tomlkit.parse((proj_dir / "pyproject.toml").read_text())
81+
dev_deps = pyproject["tool"]["uv"]["dev-dependencies"]
82+
return TypeAdapter(list[str]).validate_python(dev_deps)

uv.lock

Lines changed: 193 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)