Skip to content

Commit 109310e

Browse files
committed
Switch to uv/ruff setup
- Use uv to manage dependencies - Move requirements into pyproject.toml and locked with uv.lock - Move tox.ini into pyproject.toml - Use ruff for code styles - Minor formatting changes caused by moving black -> ruff
1 parent 2e6a7c6 commit 109310e

File tree

21 files changed

+1022
-176
lines changed

21 files changed

+1022
-176
lines changed

.github/workflows/ci.yml

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,26 @@ on:
1111
jobs:
1212
build:
1313

14-
name: Unit tests and code checks
14+
name: Unit tests and code style checks
1515
runs-on: ubuntu-latest
1616

1717
steps:
18-
- uses: actions/checkout@v3
19-
- uses: actions/setup-python@v4
18+
- uses: actions/checkout@v5
19+
20+
# https://docs.astral.sh/uv/guides/integration/github/
21+
- uses: astral-sh/setup-uv@v4
22+
with:
23+
enable-cache: true
24+
25+
- uses: actions/setup-python@v6
2026
with:
21-
python-version: "3.14"
27+
python-version-file: "pyproject.toml"
28+
29+
- name: Install dependencies
30+
run: |
31+
uv sync --frozen --extra dev
32+
uv tool install tox --with tox-uv
2233
2334
- name: Run CI
2435
run: |
25-
pip install "tox>=4.0,<5.0"
26-
tox
36+
uv run tox

.pre-commit-config.yaml

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,27 @@
11
repos:
2-
- repo: https://github.com/pre-commit/pre-commit-hooks
3-
rev: v6.0.0
4-
hooks:
5-
- id: check-yaml
6-
- id: end-of-file-fixer
7-
- id: trailing-whitespace
8-
- repo: https://github.com/psf/black-pre-commit-mirror
9-
rev: 25.9.0
10-
hooks:
11-
- id: black
12-
# Since the pre-commit runs on a file by file basis rather than a whole project,
13-
# The excludes in pyproject.toml are ignored
14-
exclude: migrations
15-
language_version: python3.14
2+
- repo: https://github.com/pre-commit/pre-commit-hooks
3+
rev: v6.0.0
4+
hooks:
5+
- id: check-yaml
6+
- id: end-of-file-fixer
7+
- id: trailing-whitespace
8+
- repo: https://github.com/adamchainz/django-upgrade
9+
rev: "1.29.1"
10+
hooks:
11+
- id: django-upgrade
12+
args: [--target-version, "5.2"]
13+
- repo: https://github.com/astral-sh/ruff-pre-commit
14+
# Ruff version.
15+
rev: v0.14.6
16+
hooks:
17+
# Run the linter.
18+
- id: ruff
19+
args: [ --fix ]
20+
# Run the formatter.
21+
- id: ruff-format
22+
- repo: https://github.com/astral-sh/uv-pre-commit
23+
# uv version.
24+
rev: 0.9.11
25+
hooks:
26+
# Compile requirements to ensure uv.lock is up-to-date
27+
- id: uv-lock

Dockerfile

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ LABEL maintainer="https://github.com/sandiegopython"
99
ENV PYTHONDONTWRITEBYTECODE 1
1010
ENV PYTHONUNBUFFERED 1
1111

12+
# uv environment variables
13+
# Copy (don't hardlink) files into /.venv. Avoid issues with Docker's FS
14+
# https://docs.astral.sh/uv/reference/environment/
15+
ENV UV_LINK_MODE=copy
16+
ENV UV_PYTHON_DOWNLOADS=never
17+
ENV UV_PROJECT_ENVIRONMENT=/.venv
18+
1219
RUN apt-get update
1320
RUN apt-get install -y --no-install-recommends curl
1421

@@ -25,28 +32,40 @@ RUN apt-get install -y --no-install-recommends \
2532
postgresql-client libpq-dev \
2633
git
2734

28-
RUN mkdir -p /code
29-
35+
RUN mkdir -p /code /home/www/
3036
WORKDIR /code
3137

32-
# Requirements are installed here to ensure they will be cached.
33-
# https://docs.docker.com/build/cache/#use-the-dedicated-run-cache
34-
COPY ./requirements /requirements
35-
RUN pip install --upgrade pip
36-
RUN --mount=type=cache,target=/root/.cache/pip pip install -r /requirements/deployment.txt
37-
RUN --mount=type=cache,target=/root/.cache/pip pip install -r /requirements/local.txt
38+
# Install uv for fast package management
39+
# https://docs.astral.sh/uv/guides/integration/docker/#installing-uv
40+
COPY --from=ghcr.io/astral-sh/uv:0.9.11 /uv /uvx /bin/
41+
42+
# Copy project files for dependency resolution
43+
# uv.lock ensures reproducible builds
44+
COPY pyproject.toml uv.lock ./
45+
RUN --mount=type=cache,target=/root/.cache/uv \
46+
uv sync --frozen --no-install-project --all-extras
3847

