Skip to content

Commit d8eb2c4

Browse files
gr8monk3ysclaude
andcommitted
feat(deps): migrate backend packaging from pip/Poetry to uv
Replace the dual-manifest setup (Poetry-format pyproject.toml that nothing installed from + requirements.txt that everything installed from) with a single PEP 621 pyproject.toml and a committed uv.lock: - pyproject.toml: PEP 621 [project] with the requirements.txt ranges as truth (Poetry file was missing tenacity, the SSO stack, and cryptography), PEP 735 dev dependency group, [tool.uv] package=false (the API is an app, not an installable package). Dead [tool.black] and [tool.isort] sections removed — pre-commit already enforces ruff + ruff-format; [tool.ruff] added (line-length 88, py312). - uv.lock: first real lockfile for the backend (85 packages). - requirements.txt: deleted. - ci.yml: SHA-pinned astral-sh/setup-uv + `uv sync --locked`; .venv/bin goes on GITHUB_PATH so existing bare pytest invocations are untouched. - security.yml: pip-audit now audits `uv export --locked --no-dev` — the dependency set that actually ships, not a loose manifest. - Dockerfile.backend: uv-based builder (uv 0.10.0, UV_COMPILE_BYTECODE, UV_PYTHON_DOWNLOADS=never); the `pip uninstall pip` hack is gone since uv venvs contain no pip. - dependabot.yml: pip -> uv ecosystem for /apps/api, same groups. - package.json test:api:* scripts and docs updated to `uv run`. - .python-version pins 3.12 to match CI and Docker. Verified: full backend suite 1254 passed / 33 skipped / 11 xfailed under uv + Python 3.12; Docker image builds and serves {"ready":true} on /ready; uv export + pip-audit runs clean (no known vulnerabilities). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 638dc06 commit d8eb2c4

12 files changed

Lines changed: 2205 additions & 158 deletions

File tree

.github/dependabot.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
version: 2
22
updates:
3-
# Backend Python dependencies
4-
- package-ecosystem: "pip"
3+
# Backend Python dependencies (uv.lock + pyproject.toml)
4+
- package-ecosystem: "uv"
55
directory: "/apps/api"
66
schedule:
77
interval: "weekly"

.github/workflows/ci.yml

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,16 @@ jobs:
3232
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
3333
with:
3434
python-version: ${{ env.PYTHON_VERSION }}
35-
cache: pip
36-
cache-dependency-path: apps/api/requirements.txt
35+
36+
- name: Set up uv
37+
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
38+
with:
39+
enable-cache: true
3740

3841
- name: Install dependencies
3942
run: |
40-
python -m pip install --upgrade pip
41-
pip install -r requirements.txt pytest pytest-asyncio pytest-cov
43+
uv sync --locked
44+
echo "$PWD/.venv/bin" >> "$GITHUB_PATH"
4245
4346
- name: Run smoke suite (blocking)
4447
run: |
@@ -119,13 +122,16 @@ jobs:
119122
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
120123
with:
121124
python-version: ${{ env.PYTHON_VERSION }}
122-
cache: pip
123-
cache-dependency-path: apps/api/requirements.txt
125+
126+
- name: Set up uv
127+
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
128+
with:
129+
enable-cache: true
124130

125131
- name: Install dependencies
126132
run: |
127-
python -m pip install --upgrade pip
128-
pip install -r requirements.txt pytest pytest-asyncio
133+
uv sync --locked
134+
echo "$PWD/.venv/bin" >> "$GITHUB_PATH"
129135
130136
- name: Run full backend tests
131137
run: pytest -q
@@ -352,13 +358,16 @@ jobs:
352358
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
353359
with:
354360
python-version: ${{ env.PYTHON_VERSION }}
355-
cache: pip
356-
cache-dependency-path: apps/api/requirements.txt
361+
362+
- name: Set up uv
363+
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
364+
with:
365+
enable-cache: true
357366

