Skip to content

Commit

Permalink
refactor: Use higher-level uv commands
Browse files Browse the repository at this point in the history
  • Loading branch information
pawamoy committed Sep 5, 2024
1 parent 6de0292 commit bf3c328
Show file tree
Hide file tree
Showing 7 changed files with 68 additions and 88 deletions.
18 changes: 14 additions & 4 deletions docs/work.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,11 @@ By default it is set to active versions of Python

Dependencies are managed by [uv](https://github.com/astral-sh/uv).

Use `make setup` or `uv venv; uv pip install -r devdeps.txt` to install the dependencies.
Use `make setup` or `uv sync` to install the dependencies.

Runtime dependencies are written in `pyproject.toml`,
under the `[project]` and `[project.optional-dependencies]`
sections, and development dependencies are listed in `devdeps.txt`.
Dependencies are written in `pyproject.toml`.
Runtime dependencies are listed under the `[project]` and `[project.optional-dependencies]` sections,
and development dependencies are listed under the `[tool.uv]` section.

Example:

Expand All @@ -93,6 +93,16 @@ dependencies = [
"fastapi>=1.0",
"importlib-metadata>=2.0",
]

[project.optional-dependencies]
test = [
"pytest",
]

[tool.uv]
dev-dependencies = [
"ruff",
]
```

## Tasks
Expand Down
6 changes: 4 additions & 2 deletions project/.github/workflows/ci.yml.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,10 @@ jobs:
python-version: {% raw %}${{ matrix.python-version }}{% endraw %}
allow-prereleases: true

- name: Install uv
run: pip install uv
- name: Setup uv
uses: astral-sh/setup-uv@v1
with:
enable-cache: true

- name: Install dependencies
env:
Expand Down
1 change: 1 addition & 0 deletions project/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
/.pdm-build/
/htmlcov/
/site/
uv.lock

# cache
.cache/
Expand Down
33 changes: 0 additions & 33 deletions project/devdeps.txt.jinja

This file was deleted.

37 changes: 36 additions & 1 deletion project/pyproject.toml.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,6 @@ source-includes = [
"scripts",
"share",
"tests",
"devdeps.txt",
"duties.py",
"mkdocs.yml",
"*.md",
Expand All @@ -139,3 +138,39 @@ source-includes = [
data = [
{path = "share/**/*", relative-to = "."},
]

[tool.uv]
dev-dependencies = [
# dev
"editables>=0.5",

# maintenance
"build>=1.2",
"git-changelog>=2.5",
"twine>=5.0; python_version < '3.13'",

# ci
"duty>=1.4",
"ruff>=0.4",
"pytest>=8.2",
"pytest-cov>=5.0",
"pytest-randomly>=3.15",
"pytest-xdist>=3.6",
"mypy>=1.10",
"types-markdown>=3.6",
"types-pyyaml>=6.0",

# docs
"black>=24.4",
"markdown-callouts>=0.4",
"markdown-exec>=1.8",
"mkdocs>=1.6",
"mkdocs-coverage>=1.0",
"mkdocs-gen-files>=0.5",
"mkdocs-git-revision-date-localized-plugin>=1.2",
"mkdocs-literate-nav>=0.6",
"mkdocs-material>=9.5",
"mkdocs-minify-plugin>=0.8",
"mkdocstrings[python]>=0.25",
"tomli>=2.0; python_version < '3.11'",
]
3 changes: 1 addition & 2 deletions project/scripts/gen_credits.py.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ with project_dir.joinpath("pyproject.toml").open("rb") as pyproject_file:
pyproject = tomllib.load(pyproject_file)
project = pyproject["project"]
project_name = project["name"]
with project_dir.joinpath("devdeps.txt").open() as devdeps_file:
devdeps = [line.strip() for line in devdeps_file if line.strip() and not line.strip().startswith(("-e", "#"))]
devdeps = [dep for dep in pyproject["tool"]["uv"]["dev-dependencies"] if not dep.startswith("-e")]

PackageMetadata = Dict[str, Union[str, Iterable[str]]]
Metadata = Dict[str, PackageMetadata]
Expand Down
58 changes: 12 additions & 46 deletions project/scripts/make
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@ from typing import Any, Iterator

PYTHON_VERSIONS = os.getenv("PYTHON_VERSIONS", "3.8 3.9 3.10 3.11 3.12 3.13").split()

exe = ""
prefix = ""


def shell(cmd: str, capture_output: bool = False, **kwargs: Any) -> str | None:
"""Run a shell command."""
Expand All @@ -37,18 +34,11 @@ def environ(**kwargs: str) -> Iterator[None]:
os.environ.update(original)


def uv_install() -> None:
def uv_install(venv: Path) -> None:
"""Install dependencies using uv."""
uv_opts = ""
if "UV_RESOLUTION" in os.environ:
uv_opts = f"--resolution={os.getenv('UV_RESOLUTION')}"
requirements = shell(f"uv pip compile {uv_opts} pyproject.toml devdeps.txt", capture_output=True)
shell("uv pip install -r -", input=requirements, text=True)
if "CI" not in os.environ:
shell("uv pip install --no-deps -e .")
else:
shell("uv pip install --no-deps .")

with environ(UV_PROJECT_ENVIRONMENT=str(venv)):
shell("uv sync")


def setup() -> None:
"""Setup the project."""
Expand All @@ -59,47 +49,27 @@ def setup() -> None:
default_venv = Path(".venv")
if not default_venv.exists():
shell("uv venv --python python")
uv_install()
uv_install(default_venv)

if PYTHON_VERSIONS:
for version in PYTHON_VERSIONS:
print(f"\nInstalling dependencies (python{version})") # noqa: T201
venv_path = Path(f".venvs/{version}")
if not venv_path.exists():
shell(f"uv venv --python {version} {venv_path}")
with environ(VIRTUAL_ENV=str(venv_path.resolve())):
uv_install()


def activate(path: str) -> None:
"""Activate a virtual environment."""
global exe, prefix # noqa: PLW0603

if (bin := Path(path, "bin")).exists():
activate_script = bin / "activate_this.py"
elif (scripts := Path(path, "Scripts")).exists():
activate_script = scripts / "activate_this.py"
exe = ".exe"
prefix = f"{path}/Scripts/"
else:
raise ValueError(f"make: activate: Cannot find activation script in {path}")

if not activate_script.exists():
raise ValueError(f"make: activate: Cannot find activation script in {path}")

exec(activate_script.read_text(), {"__file__": str(activate_script)}) # noqa: S102
with environ(UV_PROJECT_ENVIRONMENT=str(venv_path.resolve())):
uv_install(venv_path)


def run(version: str, cmd: str, *args: str, **kwargs: Any) -> None:
"""Run a command in a virtual environment."""
kwargs = {"check": True, **kwargs}
if version == "default":
activate(".venv")
subprocess.run([f"{prefix}{cmd}{exe}", *args], **kwargs) # noqa: S603, PLW1510
with environ(UV_PROJECT_ENVIRONMENT=".venv"):
subprocess.run(["uv", "run", cmd, *args], **kwargs) # noqa: S603, PLW1510
else:
activate(f".venvs/{version}")
os.environ["MULTIRUN"] = "1"
subprocess.run([f"{prefix}{cmd}{exe}", *args], **kwargs) # noqa: S603, PLW1510
with environ(UV_PROJECT_ENVIRONMENT=f".venvs/{version}", MULTIRUN="1"):
subprocess.run(["uv", "run", cmd, *args], **kwargs) # noqa: S603, PLW1510


def multirun(cmd: str, *args: str, **kwargs: Any) -> None:
Expand Down Expand Up @@ -152,11 +122,7 @@ def main() -> int:
print(" 3.x Run a command in the virtual environment for Python 3.x.") # noqa: T201
print(" clean Delete build artifacts and cache files.") # noqa: T201
print(" vscode Configure VSCode to work on this project.") # noqa: T201
try:
run("default", "python", "-V", capture_output=True)
except (subprocess.CalledProcessError, ValueError):
pass
else:
if os.path.exists(".venv"):
print("\nAvailable tasks") # noqa: T201
run("default", "duty", "--list")
return 0
Expand Down

0 comments on commit bf3c328

Please sign in to comment.