3948
COPY . /code/
4049

4150
# Build JS/static assets
4251
RUN --mount=type=cache,target=/root/.npm npm install
4352
RUN npm run dist
4453

45-
RUN python manage.py collectstatic --noinput --clear
54+
RUN uv run python manage.py collectstatic --noinput --clear
55+
56+
# Launches the application (gunicorn) with this script
57+
COPY ./docker/start /start
58+
RUN chmod +x /start
59+
60+
# Launch a shell within the container with this script
61+
COPY ./docker/shell /shell
62+
RUN chmod +x /shell
4663

4764
# Run the container unprivileged
4865
RUN addgroup www && useradd -g www www
4966
RUN chown -R www:www /code
67+
# Needed for the uv cache
68+
RUN chown -R www:www /home/www
5069
USER www
5170

5271
# Output information about the build
@@ -56,4 +75,4 @@ RUN date -u +'%Y-%m-%dT%H:%M:%SZ' > BUILD_DATE
5675

5776
EXPOSE 8000
5877

59-
CMD ["gunicorn", "--timeout", "15", "--bind", ":8000", "--workers", "2", "--max-requests", "10000", "--max-requests-jitter", "100", "--log-file", "-", "--access-logfile", "-", "config.wsgi"]
78+
CMD ["/start"]

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ dockerserve:
3535
# or run anything else on the Django container. It does expect the
3636
# container to already be running
3737
dockershell:
38-
docker compose -f $(DOCKER_CONFIG) exec django /bin/bash
38+
docker compose -f $(DOCKER_CONFIG) run --rm django /shell
3939

4040
# Build and deploy the production container
4141
deploy:

