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
13 changes: 10 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ default_install_hook_types: [pre-commit, commit-msg]
default_stages: [commit]

repos:
# https://github.com/pre-commit/pre-commit-hooks/tags
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
Expand All @@ -18,11 +19,12 @@ repos:
- id: end-of-file-fixer
- id: mixed-line-ending

# https://github.com/astral-sh/ruff-pre-commit/tags
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.4
rev: v0.12.3
hooks:
- id: ruff-format
- id: ruff
- id: ruff-check
args: ["--fix"]

- repo: local
Expand All @@ -35,28 +37,33 @@ repos:
types: [python]
args: ["--config-file=.mypy.ini"]

# https://github.com/p re-commit/mirrors-prettier/tags
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.1.0
hooks:
- id: prettier
stages: [commit]

# https://github.com/jorisroovers/gitlint/tags
- repo: https://github.com/jorisroovers/gitlint
rev: v0.19.1
hooks:
- id: gitlint
stages: [commit-msg]

# https://github.com/crate-ci/typos/tags
- repo: https://github.com/crate-ci/typos
rev: v1.31.1
rev: v1.34.0
hooks:
- id: typos

# https://github.com/shellcheck-py/shellcheck-py/tags
- repo: https://github.com/shellcheck-py/shellcheck-py
rev: v0.10.0.1
hooks:
- id: shellcheck

# https://github.com/AleksaC/hadolint-py/tags
- repo: https://github.com/AleksaC/hadolint-py
rev: v2.12.0.3
hooks:
Expand Down
33 changes: 24 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ _NOTE: use `-d` flag to run containers in the background._

#### 💻 Development Environment

1. Navigate to the project directory via `cd <project_name>`;
2. Copy [_.env.example_](.env.example) to _.env_ file via `cp .env.example .env`;
3. Fill environment variables listed in _.env_ with relevant values;
4. Create docker network via `docker network create <project_name>-network`;
5. Run the project via `docker compose -f dev.docker-compose.yml up`.

_NOTE: use `-d` flag to run containers in the background._

#### 🚀 Local Development Environment

1. Install uv if needed via `curl -LsSf https://astral.sh/uv/install.sh | sh`;
2. Navigate to the project directory via `cd <project_name>`;
3. Create a virtual environment via `uv venv`;
Expand All @@ -48,7 +58,7 @@ _NOTE: use `-d` flag to run containers in the background._
6. Initialize pre-commit environment and install pre-commit hooks via `pre-commit install`;
7. Copy [_.env.example_](.env.example) to _.env_ file via `cp .env.example .env`;
8. Fill environment variables listed in _.env_ with relevant values;
9. Run the project via `docker compose -f dev.docker-compose.yml up`.
9. Run the project via `docker compose -f local.docker-compose.yml up`.

_NOTE: use `-d` flag to run containers in the background._

Expand Down Expand Up @@ -96,7 +106,7 @@ The template uses a Manager pattern to implement business logic. The `BaseManage

```python
# Create a manager for a specific model.
class UserManager(BaseManager[User, UserRepository, UserCreate, UserUpdate]):
class UserManager(BaseManager[User, UserCreate, UserUpdate]):
pass


Expand All @@ -114,17 +124,18 @@ new_user = await user_manager.create(user_data, session)

### 🐳 Docker

This template provides Docker configuration for both development and production environments:
This template provides Docker configuration for three different environments:

1. **Development** - Uses [_dev.docker-compose.yml_](dev.docker-compose.yml) and [_dev.Dockerfile_](dev.Dockerfile) with hot-reloading for faster development.
2. **Production** - Uses [_prod.docker-compose.yml_](prod.docker-compose.yml) and [_prod.Dockerfile_](prod.Dockerfile) optimized for production use.
1. **Local Development** - Uses [_local.docker-compose.yml_](local.docker-compose.yml) and [_local.Dockerfile_](local.Dockerfile) with hot-reloading and direct port access for fastest development.
2. **Development with Proxy** - Uses [_dev.docker-compose.yml_](dev.docker-compose.yml) and [_dev.Dockerfile_](dev.Dockerfile) with hot-reloading, Traefik proxy, SSL certificates, and domain routing.
3. **Production** - Uses [_prod.docker-compose.yml_](prod.docker-compose.yml) and [_prod.Dockerfile_](prod.Dockerfile) optimized for production use.

The Docker setup includes:

- FastAPI application;
- PostgreSQL database;
- Redis for caching;
- Traefik application proxy.
- Traefik application proxy (dev/prod environments);

### 📖 API Documentation

Expand Down Expand Up @@ -229,8 +240,8 @@ The FastAPI application instance setup, including routers registration and start

Project root:

