Skip to content

Commit f48ee08

Browse files
swap to using uv for project management (#213)
1 parent b61f01d commit f48ee08

18 files changed

Lines changed: 3220 additions & 348 deletions

.dockerfiles/Dockerfile

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,38 @@
1-
FROM python:3.11-slim as base
2-
ENV PYTHONPATH /app
1+
ARG PYTHON_VERSION=3.13
2+
ARG UID=1000
3+
ARG GID=1000
4+
5+
FROM python:${PYTHON_VERSION}-slim as base
36
ENV PYTHONDONTWRITEBYTECODE 1
47
ENV PYTHONUNBUFFERED 1
5-
ENV DEBUG False
6-
RUN mkdir -p /app
7-
WORKDIR /app
8-
8+
ENV UV_LINK_MODE copy
9+
ENV UV_COMPILE_BYTECODE 1
10+
ENV UV_PYTHON_DOWNLOADS never
11+
ENV UV_PYTHON python${PYTHON_VERSION}
12+
ENV UV_PROJECT_ENVIRONMENT /app
13+
ENV PATH /app/bin:$PATH
14+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
915

10-
FROM base as py
11-
COPY src /app/src
12-
COPY LICENSE pyproject.toml README.md /app/
13-
RUN python -m pip install --upgrade pip \
14-
&& python -m pip install '.[hc,psycopg,relay]'
1516

16-
17-
FROM base as app
18-
COPY src/service /app/service
17+
FROM base as builder
18+
RUN --mount=type=cache,target=/root/.cache \
19+
--mount=type=bind,source=uv.lock,target=uv.lock \
20+
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
21+
uv sync --locked --no-dev --no-install-project
22+
COPY . /src
23+
WORKDIR /src
24+
RUN --mount=type=cache,target=/root/.cache \
25+
uv sync --locked --no-dev --no-editable --extra hc --extra psycopg --extra relay
1926

2027

2128
FROM base as final
22-
COPY --from=py /usr/local /usr/local
23-
COPY --from=app /app /app
24-
RUN addgroup --system django \
25-
&& adduser --system --ingroup django django \
29+
ARG UID
30+
ARG GID
31+
RUN mkdir -p /app
32+
COPY --from=builder /app /app
33+
RUN addgroup -gid "${GID}" --system django \
34+
&& adduser -uid "${UID}" -gid "${GID}" --home /home/django --system django \
2635
&& chown -R django:django /app
2736
USER django
28-
CMD ["python", "-m", "service"]
37+
WORKDIR /app
38+
CMD ["uv", "run", "-m", "email_relay.service"]

.dockerignore

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
1-
fly.toml
2-
.git/
3-
*.sqlite3
4-
.editorconfig
5-
.env*
6-
.gitignore
7-
.pre-commit-config.yaml
8-
.python-version
9-
.yarnrc.yml
10-
Dockerfile
11-
Justfile
12-
requirements.in
13-
__pycache__
1+
*
2+
3+
!src/
4+
!LICENSE
5+
!README.md
6+
!pyproject.toml
7+
!uv.lock

.github/workflows/release.yml

Lines changed: 20 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,69 +2,47 @@ name: release
22

33
on:
44
release:
5-
types: [released]
5+
types: [published]
66

77
jobs:
8-
check:
9-
runs-on: ubuntu-latest
10-
permissions:
11-
actions: read
12-
contents: read
13-
env:
14-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
15-
steps:
16-
- uses: actions/checkout@v4
17-
18-
- name: Check most recent test run on `main`
19-
id: latest-test-result
20-
run: |
21-
echo "result=$(gh run list \
22-
--branch=main \
23-
--workflow=test.yml \
24-
--json headBranch,workflowName,conclusion \
25-
--jq '.[] | select(.headBranch=="main" and .conclusion=="success") | .conclusion' \
26-
| head -n 1)" >> $GITHUB_OUTPUT
27-
28-
- name: OK
29-
if: ${{ (contains(steps.latest-test-result.outputs.result, 'success')) }}
30-
run: exit 0
31-
32-
- name: Fail
33-
if: ${{ !contains(steps.latest-test-result.outputs.result, 'success') }}
34-
run: exit 1
8+
test:
9+
uses: ./.github/workflows/test.yml
10+
secrets: inherit
3511