README.md

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,28 @@ This is the repository for the San Diego Python website at [sandiegopython.org](
77

88
### Prerequisites
99

10-
* Python v3.14
10+
* [uv](https://docs.astral.sh) (see [uv installation](https://docs.astral.sh/uv/getting-started/installation/))
1111
* Node v20
1212

13+
1314
### Getting started
1415

16+
Install Python 3.14 with uv:
17+
18+
```shell
19+
uv python install
20+
```
21+
22+
Install dependencies and dev setup
23+
1524
```shell
16-
pip install -r requirements/local.txt # Install local Python requirements
17-
npm install # Install JS dependencies for frontend CSS/JS
18-
npm run build # Build CSS (continuously with `npm run watch`)
19-
pre-commit install # Setup code standard pre-commit hook
20-
./manage.py migrate # Create a local development database
21-
./manage.py createsuperuser # Create a local development administrator user
22-
./manage.py runserver # Starts a local development server at http://localhost:8000
25+
uv sync --all-extras # Install local Python requirements
26+
npm install # Install JS dependencies for frontend CSS/JS
27+
npm run build # Build CSS (continuously with `npm run watch`)
28+
uv run pre-commit install # Setup code standard pre-commit hook
29+
uv run ./manage.py migrate # Create a local development database
30+
uv run ./manage.py createsuperuser # Create a local development administrator user
31+
uv run ./manage.py runserver # Starts a local development server at http://localhost:8000
2332
```
2433

2534

@@ -28,6 +37,7 @@ pre-commit install # Setup code standard pre-commit hook
2837
The entire test suite can be run with tox:
2938

3039
```shell
40+
uv tool install tox --with tox-uv # Install tox-uv (only needs to be run once ever)
3141
tox
3242
```
3343

config/settings/base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
import dj_database_url
1515

16+
1617
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
1718
BASE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../..")
1819

config/urls.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
from django.conf import settings
2+
from django.conf.urls.static import static
23
from django.contrib import admin
34
from django.urls import include
45
from django.urls import path
5-
from django.conf.urls.static import static
66

77

88
urlpatterns = [

docker/shell

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/bin/bash
2+
3+
set -o errexit
4+
set -o pipefail
5+
set -o nounset
6+
7+
8+
# Use the venv created by `uv sync` in the Dockerfile
9+
source /.venv/bin/activate
10+
11+
# Start shell with the venv activated
12+
/bin/bash

docker/start

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/bin/bash
2+
3+
set -o errexit
4+
set -o pipefail
5+
set -o nounset
6+
7+
8+
# Launch gunicorn - the production web server
9+
# https://docs.gunicorn.org/en/stable/settings.html
10+
uv run gunicorn \
11+
--timeout 15 \
12+
--bind :8000 \
13+
--workers 2 \
14+
--max-requests 10000 \
15+
--max-requests-jitter 100 \
16+
--log-file - \
17+
--access-logfile - \
18+
config.wsgi

pyproject.toml

Lines changed: 128 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,129 @@
1-
# https://black.readthedocs.io/
2-
[tool.black]
1+
[project]
2+
name = "pythonsd-django"
3+
version = "0.2.0"
4+
description = "San Diego Python website - sandiegopython.org"
5+
requires-python = ">=3.14, <3.15"
6+
dependencies = [
7+
# This is a Django app
8+
"Django >= 5.2, < 5.3",
9+
10+
# A zero dependency WSGI server
11+
"gunicorn",
12+
13+
# For connecting to various APIs (eg. Meetup.com, YouTube)
14+
"requests",
15+
16+
# For serving static assets from the WSGI server
17+
"whitenoise",
18+
19+
# Redirects requests to other hosts to this one
20+
"django-enforce-host",
21+
22+
# For parsing YouTube's XML feed
23+
# DefusedXML is necessary to parse untrusted XML safely
24+
"defusedxml",
25+
26+
# Used for image field handling
27+
"pillow",
28+
29+
"dj-database-url",
30+
]
31+
32+
[project.optional-dependencies]
33+
# Development dependencies
34+
dev = [
35+
# Used for code formatting and linting
36+
"pre-commit",
37+
38+
# Used for code coverage
39+
"coverage",
40+
"django_coverage_plugin",
41+
42+
# Used in local testing
43+
"WebTest",
44+
"beautifulsoup4",
45+
"WebOb==1.8.9",
46+
"responses",
47+
48+
# Used to help with Django related debugging
49+
"django-debug-toolbar",
50+
51+
# Used to run the test suite
52+
"tox",
53+
]
54+
55+
# Production dependencies
56+
production = [
57+
# Database server
58+
# https://www.psycopg.org/docs/install.html#install-from-source
59+
"psycopg[binary]",
60+
61+
# Email
62+
"django-anymail",
63+
64+
# Cloud storage for media storage (S3, R2, etc.)
65+
"django-storages[s3]",
66+
]
67+
68+
69+
# https://docs.astral.sh/ruff/configuration/
70+
[tool.ruff]
71+
exclude = [
72+
"manage.py",
73+
"docs",
74+
]
75+
76+
# Same as Black.
377
line-length = 88
4-
target_version = ['py310']
5-
include = '\.pyi?$'
6-
exclude = '''
7-
/(
8-
\.git
9-
| \.hg
10-
| \.mypy_cache
11-
| \.tox
12-
| \.venv
13-
| venv
14-
| _build
15-
| buck-out
16-
| build
17-
| dist
18-
| node_modules
19-
| migrations
20-
)/
21-
'''
78+
indent-width = 4
79+
80+
# Assume Python 3.14
81+
target-version = "py314"
82+
83+
[tool.ruff.lint]
84+
# Enable:
85+
# Pyflakes (`F`)
86+
# pycodestyle (`E`) error codes.
87+
# isort (`I`) import sorting
88+
select = ["E4", "E7", "E9", "F", "I"]
89+
90+
[tool.ruff.lint.isort]
91+
# https://docs.astral.sh/ruff/settings/#lintisort
92+
force-single-line = true
93+
case-sensitive = false
94+
lines-after-imports = 2
95+
96+
# Ignore `F405` (import *) in config files
97+
[tool.ruff.lint.per-file-ignores]
98+
"config/settings/*" = ["F405"]
99+
100+
101+
# Run all tests with `tox`
102+
[tool.tox]
103+
# https://tox.wiki/en/latest/config.html#pyproject-toml-native
104+
env_list = ["coverage", "styles", "migrations"]
105+
setenv = { DJANGO_SETTINGS_MODULE = "config.settings.test", DJANGO_SETTINGS_SKIP_LOCAL = "True" }
106+
107+
[tool.tox.env_run_base]
108+
description = "Run the full test suite under {base_python}"
109+
runner = "uv-venv-lock-runner"
110+
base_python = ["python3.14"]
111+
extras = [
112+
"dev",
113+
]
114+
115+
[tool.tox.env.coverage]
116+
description = "Run full unit test suite with coverage"
117+
commands = [
118+
["uv", "run", "coverage", "run", "manage.py", "test"],
119+
["uv", "run", "coverage", "html"],
120+
["uv", "run", "coverage", "report", "--show-missing", "--fail-under=100"],
121+
]
122+
123+
[tool.tox.env.styles]
124+
description = "Run type check on code base"
125+
commands = [["uv", "run", "pre-commit", "run", "--all-files"]]
126+
127+
[tool.tox.env.migrations]
128+
description = "Check for missing migrations"
129+
commands = [["uv", "run", "python", "manage.py", "makemigrations", "--check", "--dry-run"]]

0 commit comments

Comments
 (0)