Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
- [x] Verify that the copilot-instructions.md file in the .github directory is created.

- [x] Clarify Project Requirements
Python FastAPI backend monorepo using pyenv, pip, and venv, with no frontend tooling.
Python FastAPI backend monorepo using uv workspaces, with no frontend tooling.

- [x] Scaffold the Project
Created a monorepo layout with apps/, common/python/, docs/, scripts/, root setup files, and a sample FastAPI service.
Expand Down
23 changes: 9 additions & 14 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,27 +48,22 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
- name: Install uv
uses: astral-sh/setup-uv@v5
with:
python-version-file: .python-version

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements-dev.txt
# TODO - install is very slow
# for f in apps/*/requirements.txt; do pip install -r "$f"; done
- name: Sync workspace
run: uv sync --all-packages --all-groups

- name: Ruff format check
if: needs.detect-changes.outputs.lint_dirs != ''
run: ruff format --check ${{ needs.detect-changes.outputs.lint_dirs }}
run: uv run ruff format --check ${{ needs.detect-changes.outputs.lint_dirs }}

- name: Ruff lint
if: needs.detect-changes.outputs.lint_dirs != ''
run: ruff check ${{ needs.detect-changes.outputs.lint_dirs }}
run: uv run ruff check ${{ needs.detect-changes.outputs.lint_dirs }}