358367
- name: Install dependencies
359368
run: |
360-
python -m pip install --upgrade pip
361-
pip install -r requirements.txt pytest pytest-asyncio
369+
uv sync --locked
370+
echo "$PWD/.venv/bin" >> "$GITHUB_PATH"
362371
363372
# Apply db/migrations so the asyncpg-backed services have their tables.
364373
- name: Apply db/migrations

.github/workflows/security.yml

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,15 @@ jobs:
2929
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
3030
with:
3131
python-version: ${{ env.PYTHON_VERSION }}
32-
cache: pip
33-
cache-dependency-path: apps/api/requirements.txt
3432

35-
- name: Install dependencies
36-
run: |
37-
python -m pip install --upgrade pip
38-
pip install -r requirements.txt pip-audit
33+
- name: Set up uv
34+
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
35+
36+
- name: Export locked runtime dependencies
37+
run: uv export --locked --no-dev --no-hashes --format requirements-txt -o /tmp/requirements-audit.txt
3938

4039
- name: Run pip-audit
41-
run: pip-audit --strict --desc
40+
run: uvx --python "$PYTHON_VERSION" pip-audit --strict --desc --no-deps -r /tmp/requirements-audit.txt
4241

4342
bun-audit:
4443
name: Frontend Dependency Audit

CLAUDE.md

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,10 @@ bun run audit:runtime # Runtime audit policy
2727

2828
```bash
2929
cd apps/api
30-
python -m venv .venv && source .venv/bin/activate
31-
pip install -r requirements.txt
32-
python server.py # Start FastAPI on :8000
33-
pytest -q # Run all backend tests
34-
pytest tests/test_ci_smoke.py -q # Quick smoke tests only
30+
uv sync # Install deps into .venv from uv.lock
31+
uv run python server.py # Start FastAPI on :8000
32+
uv run pytest -q # Run all backend tests
33+
uv run pytest tests/test_ci_smoke.py -q # Quick smoke tests only
3534
```
3635

3736
From repo root:
@@ -50,10 +49,10 @@ cd apps/web && bunx vitest run tests/lib/api.test.ts
5049
cd apps/web && bunx playwright test e2e/navigation.spec.ts
5150

5251
# Backend (pytest) — single file
53-
cd apps/api && pytest tests/test_blog.py -q
52+
cd apps/api && uv run pytest tests/test_blog.py -q
5453

5554
# Backend — single test function
56-
cd apps/api && pytest tests/test_blog.py::test_function_name -q
55+
cd apps/api && uv run pytest tests/test_blog.py::test_function_name -q
5756
```
5857

5958
### Pre-PR checks
@@ -133,9 +132,9 @@ The frontend uses `NEXT_PUBLIC_API_URL` (default `http://localhost:8000` in dev)
133132

134133
## Conventions
135134

136-
- **Package manager**: Bun for the frontend workspace. Python pip/poetry for the backend.
135+
- **Package manager**: Bun for the frontend workspace. **uv** for the backend (`apps/api/pyproject.toml` + `uv.lock` are the single source of truth; there is no requirements.txt).
137136
- **Commit messages**: Conventional prefixes (`feat:`, `fix:`, `docs:`, `chore:`).
138-
- **Python formatting**: Black (line-length 88) + isort (black profile) + Ruff. Pre-commit hooks enforce this.
137+
- **Python formatting/linting**: Ruff only (line-length 88, `ruff format` + `ruff check`, configured in `apps/api/pyproject.toml`). Black and isort are retired. Pre-commit runs the ruff hooks on changed files; note the repo carries pre-existing ruff debt in unchanged files (the required `precommit` CI check runs only hygiene hooks, not ruff).
139138
- **TypeScript linting**: ESLint flat config (`eslint.config.mjs`) with `@typescript-eslint`, Next.js core-web-vitals, and react-hooks rules. `no-explicit-any` is warn-level.
140139
- **Testing**: Vitest (jsdom) for frontend unit tests, Playwright for E2E, pytest for backend. Frontend coverage uses `all: true` (measures every source file) with a **ratchet floor** set to the current real baseline (~10%) that only moves up toward the branches 70% / functions+lines+statements 85% target — never lower the thresholds to make a build pass (see `docs/REMEDIATION_PLAN.md`).
141140
- **Turbopack**: Default bundler in dev. E2E tests use `--webpack` flag for stability.

