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
18 changes: 12 additions & 6 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
/tests
.flake8
.tox
.git
.github/
.venv
__pycache__/
*.py[cod]
tests/
docs/
reports/
*.db
.pytest_cache
.ruff_cache
.coverage
.idea
.pre-commit-config.yaml
LICENSE
mypy.ini
.mypy_cache
.pytest_cache
46 changes: 17 additions & 29 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
# This workflow installs dependencies, lints, type-checks, and tests the project
# with a single version of Python provisioned by uv from .python-version.
# For more information see:
# https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python

name: Build

on:
push:
branches: [ '*' ]
paths:
- 'src/**'
- 'tests/**'

branches: [ main ]
pull_request:
branches: [ "main", "dev" ]
paths:
- 'src/**'
- 'tests/**'
branches: [ '**' ]

permissions:
contents: read
Expand All @@ -25,25 +20,18 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v4
uses: astral-sh/setup-uv@v5
with:
enable-cache: true
- name: Set up Python 3.11
uses: actions/setup-python@v3
with:
python-version: "3.11"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
make dev
- name: Lint
run: |
make lint
- name: Test
run: |
make cover
- name: Build Image
run: |
docker build . --file Dockerfile --tag template:PR-${{ github.event.number }}
run: make dev
- name: Lint and type-check
run: make lint
- name: ADR registry check
run: make adr-check
- name: Test with coverage gate
run: make cover
- name: Build image
run: docker build . --file Dockerfile --tag template:${{ github.sha }}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ local_settings.py
db.sqlite3
db.sqlite3-journal

# SQLite databases (default DATABASE_URL writes ./cosmic-fastapi.db)
*.db

# Flask stuff:
instance/
.webassets-cache
Expand Down
2 changes: 1 addition & 1 deletion .python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.11.4
3.13
60 changes: 37 additions & 23 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,43 +1,57 @@
FROM python:3.11.4-slim AS builder
FROM python:3.13-slim AS builder

ARG APP_DIR=/app

ARG ENV

ENV ENV=${ENV} \
PYTHONUNBUFFERED=1 \
ENV PYTHONUNBUFFERED=1 \
PYTHONHASHSEED=random \
PYTHONFAULTHANDLER=1 \
UV_PYTHON=python3.11 \
UV_COMPILE_BYTE=1 \
UV_LINK_MODE=copy \
UVICORN_PORT=8000 \
UVICORN_HOST=0.0.0.0 \
UVICORN_RELOAD=0
UV_PYTHON=python3.13 \
UV_COMPILE_BYTECODE=1 \
UV_LINK_MODE=copy

# Install uv.
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/

WORKDIR ${APP_DIR}

# Copy dependency definition files
# Copy dependency definition files.
COPY pyproject.toml uv.lock .python-version README.md ${APP_DIR}/

# Build the virtual environment from the lock file, excluding dev dependencies
# This creates a self-contained .venv directory
# Install dependencies
# Build the virtual environment from the lock file, excluding dev dependencies.
# This creates a self-contained .venv directory.
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --frozen --no-install-project --no-dev


# Copy the project into the image
# Copy the project into the image and sync it.
COPY src ${APP_DIR}/src

# Sync the project
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-dev

# Set the command to run the application using the Python from the venv
CMD ["uv" , "run", "python", "-m", "template.main"]

FROM python:3.13-slim AS runtime

ARG APP_DIR=/app

ENV PYTHONUNBUFFERED=1 \
PYTHONHASHSEED=random \
PYTHONFAULTHANDLER=1 \
PATH="${APP_DIR}/.venv/bin:$PATH" \
UVICORN_PORT=8000 \
UVICORN_HOST=0.0.0.0 \
UVICORN_RELOAD=0

# Create an unprivileged application user.
RUN groupadd --system app && useradd --system --gid app --home-dir ${APP_DIR} app

WORKDIR ${APP_DIR}

# Copy the prebuilt virtual environment and the application source.
COPY --from=builder --chown=app:app ${APP_DIR}/.venv ${APP_DIR}/.venv
COPY --from=builder --chown=app:app ${APP_DIR}/src ${APP_DIR}/src

USER app

EXPOSE 8000

# Run the application using the Python from the prebuilt venv.
CMD ["python", "-m", "template.main"]
16 changes: 6 additions & 10 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,25 @@
.PHONY: clean
clean: ## Removes all build and test artifacts
rm -f .coverage
rm -rf .mypy_cache
rm -f *.db
rm -rf .pytest_cache
rm -rf .ruff_cache
rm -rf dist
rm -rf reports
rm -f requirements.txt
rm -rf $(SSAP_DIR)
find . -type d -name __pycache__ -exec rm -rf {} +

.PHONY: dist-clean
dist-clean: clean ## Removes all build and test artifacts and virtual environment
rm -rf .venv

.PHONY: install
install: ## Install dependencies
uv sync
install: ## Install runtime dependencies
uv sync --no-dev

