# Blog AGENTS.md ## Stack - Python 3.13+, FastAPI, pydantic, uvicorn - SQLAlchemy 2.0 (async), aiosqlite - Package manager: `uv` - CI: Woodpecker (lint, test, type on push/PR to `dev`) ## Commands ```bash uv sync --group dev # Install all dev dependencies uv run pytest # Run tests (coverage >= 70% required) uv run pytest tests/unit/ # Run single test directory uv run ruff check . --fix # Lint uv run ruff format # Format uv run isort . # Sort imports uv run mypy . # Type check (strict mode) uv run blog # Start dev server (port 8000) ``` ## Pre-commit order `ruff check --fix` → `ruff format` → `isort` → `mypy` ## DDD Architecture ### Layer Structure ``` app/ ├── domain/ # Domain Layer - business logic, no dependencies │ ├── entities/ # Domain entities (Post, User, etc.) │ │ ├── base.py # Base entity class │ │ └── post.py # Post entity with business logic │ ├── value_objects/ # Value objects (Title, Content, Slug) │ │ ├── base.py │ │ ├── title.py │ │ ├── content.py │ │ └── slug.py │ ├── repositories/ # Repository interfaces (abstract) │ │ ├── base.py │ │ └── post.py │ └── exceptions.py # Domain exceptions │ ├── application/ # Application Layer - use cases │ ├── dtos/ # Data Transfer Objects │ │ └── post.py │ ├── interfaces/ # Abstract interfaces (UoW) │ │ └── unit_of_work.py │ └── use_cases/ # Use cases (CQRS-like) │ ├── create_post.py │ ├── get_post.py │ ├── update_post.py │ ├── delete_post.py │ ├── list_posts.py │ └── publish_post.py │ ├── infrastructure/ # Infrastructure Layer - external concerns │ ├── config/ # Configuration │ │ └── settings.py │ ├── database/ # Database connection & ORM models │ │ ├── connection.py │ │ └── models.py │ ├── repositories/ # Repository implementations │ │ ├── post.py # SQLAlchemyPostRepository │ │ └── unit_of_work.py # SQLAlchemyUnitOfWork │ ├── di/ # Dependency Injection │ │ └── container.py │ └── middleware/ # Exception handlers │ └── error_handler.py │ ├── presentation/ # Presentation Layer - API │ ├── api/ # FastAPI routes │ │ ├── v1/ # API version 1 │ │ │ ├── __init__.py │ │ │ └── posts.py # Posts endpoints │ │ ├── deps.py # FastAPI dependencies │ │ └── __init__.py │ └── schemas/ # Pydantic schemas │ └── post.py │ └── main.py # Application entry point tests/ ├── unit/ # Unit tests (domain, use cases) │ ├── domain/ # Domain layer tests │ ├── application/ # Application layer tests │ └── infrastructure/ # Infrastructure tests ├── integration/ # Integration tests (DB, repos) ├── api/ # API endpoint tests └── e2e/ # End-to-end tests ``` ## Key Conventions ### Dependency Rule - Domain layer has **NO dependencies** on other layers - Application layer depends only on Domain - Infrastructure depends on Domain and Application - Presentation depends on all other layers ### Testing - **Unit tests**: Test domain logic without DB/external services - **Integration tests**: Test repository implementations with real DB - **API tests**: Test endpoints with mocked use cases - **E2E tests**: Full workflow testing ### Code Patterns - Use **dataclasses** for entities and value objects - Use **frozen dataclasses** for value objects (immutable) - Use **Unit of Work** pattern for transactions - Use **Repository** pattern for data access - Use **Dependency Injection** via FastAPI's Depends() ## AI Code Generation Requirements ### Documentation Standards - **Write self-documenting code** - use clear, descriptive variable names and function names - **NO inline comments** - code should be readable without explanatory comments - **Google-style docstrings** are REQUIRED for all modules, classes, and functions ### Docstring Requirements #### Modules Every module must have a module-level docstring: ```python """Module for managing blog posts. This module provides use cases for creating, updating, and deleting blog posts in the application layer. """ ``` #### Classes Every class must have a detailed docstring: ```python class CreatePostUseCase: """Use case for creating a new blog post. This class encapsulates the business logic for creating posts, including validation and slug generation. Attributes: uow: Unit of Work for database transactions. slug_service: Service for generating URL-friendly slugs. Example: >>> use_case = CreatePostUseCase(uow, slug_service) >>> result = await use_case.execute(dto) """ ``` #### Functions/Methods Every function must have a detailed docstring with Args, Returns, Raises: ```python async def execute(self, dto: CreatePostDTO) -> PostDTO: """Execute the use case to create a new post. Args: dto: Data transfer object containing post creation data including title, content, and author information. Returns: PostDTO: The created post data transfer object with generated ID and slug. Raises: TitleValidationError: If the title is empty or too long. ContentValidationError: If the content is empty. DuplicateSlugError: If a post with the same slug exists. Note: This method is idempotent - calling it multiple times with the same data will create separate posts with unique slugs. """ ``` ### Google-Style Docstring Format Use the following sections as appropriate: - `Args` - Parameter descriptions with types - `Returns` - Return value description with type - `Raises` - Exceptions that may be raised - `Yields` - For generator functions - `Example` - Usage examples - `Note` - Additional important information - `Warning` - Critical warnings - `Attributes` - For class attributes - `See Also` - References to related code ## UI Development Requirements ### HTML Templates (Jinja2) - All HTML templates use **Jinja2** templating engine - Templates are located in `app/presentation/templates/` - Base template: `base.html` with theme support (light/dark) ### data-testid Attributes (REQUIRED) **Every interactive and significant HTML element MUST have a `data-testid` attribute** for automated testing. #### Required Elements: - **Navigation**: `data-testid="nav-link-{name}"`, `data-testid="nav-logo"` - **Buttons**: `data-testid="btn-{action}"` (e.g., `btn-create`, `btn-save`, `btn-delete`) - **Forms**: `data-testid="form-{name}"`, `data-testid="input-{field}"`, `data-testid="submit-{action}"` - **Cards/Posts**: `data-testid="post-card-{id}"`, `data-testid="post-title"`, `data-testid="post-content"` - **Lists**: `data-testid="list-{name}"`, `data-testid="list-item-{index}"` - **Theme Switcher**: `data-testid="theme-toggle"`, `data-testid="theme-{light|dark}"` - **Messages/Alerts**: `data-testid="alert-{type}"`, `data-testid="alert-message" #### Example: ```html

