Skip to content
Closed
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
5 changes: 4 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@ DATABASE__HOSTNAME=localhost
DATABASE__USERNAME=rDGJeEDqAz
DATABASE__PASSWORD=XsPQhCoEfOQZueDjsILetLDUvbvSxAMnrVtgVZpmdcSssUgbvs
DATABASE__PORT=5455
DATABASE__DB=default_db
DATABASE__DB=default_db

PROMETHEUS__ENABLED=true
PROMETHEUS__ADDR="127.0.0.1"
4 changes: 2 additions & 2 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
version: 2
updates:
- package-ecosystem: pip
- package-ecosystem: uv
directory: /
schedule:
interval: monthly
open-pull-requests-limit: 5
open-pull-requests-limit: 1
allow:
- dependency-type: "all"
groups:
Expand Down
33 changes: 12 additions & 21 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,36 +22,27 @@ jobs:
ports:
- 5432:5432
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5

- name: Set up Python
uses: actions/setup-python@v5
- name: "Set up Python"
uses: actions/setup-python@v6
with:
python-version: "3.13.1"
python-version-file: "pyproject.toml"

- name: Install Poetry
uses: snok/install-poetry@v1
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
virtualenvs-create: true
virtualenvs-in-project: false
virtualenvs-path: /opt/venv
version: "0.9.2"
enable-cache: true

- name: Load cached venv
id: cached-poetry-dependencies
uses: actions/cache@v4
with:
path: /opt/venv
key: venv-${{ runner.os }}-python-3.13.1-${{ hashFiles('poetry.lock') }}

- name: Install dependencies and actiavte virtualenv
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
run: |
poetry install --no-interaction --no-root
- name: Install the project
run: uv sync --locked --all-extras --dev
shell: bash

- name: Run tests
env:
SECURITY__JWT_SECRET_KEY: very-not-secret
DATABASE__HOSTNAME: localhost
DATABASE__PASSWORD: postgres
run: |
poetry run pytest
uv run pytest
39 changes: 12 additions & 27 deletions .github/workflows/type_check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,38 +14,23 @@ jobs:

runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5

- name: Set up Python
uses: actions/setup-python@v5
- name: "Set up Python"
uses: actions/setup-python@v6
with:
python-version: "3.13.1"
python-version-file: "pyproject.toml"

- name: Install Poetry
uses: snok/install-poetry@v1
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
virtualenvs-create: true
virtualenvs-in-project: true
version: "0.9.2"
enable-cache: true

- name: Install Poetry
uses: snok/install-poetry@v1
with:
virtualenvs-create: true
virtualenvs-in-project: false
virtualenvs-path: /opt/venv

- name: Load cached venv
id: cached-poetry-dependencies
uses: actions/cache@v4
with:
path: /opt/venv
key: venv-${{ runner.os }}-python-3.13.1-${{ hashFiles('poetry.lock') }}

- name: Install dependencies and actiavte virtualenv
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
run: |
poetry install --no-interaction --no-root
- name: Install the project
run: uv sync --locked --all-extras --dev
shell: bash

- name: Run ${{ matrix.check }}
run: |
poetry run ${{ matrix.check }} .
uv run ${{ matrix.check }} .
8 changes: 4 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
rev: v6.0.0
hooks:
- id: check-yaml

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.12
rev: v0.14.14
hooks:
- id: ruff-format

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.12
rev: v0.14.14
hooks:
- id: ruff
- id: ruff-check
args: [--fix]
19 changes: 10 additions & 9 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
FROM python:3.13.5-slim-bookworm AS base
FROM python:3.14-slim-trixie AS base

ENV PYTHONUNBUFFERED=1
WORKDIR /build

# Create requirements.txt file
FROM base AS poetry
RUN pip install poetry==2.1.3
RUN poetry self add poetry-plugin-export
COPY poetry.lock pyproject.toml ./
RUN poetry export -o /requirements.txt --without-hashes
FROM base AS uv
COPY --from=ghcr.io/astral-sh/uv:0.9.2 /uv /uvx /bin/
COPY uv.lock pyproject.toml ./
RUN uv export --no-dev --no-hashes -o /requirements.txt --no-install-workspace --frozen
RUN uv export --only-group dev --no-hashes -o /requirements-dev.txt --no-install-workspace --frozen

FROM base AS final
COPY --from=poetry /requirements.txt .
COPY --from=uv /requirements.txt .

# Create venv, add it to path and install requirements
RUN python -m venv /venv
Expand All @@ -28,8 +28,9 @@ COPY alembic.ini .
COPY pyproject.toml .
COPY init.sh .

# Expose port
# Expose port 8000 for app and optional 9090 for prometheus metrics
EXPOSE 8000
EXPOSE 9090

# Make the init script executable
RUN chmod +x ./init.sh
Expand All @@ -39,4 +40,4 @@ ENTRYPOINT ["./init.sh"]

