From c6d3c78e0cc7665ffb12378c315f16004b1689ab Mon Sep 17 00:00:00 2001 From: Artem Tsyrulnikov Date: Tue, 23 Dec 2025 10:43:01 +0300 Subject: [PATCH] feat: updated CLAUDE.md --- CLAUDE.md | 73 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 58 insertions(+), 15 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index b73db20..3a2bc9b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7,9 +7,9 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co This is a FastAPI + Telegram Bot backend application for managing Telegram channel integrations. The project uses: - **FastAPI** for HTTP API endpoints - **aiogram** for Telegram bot functionality -- **SQLAlchemy 2.0** (async) for database operations +- **Tortoise ORM** (async) for database operations - **PostgreSQL** with asyncpg driver -- **Alembic** for database migrations +- **aerich** for database migrations - **uv** for Python package management ## Development Commands @@ -23,7 +23,7 @@ uv sync docker-compose up -d # Run database migrations -uv run alembic upgrade head +make migrate-up # or: export $(cat .env | xargs) && aerich upgrade ``` ### Running the Application @@ -58,14 +58,19 @@ uv run pytest tests/test_file.py::test_function_name ### Database Migrations ```bash -# Create a new migration -uv run alembic revision --autogenerate -m "description" +# Create a new migration (auto-generated based on model changes) +make migrate-create # or: export $(cat .env | xargs) && aerich migrate # Apply migrations -uv run alembic upgrade head +make migrate-up # or: export $(cat .env | xargs) && aerich upgrade # Rollback one migration -uv run alembic downgrade -1 +make migrate-down # or: export $(cat .env | xargs) && aerich downgrade + +# NOTE: For custom constraints (CHECK, custom indexes, etc.) that cannot be defined +# in Tortoise ORM models, create empty migrations and edit them manually: +export $(cat .env | xargs) && aerich migrate --name "add_check_constraint" --empty +# Then edit the created file and add `# ruff: noqa` and `# mypy: ignore-errors` at the top. ``` ## Architecture @@ -75,12 +80,12 @@ uv run alembic downgrade -1 The codebase follows a clean architecture pattern with clear separation of concerns: 1. **Domain Layer** (`src/domain/`) - - SQLAlchemy ORM models that represent database entities - - All models inherit from `domain.Base` which provides: + - Tortoise ORM models that represent database entities + - All models inherit from `domain.base.TimestampedModel` which provides: - Auto-generated `id` (UUID primary key) - Timestamps: `created_at`, `updated_at`, `deleted_at` - - Automatic table naming (pluralized lowercase class name) - Timezone-aware datetime fields + - Use `class Meta: table = 'table_name'` for explicit table naming 2. **Use Case Layer** (`src/usecase/`) - Business logic functions organized by feature @@ -322,17 +327,55 @@ await self.telegram_bot.send_message_with_inline_keyboard('Message', chat_id, bu **Типизация Enum в Domain:** - Используйте `enum.StrEnum` для строковых enum (Python 3.11+) - Enum значения в lowercase с underscores -- Передавайте enum в SQLAlchemy через `Enum(EnumClass)` +- Используйте `fields.CharEnumField()` в Tortoise ORM ```python import enum -from sqlalchemy import Enum -from sqlalchemy.orm import Mapped, mapped_column +from tortoise import fields +from .base import TimestampedModel class TelegramStateEnum(enum.StrEnum): CREATIVE_WAITING_CHANNEL = 'creative_waiting_channel' CREATIVE_WAITING_NAME = 'creative_waiting_name' -class TelegramState(Base): - state: Mapped[TelegramStateEnum] = mapped_column(Enum(TelegramStateEnum), nullable=False) +class TelegramState(TimestampedModel): + state = fields.CharEnumField(TelegramStateEnum, max_length=26) + + class Meta: + table = 'telegram_state' +``` + +**Database Constraints:** +- Foreign keys: Use `fields.ForeignKeyField()` with `on_delete=fields.CASCADE` +- Unique constraints: Use `unique=True` or `unique_together` in Meta +- CHECK constraints: Cannot be defined in Tortoise models - create manual migrations +- Indexes: Use `index=True` on fields or define in Meta + +Example with CHECK constraint: +```bash +# Create empty migration +export $(cat .env | xargs) && aerich migrate --name "add_check_constraint" --empty +``` + +Then edit the created file: +```python +# migrations/models/1_xxx_add_check_constraint.py +# ruff: noqa +# mypy: ignore-errors +from tortoise import BaseDBAsyncClient + +RUN_IN_TRANSACTION = True + +async def upgrade(db: BaseDBAsyncClient) -> str: + return """ + ALTER TABLE "table_name" + ADD CONSTRAINT "constraint_name" + CHECK (your_condition); + """ + +async def downgrade(db: BaseDBAsyncClient) -> str: + return """ + ALTER TABLE "table_name" + DROP CONSTRAINT IF EXISTS "constraint_name"; + """ ```