{{ post.title }}

{{ post.content }}

``` ### CSS Architecture (Gitea-inspired) - **Theme files**: `static/css/themes/theme-{light|dark}.css` with CSS variables - **Base styles**: `static/css/base.css` - reset, typography, CSS variables usage - **Components**: `static/css/components.css` - buttons, cards, forms, inputs - **Layout**: `static/css/layout.css` - grid, navigation, containers ### Theme Support - Light and dark themes based on Gitea color scheme - Theme switching via `data-theme` attribute on `` element - LocalStorage persistence for user preference - All colors use CSS custom properties (variables) ### Static Assets - **All assets are local** - no external CDN dependencies - Location: `static/` directory at project root - Served via FastAPI `StaticFiles` middleware ### DDD Concepts Used ### Entities - Have identity (UUID) - Mutable state - Business logic methods (publish, update_title, etc.) - Example: `Post` entity ### Value Objects - Immutable - Defined by attributes - Validated on creation - Examples: `Title`, `Content`, `Slug` ### Aggregates & Repositories - `Post` is an aggregate root - `PostRepository` interface in Domain - `SQLAlchemyPostRepository` implementation in Infrastructure ### Domain Events - Placeholder for future implementation - Can be added via event bus in application layer ## Configuration - `.env` file loaded by pydantic-settings - Settings available via `app.infrastructure.config.settings` ## Database - SQLAlchemy 2.0 with async support - SQLite by default (aiosqlite) - Tables auto-created on startup - Use `init_db()` and `close_db()` in lifespan