Skip to content

Commit cf5527b

Browse files
committed
BREAKING CHANGE!: Switch to feature rich template
1 parent b6a4daf commit cf5527b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

88 files changed

+1418
-293
lines changed

.github/workflows/tests.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: Testing app
2+
3+
on: push
4+
5+
jobs:
6+
lint:
7+
strategy:
8+
matrix:
9+
cmd:
10+
- black
11+
- ruff
12+
- mypy
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
- name: Install poetry
17+
run: pipx install poetry
18+
- name: Set up Python
19+
uses: actions/setup-python@v5
20+
with:
21+
python-version: '3.11'
22+
cache: 'poetry'
23+
- name: Install deps
24+
run: poetry install
25+
- name: Run lint check
26+
run: poetry run pre-commit run -a ${{ matrix.cmd }}
27+
pytest:
28+
runs-on: ubuntu-latest
29+
steps:
30+
- uses: actions/checkout@v4
31+
- name: Create .env
32+
run: touch .env
33+
- name: Set up Python
34+
uses: actions/setup-python@v5
35+
with:
36+
python-version: '3.11'
37+
- name: Update docker-compose
38+
uses: KengoTODA/actions-setup-docker-compose@v1
39+
with:
40+
version: "2.28.0"
41+
- name: run tests
42+
run: docker-compose run --rm api pytest -vv

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,4 +140,3 @@ dmypy.json
140140

141141
# Cython debug symbols
142142
cython_debug/
143-
/.qodo/

Dockerfile

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
# Start from a slim version of Python 3.13
22
FROM python:3.13-slim AS base
3+
COPY --from=ghcr.io/astral-sh/uv:0.6.4 /uv /uvx /bin/
34

4-
# Set Python Envs
5-
ENV APP_HOME=/app/ PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 UV_COMPILE_BYTECODE=1
5+
# Set Envs
6+
ENV APP_HOME=/app
7+
8+
ENV PYTHONDONTWRITEBYTECODE=1 \
9+
PYTHONUNBUFFERED=1 \
10+
UV_COMPILE_BYTECODE=1 \
11+
PATH="$APP_HOME/.venv/bin:$PATH" \
12+
UV_LINK_MODE=copy \
13+
PYTHONPATH=$APP_HOME
614

715
WORKDIR $APP_HOME
816

