Skip to content

Commit

Permalink
Feature/12 implement usethis tool deptry (#13)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
nathanjmcdougall authored Sep 12, 2024
1 parent 957fb23 commit c9b2fa6
Show file tree
Hide file tree
Showing 8 changed files with 317 additions and 10 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ on:
jobs:
tests:
runs-on: ${{ matrix.os }}
env:
PYTHONIOENCODING: utf-8
steps:
- name: Checkout code
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
Expand Down
11 changes: 10 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@ version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []
dependencies = [
"rich>=13.8.1",
"typer>=0.12.5",
]

[project.scripts]
usethis = "usethis.__main__:app"

[build-system]
requires = ["hatchling"]
Expand All @@ -15,4 +21,7 @@ dev-dependencies = [
"pytest>=8.3.2",
"pytest-md>=0.2.0",
"pytest-emoji>=0.2.0",
"pydantic>=2.9.1",
"tomlkit>=0.13.2",
"deptry>=0.20.0",
]
5 changes: 3 additions & 2 deletions src/usethis/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
def hello() -> str:
return "Hello from usethis!"
from rich.console import Console

console = Console()
12 changes: 12 additions & 0 deletions src/usethis/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import typer

import usethis.tool

app = typer.Typer(
help=(
"🤖 Automate Python package and project setup tasks that are otherwise "
"performed manually."
)
)
app.add_typer(usethis.tool.app, name="tool")
app(prog_name="usethis")
15 changes: 15 additions & 0 deletions src/usethis/tool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import subprocess

import typer

from usethis import console

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() -> None:
console.print("✔ Ensuring deptry is a development dependency", style="green")
subprocess.run(["uv", "add", "--dev", "--quiet", "deptry"], check=True)
2 changes: 0 additions & 2 deletions tests/test_nothing.py

This file was deleted.

82 changes: 82 additions & 0 deletions tests/usethis/test_tool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import os
import subprocess
from contextlib import contextmanager
from pathlib import Path
from typing import Generator

import pytest
import tomlkit
from pydantic import TypeAdapter

from usethis.tool import deptry


@contextmanager
def change_cwd(new_dir: Path) -> Generator[None, None, None]:
"""Change the working directory temporarily."""
old_dir = Path.cwd()
os.chdir(new_dir)
try:
yield
finally:
os.chdir(old_dir)


@pytest.fixture
def uv_init_dir(tmp_path: Path) -> None:
subprocess.run(["uv", "init"], cwd=tmp_path, check=True)
return tmp_path


class TestDeptry:
def test_dependency_added(self, uv_init_dir: Path):
# Act
with change_cwd(uv_init_dir):
deptry()

# Assert
(dev_dep,) = _get_dev_deps(uv_init_dir)
assert dev_dep.startswith("deptry>=")

def test_stdout(self, uv_init_dir: Path, capfd: pytest.CaptureFixture[str]):
# Act
with change_cwd(uv_init_dir):
deptry()

# Assert
out, _ = capfd.readouterr()
assert out == "✔ Ensuring deptry is a development dependency\n"

def test_run_deptry_fail(self, uv_init_dir: Path):
# Arrange
f = uv_init_dir / "bad.py"
f.write_text("import broken_dependency")

# Act
with change_cwd(uv_init_dir):
deptry()

# Assert
with pytest.raises(subprocess.CalledProcessError):
subprocess.run(["deptry", "."], cwd=uv_init_dir, check=True)

def test_run_deptry_pass(self, uv_init_dir: Path):
# Arrange
f = uv_init_dir / "good.py"
f.write_text("import sys")

# Act
with change_cwd(uv_init_dir):
deptry()

# Assert
subprocess.run(["deptry", "."], cwd=uv_init_dir, check=True)

def test_cli(self, uv_init_dir: Path):
subprocess.run(["usethis", "tool", "deptry"], cwd=uv_init_dir, check=True)


def _get_dev_deps(proj_dir: Path) -> list[str]:
pyproject = tomlkit.parse((proj_dir / "pyproject.toml").read_text())
dev_deps = pyproject["tool"]["uv"]["dev-dependencies"]
return TypeAdapter(list[str]).validate_python(dev_deps)
198 changes: 193 additions & 5 deletions uv.lock

Large diffs are not rendered by default.

0 comments on commit c9b2fa6

Please sign in to comment.