3612
pypi:
37-
if: ${{ github.event_name == 'release' }}
3813
runs-on: ubuntu-latest
39-
needs: check
14+
needs: test
4015
environment: release
4116
permissions:
42-
contents: read
17+
contents: write
4318
id-token: write
4419
steps:
45-
- uses: actions/checkout@v4
46-
with:
47-
persist-credentials: false
48-
49-
- uses: westerveltco/setup-ci-action@v0
20+
- name: Install uv
21+
uses: astral-sh/setup-uv@v5
5022
with:
51-
python-version: 3.12
52-
extra-python-dependencies: hatch
53-
use-uv: true
23+
enable-cache: true
24+
pyproject-file: pyproject.toml
5425

5526
- name: Build package
5627
run: |
57-
hatch build
28+
uv build
29+
30+
- name: Upload release assets to GitHub
31+
env:
32+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
33+
run: |
34+
gh release upload ${{ github.event.release.tag_name }} ./dist/*
5835
5936
- name: Publish to PyPI
60-
uses: pypa/gh-action-pypi-publish@release/v1
37+
run: |
38+
uv publish
6139
6240
docker:
6341
runs-on: ubuntu-latest
6442
needs: check
6543
environment: release
6644
permissions:
67-
contents: read
45+
contents: write
6846
packages: write
6947
steps:
7048
- uses: actions/checkout@v4

.github/workflows/test.yml

Lines changed: 45 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,16 @@ jobs:
2020
matrix: ${{ steps.set-matrix.outputs.matrix }}
2121
steps:
2222
- uses: actions/checkout@v4
23-
with:
24-
persist-credentials: false
2523

26-
- uses: westerveltco/setup-ci-action@v0
24+
- name: Install uv
25+
uses: astral-sh/setup-uv@v5
2726
with:
28-
python-version: 3.9
29-
extra-python-dependencies: nox
30-
use-uv: true
27+
enable-cache: true
28+
pyproject-file: pyproject.toml
3129

3230
- id: set-matrix
3331
run: |
34-
echo "matrix=$(python -m nox -l --json | jq -c '[.[] | select(.name == "tests") | {"python-version": .python, "django-version": .call_spec.django}] | {include: .}')" >> $GITHUB_OUTPUT
32+
uv run nox --session "gha_matrix"
3533
3634
test:
3735
name: Python ${{ matrix.python-version }}, Django ${{ matrix.django-version }}
@@ -42,18 +40,16 @@ jobs:
4240
matrix: ${{ fromJSON(needs.generate-matrix.outputs.matrix) }}
4341
steps:
4442
- uses: actions/checkout@v4
45-
with:
46-
persist-credentials: false
4743

48-
- uses: westerveltco/setup-ci-action@v0
44+
- name: Install uv
45+
uses: astral-sh/setup-uv@v5
4946
with:
50-
python-version: ${{ matrix.python-version }}
51-
extra-python-dependencies: nox
52-
use-uv: true
47+
enable-cache: true
48+
pyproject-file: pyproject.toml
5349

5450
- name: Run tests
5551
run: |
56-
python -m nox --session "tests(python='${{ matrix.python-version }}', django='${{ matrix.django-version }}')"
52+
uv run nox --session "tests(python='${{ matrix.python-version }}', django='${{ matrix.django-version }}')"
5753
5854
tests:
5955
runs-on: ubuntu-latest
@@ -71,32 +67,51 @@ jobs:
7167
runs-on: ubuntu-latest
7268
steps:
7369
- uses: actions/checkout@v4
74-
with:
75-
persist-credentials: false
7670

77-
- uses: westerveltco/setup-ci-action@v0
71+
- name: Install uv
72+
uses: astral-sh/setup-uv@v5
7873
with:
79-
python-version: 3.9
80-
extra-python-dependencies: nox
81-
use-uv: true
74+
enable-cache: true
75+
pyproject-file: pyproject.toml
8276

83-
- name: Run mypy
77+
- name: Run type checks
8478
run: |
85-
python -m nox --session "mypy"
79+
uv run nox --session "mypy"
8680
8781
coverage:
8882
runs-on: ubuntu-latest
8983
steps:
9084
- uses: actions/checkout@v4
91-
with:
92-
persist-credentials: false
9385

94-
- uses: westerveltco/setup-ci-action@v0
86+
- name: Install uv
87+
uses: astral-sh/setup-uv@v5
9588
with:
96-
python-version: 3.9
97-
extra-python-dependencies: nox
98-
use-uv: true
89+
enable-cache: true
90+
pyproject-file: pyproject.toml
91+
92+
- name: Generate code coverage
93+
run: |
94+
uv run nox --session "coverage"
9995
100-
- name: Run coverage
96+
docker:
97+
runs-on: ubuntu-latest
98+
steps:
99+
- uses: actions/checkout@v4
100+
101+
- name: Set up Docker Buildx
102+
uses: docker/setup-buildx-action@v3
103+
104+
- name: Build Docker image
105+
uses: docker/build-push-action@v6
106+
with:
107+
context: .
108+
file: .dockerfiles/Dockerfile
109+
load: true
110+
tags: django-email-relay-test:latest
111+
push: false
112+
cache-from: type=gha
113+
cache-to: type=gha,mode=max
114+
115+
- name: Run container and check
101116
run: |
102-
python -m nox --session "coverage"
117+
docker run --rm --name test-container django-email-relay-test:latest uv run -m email_relay.service --help

.just/copier.just

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
set unstable := true
2+
3+
justfile := justfile_directory() + "/.just/copier.just"
4+
5+
[private]
6+
default:
7+
@just --list --justfile {{ justfile }}
8+
9+
[private]
10+
fmt:
11+
@just --fmt --justfile {{ justfile }}
12+
13+
# Create a copier answers file
14+
[no-cd]
15+
copy TEMPLATE_PATH DESTINATION_PATH=".":
16+
uv run copier copy --trust {{ TEMPLATE_PATH }} {{ DESTINATION_PATH }}
17+
18+
# Recopy the project from the original template
19+
[no-cd]
20+
recopy ANSWERS_FILE *ARGS:
21+
uv run copier recopy --trust --answers-file {{ ANSWERS_FILE }} {{ ARGS }}
22+
23+
# Loop through all answers files and recopy the project using copier
24+
[no-cd]
25+
@recopy-all *ARGS:
26+
for file in `ls .copier/`; do just copier recopy .copier/$file "{{ ARGS }}"; done
27+
28+
# Update the project using a copier answers file
29+
[no-cd]
30+
update ANSWERS_FILE *ARGS:
31+
uv run copier update --trust --answers-file {{ ANSWERS_FILE }} {{ ARGS }}
32+
33+
# Loop through all answers files and update the project using copier
34+
[no-cd]
35+
@update-all *ARGS:
36+
for file in `ls .copier/`; do just copier update .copier/$file "{{ ARGS }}"; done

.just/docker.just

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
set unstable := true
2+
3+
justfile := justfile_directory() + "/.just/docker.just"
4+
5+
[private]
6+
default:
7+
@just --list --justfile {{ justfile }}
8+
9+
[private]
10+
fmt:
11+
@just --fmt --justfile {{ justfile }}
12+
13+
[no-cd]
14+
build:
15+
docker build --file .dockerfiles/Dockerfile --tag docker-email-relay:local .
16+
17+
[no-cd]
18+
run *ARGS: build
19+
docker run --rm docker-email-relay:local {{ ARGS }}
20+
21+
[no-cd]
22+
smoke:
23+
@just docker run uv run -m email_relay.service --help

.just/documentation.just

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
set unstable := true
2+
3+
justfile := justfile_directory() + "/.just/documentation.just"
4+
5+
[private]
6+
default:
7+
@just --list --justfile {{ justfile }}
8+
9+
[private]
10+
fmt:
11+
@just --fmt --justfile {{ justfile }}
12+
13+
# Build documentation using Sphinx
14+
[no-cd]
15+
build LOCATION="docs/_build/html": cog
16+
uv run --group docs sphinx-build docs {{ LOCATION }}
17+
18+
# Serve documentation locally
19+
[no-cd]
20+
serve PORT="8000": cog
21+
#!/usr/bin/env sh
22+
HOST="localhost"
23+
if [ -f "/.dockerenv" ]; then
24+
HOST="0.0.0.0"
25+
fi
26+
uv run --group docs sphinx-autobuild docs docs/_build/html --host "$HOST" --port {{ PORT }}
27+
28+
[no-cd]
29+
[private]
30+
cog:
31+
uv run --with cogapp cog -r CONTRIBUTING.md docs/development/just.md

0 commit comments

Comments
 (0)