- `dev.Dockerfile` / `prod.Dockerfile`: separate Dockerfiles for development and production environments;
- `dev.docker-compose.yml` / `prod.docker-compose.yml`: Docker Compose configurations for spinning up development and production environments;
- `local.Dockerfile` / `dev.Dockerfile` / `prod.Dockerfile`: separate Dockerfiles for local development, development with proxy, and production environments;
- `local.docker-compose.yml` / `dev.docker-compose.yml` / `prod.docker-compose.yml`: Docker Compose configurations for spinning up different environments;
- `pyproject.toml`: project dependencies and configuration using the UV package manager;
- `uv.lock`: lockfile for exact dependency versions;
- `entrypoint.sh`: entrypoint script used when the application runs inside a Docker container;
Expand All @@ -246,12 +257,13 @@ Project root:
│ ├── alembic.ini
│ ├── database
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── engine.py
│ │ ├── migrations
│ │ │ ├── env.py
│ │ │ ├── script.py.mako
│ │ │ └── versions
│ │ │ └── 2025_04_06_...
│ │ │ └── ...
│ │ └── models.py
│ ├── entrypoint.sh
│ ├── exceptions
Expand All @@ -275,6 +287,7 @@ Project root:
│ │ └── user.py
│ ├── schemas
│ │ ├── __init__.py
│ │ ├── base.py
│ │ └── user.py
│ ├── settings.py
│ └── utils
Expand All @@ -289,6 +302,8 @@ Project root:
│ └── types.py
├── dev.Dockerfile
├── dev.docker-compose.yml
├── local.Dockerfile
├── local.docker-compose.yml
├── prod.Dockerfile
├── prod.docker-compose.yml
├── pyproject.toml
Expand Down
18 changes: 18 additions & 0 deletions app/database/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from datetime import datetime

from sqlalchemy import DateTime
from sqlalchemy.orm import Mapped, declarative_base, mapped_column

Base = declarative_base()
metadata = Base.metadata


class BaseDBModel(Base):
__abstract__ = True

id: Mapped[int] = mapped_column(primary_key=True, index=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)

def __repr__(self) -> str:
return f"<{self.__class__.__name__} (id={self.id})>"
4 changes: 0 additions & 4 deletions app/database/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,10 @@
async_sessionmaker,
create_async_engine,
)
from sqlalchemy.orm import declarative_base

from app.exceptions.database import DatabaseInitializationError
from app.settings import settings

Base = declarative_base()
metadata = Base.metadata


class DatabaseSessionManager:
def __init__(self, db_url: str, engine_kwargs: dict[str, Any] | None = None) -> None:
Expand Down
2 changes: 1 addition & 1 deletion app/database/migrations/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from sqlalchemy.ext.asyncio.engine import create_async_engine
from sqlalchemy.future import Connection

from app.database.engine import metadata
from app.database.base import metadata
from app.database.models import * # noqa: F403
from app.settings import settings

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""add-user-table

Revision ID: 6786a8aedf8f
Revision ID: e00141056a92
Revises:
Create Date: 2025-04-06 16:14:29.743402
Create Date: 2025-07-14 12:43:38.293435

"""

Expand All @@ -12,7 +12,7 @@
from alembic import op

# revision identifiers, used by Alembic.
revision: str = "6786a8aedf8f"
revision: str = "e00141056a92"
down_revision: str | None = None
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
Expand All @@ -22,11 +22,11 @@ def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"user",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.Column("updated_at", sa.DateTime(), nullable=False),
sa.Column("email", sa.String(length=100), nullable=False),
sa.Column("password", sa.String(length=100), nullable=False),
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_user_email"), "user", ["email"], unique=True)
Expand Down
11 changes: 3 additions & 8 deletions app/database/models.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
from datetime import datetime

from sqlalchemy import DateTime, Integer, String
from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column

from app.database.engine import Base
from app.database.base import BaseDBModel


class User(Base):
class User(BaseDBModel):
__tablename__ = "user"

id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
email: Mapped[str] = mapped_column(String(100), unique=True, index=True)
password: Mapped[str] = mapped_column(String(100), nullable=False)
16 changes: 10 additions & 6 deletions app/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
#! /usr/bin/env bash
echo "Waiting for the database initialization... "
sleep 5;

if [ "$ENV" = "local" ] || [ "$ENV" = "dev" ]; then
echo "Waiting for the database initialization... "
sleep 5;
fi

echo "Running migrations... "
alembic upgrade head

echo "Starting project... "

if [ "$ENV" = "dev" ]; then
if [ "$ENV" = "local" ]; then
uvicorn main:app --loop uvloop --reload --log-level debug --use-colors --timeout-graceful-shutdown 5 --host 0.0.0.0 --port 80
elif [ "$ENV" = "prod" ]; then
uvicorn main:app --loop uvloop --log-level info --use-colors --timeout-graceful-shutdown 5 --port 80
elif [ "$ENV" = "dev" ]; then
uvicorn main:app --loop uvloop --reload --log-level debug --timeout-graceful-shutdown 5 --host 0.0.0.0 --port 80
else
uvicorn main:app --loop uvloop --log-level info --timeout-graceful-shutdown 5 --host 0.0.0.0 --port 80
fi
Loading