Guidelines for AI agents working in this codebase.
A Point of Sale (Kasir) REST API built with FastAPI and PostgreSQL.
- Language: Python 3.14+
- Framework: FastAPI
- Database: PostgreSQL via psycopg (async)
- Package Manager: uv
- Models: Pydantic BaseModel
# Install dependencies
uv sync
# Run development server
uv run fastapi dev src/kasir_api/app.py
# Run production server
uv run fastapi run src/kasir_api/app.py --port 8000# Run all tests
uv run pytest
# Run a single test file
uv run pytest tests/test_example.py
# Run a specific test function
uv run pytest tests/test_example.py::test_function_name
# Run tests with verbose output
uv run pytest -v
# Run tests matching a pattern
uv run pytest -k "test_category"
# Run with coverage
uv run pytest --cov=src/kasir_apiNo linter/formatter is currently configured. Recommended tools:
# If ruff is added:
uv run ruff check src/
uv run ruff format src/
# If mypy is added:
uv run mypy src/src/kasir_api/
├── app.py # FastAPI application entry point
├── config.py # Environment variable loading
├── settings.py # Settings class
├── errors.py # Custom exception classes
├── models/ # Pydantic models
│ ├── category.py
│ └── product.py
├── repositories/ # Database access layer
│ ├── db.py # Connection pool
│ ├── category_repository.py
│ └── product_repository.py
└── handlers/ # Request handlers (currently empty)
Order imports in this sequence, separated by blank lines:
- Standard library
- Third-party packages
- Local application imports
import os
from fastapi import FastAPI
from pydantic import BaseModel
from kasir_api.config import DATABASE_URL
from kasir_api.models.category import Category- Always use type hints for function parameters and return values
- Use modern union syntax:
str | None(notOptional[str]) - Use
list[T]instead ofList[T]
async def get_by_id(self, id: int) -> Category | None:
...
async def get_all(self) -> list[Category]:
...- Classes: PascalCase (
CategoryRepository,Product) - Functions/Methods: snake_case (
get_by_id,find_all) - Variables: snake_case (
database_url,row_factory) - Constants: UPPER_SNAKE_CASE (
DATABASE_URL,PORT) - Files: snake_case (
category_repository.py)
Define models in src/kasir_api/models/ directory:
from pydantic import BaseModel
class Category(BaseModel):
id: int
name: str
description: str | None = NoneDatabase access follows the repository pattern in src/kasir_api/repositories/:
from psycopg_pool import AsyncConnectionPool
from psycopg.rows import dict_row
class CategoryRepository:
def __init__(self, pool: AsyncConnectionPool):
self.pool = pool
@staticmethod
def _to_model(row: dict) -> Category:
return Category(**row)
async def get_all(self) -> list[Category]:
async with self.pool.connection() as conn:
async with conn.cursor(row_factory=dict_row) as cur:
await cur.execute("""
SELECT id, name, description
FROM categories
""")
rows = await cur.fetchall()
return [self._to_model(r) for r in rows]Use custom exceptions from src/kasir_api/errors.py:
from kasir_api.errors import NotFoundError, ConflictError, ValidationError
# Available exceptions:
# - AppError: Base class for all app errors
# - NotFoundError: Resource not found (404)
# - ConflictError: Data conflict/unique constraint (409)
# - ValidationError: Invalid data (400)
# - UnauthorizedError: No access (401)All database operations and handlers should be async:
async def create(self, category: Category) -> Category:
async with self.pool.connection() as conn:
async with conn.cursor(row_factory=dict_row) as cur:
await cur.execute(...)- Use triple-quoted strings for multi-line SQL
- Use parameterized queries with
%splaceholders (psycopg style) - Always use
RETURNINGclause for INSERT/UPDATE operations
await cur.execute("""
INSERT INTO categories (name, description)
VALUES (%s, %s)
RETURNING id, name, description
""", (category.name, category.description))- Load from
.envfile using python-dotenv - Define in
config.py, expose viasettings.py - Required variables:
DB_CONN,PORT
PostgreSQL connection uses psycopg async connection pool:
import psycopg_pool
pool = psycopg_pool.ConnectionPool(settings.database_url)- New Model: Create in
src/kasir_api/models/ - New Repository: Create in
src/kasir_api/repositories/ - New Handler: Create in
src/kasir_api/handlers/ - New Endpoint: Register in
src/kasir_api/app.py