# TODO - when slow install is resolved
# - name: Run tests
# if: needs.detect-changes.outputs.test_dirs != ''
# run: pytest ${{ needs.detect-changes.outputs.test_dirs }}
- name: Run tests
if: needs.detect-changes.outputs.test_dirs != ''
run: uv run pytest ${{ needs.detect-changes.outputs.test_dirs }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ __pycache__/
*.pyc
*.pyo
*.pyd
*.egg-info/
.coverage
htmlcov/
.env
Expand Down
17 changes: 5 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# pfmt

Python backend monorepo for FastAPI development using `pyenv`, `pip`, and `venv`.
Python backend monorepo for FastAPI development using `uv` workspaces.

## Read Me First

- Contributors: read `.github/copilot-instructions.md` for workspace-specific project guidance.
- Developers: use `.python-version`, a local `.venv`, and `pip` requirements files for dependency management.
- Developers: use `.python-version` and `uv` workspaces (`pyproject.toml` per package) for dependency management.
- For template design principles, see this [reference](https://github.com/ais-one/cookbook?tab=readme-ov-file#1---important---read-me-first).

## Template Maintenance
Expand Down Expand Up @@ -48,16 +48,9 @@ source .venv/bin/activate
If you prefer to bootstrap manually:

```bash
pyenv install -s $(cat .python-version)
pyenv local $(cat .python-version)
python3 -m venv .venv
uv python install $(cat .python-version)
uv sync --all-packages --all-groups
source .venv/bin/activate
python -m pip install --upgrade pip
pip install -r requirements-dev.txt -r apps/app_sample/requirements.txt

for file in apps/*/requirements.txt; do
pip install -r "$file"
done
```

## Run The Sample Service
Expand Down Expand Up @@ -97,7 +90,7 @@ git config core.hooksPath .githooks

## Linting and Formatting

This project uses [Ruff](https://docs.astral.sh/ruff/) for linting and formatting (installed via `requirements-dev.txt`).
This project uses [Ruff](https://docs.astral.sh/ruff/) for linting and formatting (installed via the `dev` dependency group in `pyproject.toml`).

Check for issues:

Expand Down
16 changes: 16 additions & 0 deletions apps/app_admin/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[project]
name = "app-admin"
version = "0.1.0"
description = "Admin FastAPI service."
requires-python = "==3.12.8"
dependencies = [
"fastapi==0.115.12",
"uvicorn[standard]==0.34.1",
"common-python",
]

[tool.uv]
package = false

[tool.uv.sources]
common-python = { workspace = true }
2 changes: 0 additions & 2 deletions apps/app_admin/requirements.txt

This file was deleted.

16 changes: 16 additions & 0 deletions apps/app_sample/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[project]
name = "app-sample"
version = "0.1.0"
description = "Sample FastAPI service."
requires-python = "==3.12.8"
dependencies = [
"fastapi==0.115.12",
"uvicorn[standard]==0.34.1",
"common-python",
]

[tool.uv]
package = false

[tool.uv.sources]
common-python = { workspace = true }
2 changes: 0 additions & 2 deletions apps/app_sample/requirements.txt

This file was deleted.

19 changes: 19 additions & 0 deletions common/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[project]
name = "common-python"
version = "0.1.0"
description = "Shared Python modules for backend services."
requires-python = "==3.12.8"
dependencies = [
"fastapi>=0.115",
"pydantic>=2",
]

[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"

[tool.setuptools]
packages = ["common.python"]

[tool.setuptools.package-dir]
"common.python" = "python"
3 changes: 2 additions & 1 deletion common/python/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

__version__ = "0.1.0"

from common.python.config import env_bool, env_int, env_str, get_environment
from common.python.config import env_bool, env_float, env_int, env_str, get_environment
from common.python.logger import get_logger
from common.python.models import (
EchoRequest,
Expand All @@ -20,6 +20,7 @@
"api_url",
"create_test_client",
"env_bool",
"env_float",
"env_int",
"env_str",
"get_environment",
Expand Down
7 changes: 7 additions & 0 deletions common/python/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ def env_bool(name: str, default: bool) -> bool:
return value.lower() in {"1", "true", "yes", "on"}


def env_float(name: str, default: float) -> float:
value = os.getenv(name)
if value is None:
return default
return float(value)


def env_int(name: str, default: int) -> int:
value = os.getenv(name)
if value is None:
Expand Down
22 changes: 22 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,25 @@
[project]
name = "pfmt"
version = "0.0.0"
description = "Python backend monorepo root."
requires-python = "==3.12.8"
dependencies = []

[dependency-groups]
dev = [
"pytest==8.3.5",
"httpx==0.28.1",
"ruff==0.11.5",
"commitizen==3.29.0",
]

[tool.uv]
package = false

[tool.uv.workspace]
members = ["common", "apps/*"]
exclude = ["apps/__pycache__"]

[tool.ruff]
target-version = "py312"
line-length = 88
Expand Down
4 changes: 0 additions & 4 deletions requirements-dev.txt

This file was deleted.

58 changes: 15 additions & 43 deletions scripts/bootstrap.sh
Original file line number Diff line number Diff line change
@@ -1,58 +1,30 @@
#!/usr/bin/env bash
# Bootstrap the repo with uv.
#
# - Installs the Python version declared in .python-version (via uv's
# managed Python) if missing.
# - Creates .venv at the repo root and installs every workspace member
# (common, apps/*) plus the dev dependency group (ruff, pytest,
# commitizen, httpx) into it.
# - Wires up .githooks.
set -euo pipefail

ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
PYTHON_VERSION="$(cat "$ROOT_DIR/.python-version")"

cd "$ROOT_DIR"

version_matches() {
python3 - "$PYTHON_VERSION" <<'PY'
import re
import sys

expected = sys.argv[1]
current = sys.version.split()[0]
pattern = re.compile(r"^\d+\.\d+\.\d+$")

if not pattern.match(expected):
raise SystemExit(1)

raise SystemExit(0 if current == expected else 1)
PY
}

if command -v pyenv >/dev/null 2>&1; then
if pyenv versions --bare | grep -Fxq "$PYTHON_VERSION"; then
pyenv local "$PYTHON_VERSION"
elif version_matches; then
echo "python3 already matches $PYTHON_VERSION; skipping pyenv install"
else
echo "Attempting to install Python $PYTHON_VERSION via pyenv..."
if pyenv install -s "$PYTHON_VERSION"; then
pyenv local "$PYTHON_VERSION"
else
echo "pyenv install failed; continuing with the available python3 interpreter" >&2
fi
fi
fi

if ! version_matches; then
echo "python3 $(python3 --version 2>&1 | awk '{print $2}') does not match required version $PYTHON_VERSION" >&2
echo "Install the requested version with pyenv or update .python-version before bootstrapping." >&2
if ! command -v uv >/dev/null 2>&1; then
echo "uv is required but not installed. See: https://docs.astral.sh/uv/getting-started/installation/" >&2
exit 1
fi

python3 -m venv .venv
. .venv/bin/activate
python -m pip install --upgrade pip
pip install -r requirements-dev.txt
PYTHON_VERSION="$(cat "$ROOT_DIR/.python-version")"
uv python install "$PYTHON_VERSION"

for requirements_file in apps/*/requirements.txt; do
pip install -r "$requirements_file"
done
echo "Syncing workspace (.venv) with all members and dev group..."
uv sync --all-packages --all-groups

chmod +x .githooks/pre-commit .githooks/pre-push
git config core.hooksPath .githooks

echo
echo "Bootstrap complete. Activate with: source .venv/bin/activate"
Loading
Loading