Skip to content

Commit

Permalink
Add uv project management example
Browse files Browse the repository at this point in the history
  • Loading branch information
davidkun committed Sep 2, 2024
1 parent 54dd013 commit 3f05e9b
Show file tree
Hide file tree
Showing 10 changed files with 366 additions and 3 deletions.
27 changes: 27 additions & 0 deletions .github/workflows/uv.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: uv workflow
on:
push:
paths:
- 'uv-demo/**'
- '.github/workflows/uv.yml'
defaults:
run:
working-directory: ./uv-demo

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install uv 0.4.2
run: curl -LsSf https://astral.sh/uv/0.4.2/install.sh | sh
- name: Sync
run: uv sync --all-extras --dev
- name: Lint
run: uv run ruff check
- name: Run tests
run: uv run pytest
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
# Comparing Hatch, Poetry, and Rye
# Comparing Hatch, Poetry, Rye, and uv

> [!NOTE]
> Originally this compared hatch, poetry, and rye, but `uv` [recently announced](https://astral.sh/blog/uv-unified-python-packaging) their release of a package manager too. So `uv` was added to the comparison here. Going forward, Astral will take stewardship of `rye` (see this [announcement](https://github.com/astral-sh/rye/discussions/659) and this [blog post](https://lucumr.pocoo.org/2024/2/15/rye-grows-with-uv/)) and it will kinda get "merged" into `uv`. The tl;dr is you should choose `uv` for your project management tool.
Each subdirectory has a Python project example for
[Hatch](https://hatch.pypa.io/latest/),
[Rye](https://rye.astral.sh), and
[Poetry](https://python-poetry.org/).
[Rye](https://rye.astral.sh),
[Poetry](https://python-poetry.org/), and
[uv](https://docs.astral.sh/uv/).

<table>
<tr>
Expand All @@ -13,6 +17,7 @@ Each subdirectory has a Python project example for
<td><a href="https://hatch.pypa.io"><img src="https://hatch.pypa.io/latest/assets/images/logo.svg" height="64px" alt="Hatch"></a></td>
<td><a href="https://rye.astral.sh"><img src="https://rye.astral.sh/static/logo.svg" height="64px" alt="Rye"></a></td>
<td><a href="https://python-poetry.org"><img src="https://python-poetry.org/images/logo-origami.svg" height="64px" alt="Poetry"></a></td>
<td><a href="https://docs.astral.sh/uv"><img src="https://docs.astral.sh/uv/assets/logo-letter.svg" height="64px" alt="uv"></a></td>
</tr>
</table>

Expand All @@ -24,6 +29,7 @@ actions:
* [hatch-demo](./hatch-demo/README.md)
* [poetry-demo](./poetry-demo/README.md)
* [rye-demo](./rye-demo/README.md)
* [uv-demo](./uv-demo/README.md)

There is a GitHub Action for each sub-project in [workflows](.github/workflows/).
Note that the actions are minimal examples for the sake of keeping things simple
Expand Down
79 changes: 79 additions & 0 deletions uv-demo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# uv Demo

## Steps

1. Install `uv`:
```bash
$ curl -LsSf https://astral.sh/uv/install.sh | sh
```

2. Create a new project (with the `--lib` [option](https://docs.astral.sh/uv/concepts/projects/#libraries)):
```bash
$ uv init --lib uv-demo
```

> [!TIP]
> The `--lib` option specifies the `build-system` for us in `pyproject.toml` and creates the project in the [src layout](https://packaging.python.org/en/latest/discussions/src-layout-vs-flat-layout/).

3. Add dependencies (`requests`, `polars`, `pytest`, `ruff`)
* Note: Add `pytest` and `ruff` under the `dev` group.

```bash
$ uv add requests
$ uv add polars
$ uv add --dev pytest
$ uv add --dev ruff
```

Those commands added the following sections to our initial `pyproject.toml` file, and created/updated a `uv.lock` file.

```toml
[project]
name = "uv-demo"
version = "0.1.0"
description = ""
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"polars>=1.6.0",
"requests>=2.32.3",
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.uv]
dev-dependencies = [
"pytest>=8.3.2",
"ruff>=0.6.3",
]
```

4. Add code (`demo.py`, `__init__.py`, `__main__.py`) and tests (`test_demo.py`).

5. Run tests and lint/format code:
```bash
$ uv run pytest
$ uv run ruff check --fix
$ uv run ruff format
```

6. Run the app:
```bash
$ uv run python -m uv_demo
```

Or, add the following section to `pyproject.toml`:
```toml
[project.scripts]
"uv-demo" = "uv_demo:main"
```

to run it more directly:
```bash
$ uv run uv-demo
```

9. Add [uv-based GH workflow](../.github/workflows/uv.yml) to lint and run tests.
23 changes: 23 additions & 0 deletions uv-demo/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[project]
name = "uv-demo"
version = "0.1.0"
description = ""
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"polars>=1.6.0",
"requests>=2.32.3",
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.uv]
dev-dependencies = [
"pytest>=8.3.2",
"ruff>=0.6.3",
]

[project.scripts]
"uv-demo" = "uv_demo:main"
5 changes: 5 additions & 0 deletions uv-demo/src/uv_demo/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from . import demo

def main() -> int:
demo.main()
return 0
4 changes: 4 additions & 0 deletions uv-demo/src/uv_demo/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import uv_demo
import sys

sys.exit(uv_demo.main())
26 changes: 26 additions & 0 deletions uv-demo/src/uv_demo/demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from io import StringIO
import polars as pl
import requests

IRIS = "https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv"


def main():
result = analyze(dataframe_from(iris_data()))
print(result)


def iris_data(url: str = IRIS) -> str:
return requests.get(url).text


def dataframe_from(csv_data: str) -> pl.DataFrame:
return pl.read_csv(StringIO(csv_data))


def analyze(df: pl.DataFrame) -> pl.DataFrame:
return df.filter(pl.col("sepal_length") > 5).group_by("species").agg(pl.all().sum())


if __name__ == "__main__":
main()
Empty file added uv-demo/tests/__init__.py
Empty file.
9 changes: 9 additions & 0 deletions uv-demo/tests/test_demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from uv_demo.demo import dataframe_from


def test_dataframe_from():
df = dataframe_from(
"""A,B,C
0.0,10.0,200.1"""
)
assert len(df.count()) == 1
Loading

0 comments on commit 3f05e9b

Please sign in to comment.