CONTRIBUTING.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,8 @@ Start the backend:
2222

2323
```bash
2424
cd apps/api
25-
python -m venv .venv
26-
source .venv/bin/activate
27-
pip install -r requirements.txt
28-
python server.py
25+
uv sync # installs Python 3.12 deps into .venv from uv.lock
26+
uv run python server.py
2927
```
3028

3129
Start the web app from the repository root:
@@ -50,7 +48,7 @@ For backend changes, also run:
5048

5149
```bash
5250
bun run test:api:smoke
53-
cd apps/api && pytest -q
51+
cd apps/api && uv run pytest -q
5452
```
5553

5654
For UI or user-flow changes, also run:

Dockerfile.backend

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
1414
build-essential \
1515
&& rm -rf /var/lib/apt/lists/*
1616

17-
RUN python -m venv /opt/venv
18-
ENV PATH="/opt/venv/bin:$PATH"
17+
COPY --from=ghcr.io/astral-sh/uv:0.10.0 /uv /uvx /bin/
18+
19+
# uv builds the venv at /opt/venv from the lockfile; it contains no pip at all.
20+
ENV UV_PROJECT_ENVIRONMENT=/opt/venv \
21+
UV_PYTHON_DOWNLOADS=never \
22+
UV_COMPILE_BYTECODE=1
1923

20-
COPY apps/api/requirements.txt ./
21-
RUN pip install --no-cache-dir --upgrade pip && \
22-
pip install --no-cache-dir -r requirements.txt && \
23-
pip uninstall -y pip
24+
COPY apps/api/pyproject.toml apps/api/uv.lock ./
25+
RUN uv sync --locked --no-dev
2426

2527
# -----------------------------------------------------------------------------
2628
# Stage 2: Runtime - lean production image

README.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,8 @@ Minimum local values:
7272

7373
```bash
7474
cd apps/api
75-
python -m venv .venv
76-
source .venv/bin/activate
77-
pip install -r requirements.txt
78-
python server.py
75+
uv sync # installs Python 3.12 deps into .venv from uv.lock
76+
uv run python server.py
7977
```
8078

8179
### 4. Start the web app

apps/api/.python-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.12

apps/api/pyproject.toml

Lines changed: 55 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,67 @@
1-
[tool.poetry]
1+
[project]
22
name = "blog-ai"
33
version = "0.1.0"
44
description = "AI-driven solution for generating blog posts and books"
5-
authors = ["Lorenzo"]
5+
authors = [{ name = "Lorenzo" }]
66
license = "AGPL-3.0-only"
77
readme = "README.md"
8+
requires-python = ">=3.12"
9+
dependencies = [
10+
"tenacity>=9.1.4",
11+
"openai>=2.15.0",
12+
"anthropic>=0.76.0",
13+
"google-generativeai>=0.3.1",
14+
"requests>=2.31.0",
15+
"pydantic>=2.5.2,<3.0.0",
16+
"pydantic-settings>=2.1.0",
17+
"python-dotenv>=1.0.0",
18+
# Image generation (Stability AI uses REST API via httpx, no SDK needed)
19+
"Pillow>=12.1.1",
20+
# Transitive dependency pins (Dependabot security alerts)
21+
"starlette>=0.47.2", # CVE: DoS via multipart/form-data (transitive via fastapi)
22+
"protobuf>=5.29.6", # CVE: JSON recursion depth bypass (transitive via google-generativeai)
23+
# Backend server
24+
"fastapi>=0.115.0,<1.0.0",
25+
"uvicorn>=0.27.0,<1.0.0",
26+
"websockets>=12.0",
27+
"python-multipart>=0.0.9",
28+
"uvloop>=0.19.0; platform_system != 'Windows'",
29+
# Postgres driver (Neon / managed Postgres)
30+
"asyncpg>=0.29.0",
31+
# Security
32+
"bcrypt>=4.0.0,<5.0.0",
33+
"nh3>=0.2.14",
34+
# SSO (Single Sign-On)
35+
"python3-saml>=1.16.0", # SAML 2.0 support
36+
"authlib>=1.7.2", # OIDC/OAuth2 support
37+
"httpx>=0.27.0,<1.0.0", # Async HTTP client for OIDC
38+
"PyJWT>=2.13.0", # JWT token validation
39+
"cryptography>=50.0.0,<51.0.0", # CVE fixes + certificate handling
40+
# Redis for job storage
41+
"redis>=5.0.0",
42+
# Payment processing
43+
"stripe>=7.0.0",
44+
# Monitoring and error tracking
45+
"sentry-sdk[fastapi]>=1.40.0",
46+
]
847

9-
[tool.poetry.dependencies]
10-
python = "^3.12"
11-
openai = "^2.15.0"
12-
anthropic = "^0.76.0"
13-
google-generativeai = ">=0.3.1"
14-
requests = "^2.31.0"
15-
pydantic = "^2.5.2"
16-
pydantic-settings = "^2.1.0"
17-
python-dotenv = "^1.0.0"
18-
fastapi = ">=0.115.0,<1.0.0"
19-
uvicorn = "^0.27.0"
20-
websockets = "^12.0"
21-
python-multipart = "^0.0.9"
22-
uvloop = { version = "^0.19.0", markers = "platform_system != 'Windows'" }
23-
24-
# Transitive dependency pins (security)
25-
starlette = ">=0.47.2" # CVE fix: DoS via multipart
26-
protobuf = ">=5.29.6" # CVE fix: JSON recursion bypass
27-
28-
# Database client (Neon / managed Postgres)
29-
asyncpg = "^0.29.0"
30-
31-
# Image generation (Stability AI uses REST API via httpx, no SDK needed)
32-
Pillow = ">=12.1.1"
33-
httpx = "^0.27.0"
34-
35-
# Security
36-
bcrypt = "^4.0.0"
37-
nh3 = "^0.2.14"
38-
39-
# Redis for job storage
40-
redis = "^5.0.0"
41-
42-
# Payment processing
43-
stripe = "^7.0.0"
44-
45-
# Monitoring and error tracking
46-
sentry-sdk = { version = "^1.40.0", extras = ["fastapi"] }
48+
[dependency-groups]
49+
dev = [
50+
"pytest>=8.2,<10",
51+
"pytest-asyncio>=1.3.0",
52+
"pytest-cov>=6.0.0",
53+
"mypy>=1.5.1",
54+
"ruff>=0.9.10",
55+
]
4756

48-
[tool.poetry.group.dev.dependencies]
49-
pytest = ">=8.2,<10"
50-
pytest-asyncio = "^1.3.0"
51-
black = "^24.3.0"
52-
isort = "^7.0.0"
53-
mypy = "^1.5.1"
57+
[tool.uv]
58+
# The API is an application (server.py + app/ + src/), not an installable
59+
# package — uv sync installs only the dependencies.
60+
package = false
5461

55-
[tool.black]
62+
[tool.ruff]
5663
line-length = 88
57-
target-version = ["py312"]
58-
59-
[tool.isort]
60-
profile = "black"
61-
line_length = 88
64+
target-version = "py312"
6265

6366
[tool.pytest.ini_options]
6467
testpaths = ["tests"]
@@ -68,7 +71,3 @@ markers = [
6871
"security: marks tests as security-related",
6972
"slow: marks tests as slow-running",
7073
]
71-
72-
[build-system]
73-
requires = ["poetry-core"]
74-
build-backend = "poetry.core.masonry.api"

apps/api/requirements.txt

Lines changed: 0 additions & 54 deletions
This file was deleted.

0 commit comments

Comments
 (0)