# Set CMD to uvicorn
# /venv/bin/uvicorn is used because from entrypoint script PATH is new
CMD ["/venv/bin/uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "2", "--loop", "uvloop"]
CMD ["/venv/bin/uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "1", "--loop", "uvloop"]
34 changes: 34 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
BIND_PORT ?= 8000
BIND_HOST ?= localhost

.PHONY: help
help: ## Print this help message
grep -E '^[\.a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

.env: ## Ensure there is env file or create one
echo "No .env file found. Want to create it from .env.example? [y/n]" && read answer && if [ $${answer:-'N'} = 'y' ]; then cp .env.example .env;fi

.PHONY: local-setup
local-setup: ## Setup local postgres database
docker compose up -d

.PHONY: up
up: local-setup ## Run FastAPI development server
uv run alembic upgrade head
uv run uvicorn app.main:app --reload --host $(BIND_HOST) --port $(BIND_PORT)

.PHONY: run
run: up ## Alias for `up`

.PHONY: down
down: ## Stop database
docker compose down

.PHONY: test
test: local-setup ## Run unit tests
uv run pytest .

.PHONY: lint
lint: local-setup ## Run all linters
uv run pre-commit run -a
uv run mypy .
13 changes: 6 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ _Check out online example: https://minimal-fastapi-postgres-template.rafsaf.pl,
- [Features](#features)
- [Quickstart](#quickstart)
- [1. Create repository from a template](#1-create-repository-from-a-template)
- [2. Install dependecies with Poetry](#2-install-dependecies-with-poetry)
- [2. Install dependecies with uv](#2-install-dependecies-with-uv)
- [3. Setup database and migrations](#3-setup-database-and-migrations)
- [4. Now you can run app](#4-now-you-can-run-app)
- [5. Activate pre-commit](#5-activate-pre-commit)
Expand All @@ -38,7 +38,7 @@ _Check out online example: https://minimal-fastapi-postgres-template.rafsaf.pl,
- [x] Full [Alembic](https://alembic.sqlalchemy.org/en/latest/) migrations setup
- [x] Refresh token endpoint (not only access like in official template)
- [x] Ready to go Dockerfile with [uvicorn](https://www.uvicorn.org/) webserver as an example
- [x] [Poetry](https://python-poetry.org/docs/), `mypy`, `pre-commit` hooks with [ruff](https://github.com/astral-sh/ruff)
- [x] [uv](https://docs.astral.sh/uv/getting-started/installation/), `mypy`, `pre-commit` hooks with [ruff](https://github.com/astral-sh/ruff)
- [x] Perfect pytest asynchronous test setup with +40 tests and full coverage

<br>
Expand All @@ -55,16 +55,15 @@ _Check out online example: https://minimal-fastapi-postgres-template.rafsaf.pl,

See [docs](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-repository-from-a-template).

### 2. Install dependecies with [Poetry](https://python-poetry.org/docs/)
### 2. Install dependecies with [uv](https://docs.astral.sh/uv/getting-started/installation/)

```bash
cd your_project_name

### Poetry install (python3.13)
poetry install
uv sync
```

Note, be sure to use `python3.13` with this template with either poetry or standard venv & pip, if you need to stick to some earlier python version, you should adapt it yourself (remove new versions specific syntax for example `str | int` for python < 3.10)
Note, be sure to use `python3.13` with this template with either uv or standard venv & pip, if you need to stick to some earlier python version, you should adapt it yourself (remove new versions specific syntax for example `str | int` for python < 3.10)

### 3. Setup database and migrations

Expand Down Expand Up @@ -373,7 +372,7 @@ There are some **opinionated** default settings in `/app/main.py` for documentat
```python
app = FastAPI(
title="minimal fastapi postgres template",
version="6.1.0",
version="7.0.0",
description="https://github.com/rafsaf/minimal-fastapi-postgres-template",
openapi_url="/openapi.json",
docs_url="/",
Expand Down
2 changes: 1 addition & 1 deletion alembic.ini
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ truncate_slug_length = 40
# version_path_separator = ;
# version_path_separator = space
version_path_separator = os # Use os.pathsep. Default configuration used for new projects.

path_separator=os
# set to 'true' to search source files recursively
# in each "version_locations" directory
# new in Alembic version 1.10
Expand Down
22 changes: 16 additions & 6 deletions alembic/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,12 @@
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
from app.models import Base # noqa
from app.core.models import Base # noqa

target_metadata = Base.metadata

# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
# import other models here
import app.auth.models # noqa


def get_database_uri() -> str:
Expand Down Expand Up @@ -93,4 +91,16 @@ async def run_migrations_online() -> None:
if context.is_offline_mode():
run_migrations_offline()
else:
asyncio.run(run_migrations_online())
try:
loop: asyncio.AbstractEventLoop | None = asyncio.get_running_loop()
except RuntimeError:
loop = None

if loop and loop.is_running():
# pytest-asyncio or other test runner is running the event loop
# so we need to use run_coroutine_threadsafe
future = asyncio.run_coroutine_threadsafe(run_migrations_online(), loop)
future.result(timeout=15)
else:
# no event loop is running, safe to use asyncio.run
asyncio.run(run_migrations_online())
Loading