.PHONY: dev
dev: ## Install dev dependencies
dev: ## Install runtime and dev dependencies
uv sync --dev

.PHONY: build
build: ## Creates a virtual environment
uv venv

.PHONY: test
test: ## Executes tests cases
uv run pytest
Expand Down
38 changes: 28 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,33 @@ extensions such as optimistic locking, broker adapters, and the transactional ou

Variables prefixed with `FASTAPI_` are used to configure the API UI.

| Name | Description | Default Value |
|-----------------------------|---------------------|------------------|
| FASTAPI_DEBUG | Debug Mode | False |
| FASTAPI_PROJECT_NAME | Swagger Title | API GATEWAY |
| FASTAPI_PROJECT_DESCRIPTION | Swagger Description | ... |
| FASTAPI_PROJECT_LICENSE | License info | ... |
| FASTAPI_PROJECT_CONTACT | Contact details | ... |
| FASTAPI_VERSION | Application Version | template.version |
| FASTAPI_DOCS_URL | Swagger Endpoint | /docs |
| Name | Description | Default Value |
|------------------------------|---------------------------------------------|--------------------------------------------------|
| FASTAPI_DEBUG | Debug Mode | False |
| FASTAPI_PROJECT_NAME | Swagger Title | Cosmic FastAPI |
| FASTAPI_PROJECT_DESCRIPTION | Swagger Description | This is a FastAPI template demo. |
| FASTAPI_PROJECT_LICENSE | License info (JSON object, see below) | `{"name": "MIT", "url": "..."}` |
| FASTAPI_PROJECT_CONTACT | Contact details (JSON object, see below) | `{"name": "Tomas Sanchez", "url": "...", ...}` |
| FASTAPI_VERSION | Application Version | from package metadata (`pyproject.toml`) |
| FASTAPI_DOCS_URL | Swagger Endpoint | /docs |
| FASTAPI_BACKEND_CORS_ORIGINS | Allowed CORS origins (JSON list) | `["http://localhost:3000", "http://localhost:8000"]` |

`FASTAPI_PROJECT_LICENSE` and `FASTAPI_PROJECT_CONTACT` parse into `LicenseInfo` and `ContactInfo` models, so they must
be provided as JSON objects:

```bash
FASTAPI_PROJECT_LICENSE='{"name": "MIT", "url": "https://mit-license.org/"}'
FASTAPI_PROJECT_CONTACT='{"name": "Tomas Sanchez", "url": "https://tomsanchez.com.ar", "email": "info@tomsanchez.com.ar"}'
```

`FASTAPI_BACKEND_CORS_ORIGINS` is a JSON list of allowed origins. The CORS middleware disables credentials whenever the
list contains the `"*"` wildcard, since reflecting any origin together with credentials is an unsafe posture:

```bash
FASTAPI_BACKEND_CORS_ORIGINS='["https://app.example.com", "https://admin.example.com"]'
```

`FASTAPI_VERSION` defaults to the installed package version (single source of truth in `pyproject.toml`).

Variables prefixed with `UVICORN_` are used to configure the server.

Expand Down Expand Up @@ -311,7 +329,7 @@ pip install uv
1. Clone the repository

```bash
git clone "git@github.com/tomasanchez/cosmic-fastapi.git"
git clone "git@github.com:tomasanchez/cosmic-fastapi.git"
```
2. Install dependencies

Expand Down
30 changes: 20 additions & 10 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
services:
app:
# This tells Docker Compose to build the image from the Dockerfile
# in the current directory.
# Build the image from the Dockerfile in the current directory.
build: .
# Name the container for easier identification.
container_name: cosmic-fastapi-app
# This maps port 8000 on your local machine to port 8000 inside the container,
# allowing you to access the running application at http://localhost:8000.
# Map port 8000 on the host to port 8000 inside the container so the app is
# reachable at http://localhost:8000.
ports:
- "8000:8000"
# This mounts your local 'src' folder into the container at '/app/src'.
# This is the key to live-reloading: changes you make to your code locally
# will be immediately reflected inside the container.
# Mount the local 'src' folder into the container so code changes are
# reflected for Uvicorn's live-reload.
volumes:
- ./src:/app/src
# Override environment variables defined in the Dockerfile.
# Here, we enable Uvicorn's auto-reload feature for development.
# Persist the SQLite database file across container recreation.
- app-data:/app/data
environment:
- ENV=development
# Enable Uvicorn auto-reload for local development.
- UVICORN_RELOAD=true
# Store the SQLite database on the persistent named volume.
- DATABASE_URL=sqlite+pysqlite:///./data/cosmic-fastapi.db
# Create the schema at startup so the demo works on first run.
# Production deployments must use Alembic migrations (make migrate)
# instead of auto-creating the schema.
- DATABASE_AUTO_CREATE_SCHEMA=true
# Allow the bundled frontend / local tools to call the API in development.
- FASTAPI_BACKEND_CORS_ORIGINS=["http://localhost:8000"]

volumes:
app-data:
Loading
Loading