@@ -15,36 +23,19 @@ RUN apt-get update \
1523
wget \
1624
&& pip install --prefer-binary --no-cache-dir --upgrade pip \
1725
&& apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \
18-
&& rm -rf /var/lib/apt/lists/* \
19-
20-
# Install uv
21-
# Ref: https://docs.astral.sh/uv/guides/integration/docker/#installing-uv
22-
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
23-
24-
# Place executables in the environment at the front of the path
25-
# Ref: https://docs.astral.sh/uv/guides/integration/docker/#using-the-environment
26-
ENV PATH="/app/.venv/bin:$PATH" UV_LINK_MODE=copy PYTHONPATH=$APP_HOME
26+
&& rm -rf /var/lib/apt/lists/*
2727

2828
# ===== Prod stage =====
2929
FROM base AS prod
3030

31-
# Install base dependencies
32-
# Ref: https://docs.astral.sh/uv/guides/integration/docker/#intermediate-layers
33-
RUN --mount=type=cache,target=/root/.cache/uv \
34-
--mount=type=bind,source=uv.lock,target=uv.lock \
35-
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
36-
uv sync --locked --no-install-project --no-dev
31+
COPY pyproject.toml uv.lock ./
3732

38-
COPY ./pyproject.toml ./uv.lock $APP_HOME
39-
40-
COPY ./src $APP_HOME/src
41-
42-
# Sync the project
4333
RUN --mount=type=cache,target=/root/.cache/uv \
4434
uv sync --locked --no-dev
4535

46-
# Run app - exec form (doesn’t start a shell on its own)
47-
CMD ["/usr/local/bin/python", "-m", "main.py"]
36+
COPY ./app ./$APP_HOME
37+
38+
CMD ["python", "-m", "app"]
4839

4940
# ===== Dev stage =====
5041
FROM prod AS dev

README.md

Lines changed: 55 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# FastAPI_super_template
1+
# app
22

33
This project was generated using fastapi_template.
44

@@ -11,7 +11,7 @@ To run the project use this set of commands:
1111

1212
```bash
1313
poetry install
14-
poetry run python -m src
14+
poetry run python -m app
1515
```
1616

1717
This will start the server on the configured host.
@@ -46,10 +46,13 @@ docker-compose build
4646
## Project structure
4747

4848
```bash
49-
$ tree "FastAPI_super_template"
50-
src
49+
$ tree "app"
50+
app
5151
├── conftest.py # Fixtures for all tests.
52-
├── main.py # Startup script. Starts uvicorn.
52+
├── db # module contains db configurations
53+
│   ├── dao # Data Access Objects. Contains different classes to interact with database.
54+
│   └── models # Package contains different models for ORMs.
55+
├── __main__.py # Startup script. Starts uvicorn.
5356
├── services # Package for different external services such as rabbit or redis etc.
5457
├── settings.py # Main configuration settings for project.
5558
├── static # Static content.
@@ -68,18 +71,18 @@ This application can be configured with environment variables.
6871
You can create `.env` file in the root directory and place all
6972
environment variables here.
7073

71-
All environment variables should start with "FASTAPI_SUPER_TEMPLATE_" prefix.
74+
All environment variables should start with "APP_" prefix.
7275

73-
For example if you see in your "FastAPI_super_template/settings.py" a variable named like
74-
`random_parameter`, you should provide the "FASTAPI_SUPER_TEMPLATE_RANDOM_PARAMETER"
76+
For example if you see in your "app/settings.py" a variable named like
77+
`random_parameter`, you should provide the "APP_RANDOM_PARAMETER"
7578
variable to configure the value. This behaviour can be changed by overriding `env_prefix` property
76-
in `FastAPI_super_template.settings.Settings.Config`.
79+
in `app.settings.Settings.Config`.
7780

7881
An example of .env file:
7982
```bash
80-
FASTAPI_SUPER_TEMPLATE_RELOAD="True"
81-
FASTAPI_SUPER_TEMPLATE_PORT="8000"
82-
FASTAPI_SUPER_TEMPLATE_ENVIRONMENT="dev"
83+
APP_RELOAD="True"
84+
APP_PORT="8000"
85+
APP_ENVIRONMENT="dev"
8386
```
8487

8588
You can read more about BaseSettings class here: https://pydantic-docs.helpmanual.io/usage/settings/
@@ -134,7 +137,40 @@ If you haven't pushed to docker registry yet, you can build image locally.
134137

135138
```bash
136139
docker-compose build
137-
docker save --output src.tar src:latest
140+
docker save --output app.tar app:latest
141+
```
142+
143+
## Migrations
144+
145+
If you want to migrate your database, you should run following commands:
146+
```bash
147+
# To run all migrations until the migration with revision_id.
148+
alembic upgrade "<revision_id>"
149+
150+
# To perform all pending migrations.
151+
alembic upgrade "head"
152+
```
153+
154+
### Reverting migrations
155+
156+
If you want to revert migrations, you should run:
157+
```bash
158+
# revert all migrations up to: revision_id.
159+
alembic downgrade <revision_id>
160+
161+
# Revert everything.
162+
alembic downgrade base
163+
```
164+
165+
### Migration generation
166+
167+
To generate migrations you should run:
168+
```bash
169+
# For automatic change detection.
170+
alembic revision --autogenerate
171+
172+
# For empty file generation.
173+
alembic revision
138174
```
139175

140176

@@ -148,6 +184,12 @@ docker-compose down
148184
```
149185

150186
For running tests on your local machine.
187+
1. you need to start a database.
188+
189+
I prefer doing it with docker:
190+
```
191+
docker run -p "5432:5432" -e "POSTGRES_PASSWORD=app" -e "POSTGRES_USER=app" -e "POSTGRES_DB=app" postgres:16.3-bullseye
192+
```
151193

152194

153195
2. Run the pytest.

alembic.ini

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
[alembic]
2+
script_location = app/db/migrations
3+
file_template = %%(year)d-%%(month).2d-%%(day).2d-%%(hour).2d-%%(minute).2d_%%(rev)s
4+
prepend_sys_path = .
5+
output_encoding = utf-8
6+
# truncate_slug_length = 40
7+
8+
9+
[post_write_hooks]
10+
hooks = black,ruff
11+
12+
black.type = console_scripts
13+
black.entrypoint = black
14+
15+
ruff.type = exec
16+
ruff.executable = ruff
17+
ruff.options = check --fix REVISION_SCRIPT_FILENAME --ignore N999
18+
19+
# Logging configuration
20+
[loggers]
21+
keys = root,sqlalchemy,alembic
22+
23+
[handlers]
24+
keys = console
25+
26+
[formatters]
27+
keys = generic
28+
29+
[logger_root]
30+
level = WARN
31+
handlers = console
32+
qualname =
33+
34+
[logger_sqlalchemy]
35+
level = WARN
36+
handlers =
37+
qualname = sqlalchemy.engine
38+
39+
[logger_alembic]
40+
level = INFO
41+
handlers =
42+
qualname = alembic
43+
44+
[handler_console]
45+
class = StreamHandler
46+
args = (sys.stderr,)
47+
level = NOTSET
48+
formatter = generic
49+
50+
[formatter_generic]
51+
format = %(levelname)-5.5s [%(name)s] %(message)s
52+
datefmt = %H:%M:%S

app/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""app package."""

src/main.py renamed to app/__main__.py

Lines changed: 11 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,15 @@
44

55
import uvicorn
66

7-
from src.gunicorn_runner import GunicornApplication
8-
from src.settings import settings
7+
from app.settings import settings
98

109

1110
def set_multiproc_dir() -> None:
1211
"""
1312
Sets mutiproc_dir env variable.
1413
1514
This function cleans up the multiprocess directory
16-
and recreates it. This actions are required by prometheus-client
15+
and recreates it. Thous actions are required by prometheus-client
1716
to share metrics between processes.
1817
1918
After cleanup, it sets two variables.
@@ -36,30 +35,15 @@ def set_multiproc_dir() -> None:
3635
def main() -> None:
3736
"""Entrypoint of the application."""
3837
set_multiproc_dir()
39-
if settings.reload:
40-
uvicorn.run(
41-
"src.web.application:get_app",
42-
workers=settings.workers_count,
43-
host=settings.host,
44-
port=settings.port,
45-
reload=settings.reload,
46-
log_level=settings.log_level.value.lower(),
47-
factory=True,
48-
)
49-
else:
50-
# We choose gunicorn only if reload
51-
# option is not used, because reload
52-
# feature doesn't work with gunicorn workers.
53-
GunicornApplication(
54-
"src.web.application:get_app",
55-
host=settings.host,
56-
port=settings.port,
57-
workers=settings.workers_count,
58-
factory=True,
59-
accesslog="-",
60-
loglevel=settings.log_level.value.lower(),
61-
access_log_format='%r "-" %s "-" %Tf',
62-
).run()
38+
uvicorn.run(
39+
"app.web.application:get_app",
40+
workers=settings.workers_count,
41+
host=settings.host,
42+
port=settings.port,
43+
reload=settings.reload,
44+
log_level=settings.log_level.value.lower(),
45+
factory=True,
46+
)
6347

6448

6549
if __name__ == "__main__":

app/db/__init__.py

Whitespace-only changes.

app/db/base.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from sqlalchemy.orm import DeclarativeBase
2+
3+
from app.db.meta import meta
4+
5+
6+
class Base(DeclarativeBase):
7+
"""Base for all models."""
8+
9+
metadata = meta

app/db/dao/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""DAO classes."""

0 commit comments

Comments
 (0)