

Also from Kynth Studios


Also from Kynth Studios


Also from Kynth Studios
1# GenSlides Backend - Development Guidelines23IMPORTANT: always use latest dependencies.45## Tech Stack67- **Language**: Python 3.11+8- **Framework**: FastAPI9- **Package Manager**: uv10- **Image Generation**: Google AI SDK (Gemini)11- **Data Storage**: File system (YAML + images)1213## Architecture Principles1415### SOLID Principles1617- **S - Single Responsibility**: Each module has one clear purpose18 - `api/routes/` - HTTP request handling only19 - `services/` - Business logic only20 - `repositories/` - Data persistence only21 - `clients/` - External API communication only2223- **O - Open/Closed**: Use abstractions for extensibility24 - Define protocols/interfaces for services25 - New image generators can be added without modifying existing code2627- **L - Liskov Substitution**: Subtypes must be substitutable28 - All repository implementations must honor the same contract2930- **I - Interface Segregation**: Small, focused interfaces31 - Separate schemas for request/response32 - Don't force clients to depend on methods they don't use3334- **D - Dependency Inversion**: Depend on abstractions35 - Services receive repositories via dependency injection36 - Use FastAPI's `Depends()` for DI3738### YAGNI (You Aren't Gonna Need It)3940- Don't add features until they're actually needed41- No premature abstractions42- Start simple, refactor when patterns emerge4344### KISS (Keep It Simple, Stupid)4546- Prefer straightforward solutions over clever ones47- Minimize layers of indirection48- Use standard library when possible4950### DRY (Don't Repeat Yourself)5152- Avoid duplicating code53- Use functions, classes, and modules to DRY code54- Use patterns and templates to DRY code5556## Code Organization5758```59backend/60├── main.py # FastAPI app entry, router registration61├── config.py # Settings via pydantic-settings62├── api/ # HTTP layer63│ ├── routes/ # Route handlers (thin, delegate to services)64│ ├── schemas/ # Pydantic models for request/response65│ └── dependencies.py # FastAPI dependencies (DI setup)66├── services/ # Business logic layer67├── repositories/ # Data access layer68├── models/ # Domain models (dataclasses)69├── clients/ # External service clients70└── utils/ # Pure utility functions71```7273### Layer Responsibilities7475| Layer | Responsibility | Can Call |76|--------------|-------------------------------|-----------------------|77| Routes | HTTP handling, validation | Services |78| Services | Business logic, orchestration | Repositories, Clients |79| Repositories | Data persistence | File system |80| Clients | External APIs | External services |8182### Import Rules8384```python85# ALLOWED86from services.slide_service import SlideService # routes -> services87from repositories.slide_repository import SlideRepository # services -> repositories88from clients.gemini_client import GeminiClient # services -> clients8990# NOT ALLOWED91from api.routes.slides import router # services should not import routes92from services.slide_service import SlideService # repositories should not import services93```9495## Concurrency9697### Async/Await Best Practices9899```python100# Use async for I/O-bound operations101async def generate_image(self, prompt: str) -> bytes:102 # Gemini API call is I/O-bound103 response = await self.client.models.generate_content_async(...)104 return response105106# Use sync for CPU-bound operations107def compute_hash(content: str) -> str:108 # Blake3 hashing is CPU-bound, keep sync109 return blake3.blake3(content.encode()).hexdigest()[:16]110```111112### File I/O113114```python115# For file operations, use aiofiles or run_in_executor116import aiofiles117118async def save_image(self, path: Path, data: bytes) -> None:119 async with aiofiles.open(path, 'wb') as f:120 await f.write(data)121122# Or use run_in_executor for sync file ops123from functools import partial124import asyncio125126async def read_yaml(self, path: Path) -> dict:127 loop = asyncio.get_event_loop()128 return await loop.run_in_executor(None, partial(yaml.safe_load, path.read_text()))129```130131### Parallel Image Generation132133```python134# Generate multiple images concurrently135async def generate_style_candidates(self, prompt: str) -> list[bytes]:136 tasks = [137 self._generate_single(f"{prompt} (variant {i})")138 for i in range(2)139 ]140 return await asyncio.gather(*tasks)141```142143## Error Handling144145### Exception Hierarchy146147```python148# models/exceptions.py149class GenSlidesError(Exception):150 """Base exception for all application errors"""151 pass152153class ProjectNotFoundError(GenSlidesError):154 """Raised when project/slug doesn't exist"""155 pass156157class SlideNotFoundError(GenSlidesError):158 """Raised when slide doesn't exist"""159 pass160161class ImageGenerationError(GenSlidesError):162 """Raised when Gemini API fails"""163 pass164165class ValidationError(GenSlidesError):166 """Raised for invalid input data"""167 pass168```169170### HTTP Error Mapping171172```python173# api/dependencies.py174from fastapi import HTTPException, status175176def handle_service_error(func):177 @wraps(func)178 async def wrapper(*args, **kwargs):179 try:180 return await func(*args, **kwargs)181 except ProjectNotFoundError as e:182 raise HTTPException(status_code=404, detail=str(e))183 except SlideNotFoundError as e:184 raise HTTPException(status_code=404, detail=str(e))185 except ImageGenerationError as e:186 raise HTTPException(status_code=502, detail=str(e))187 except ValidationError as e:188 raise HTTPException(status_code=422, detail=str(e))189 return wrapper190```191192### Error Response Format193194```python195# Consistent error responses196{197 "detail": "Human-readable error message",198 "error_code": "PROJECT_NOT_FOUND", # Optional: machine-readable code199 "context": {} # Optional: additional context200}201```202203## Logging204205### Configuration206207```python208# config.py209import logging210import sys211212def setup_logging(level: str = "INFO") -> None:213 logging.basicConfig(214 level=level,215 format="%(asctime)s | %(levelname)-8s | %(name)s | %(message)s",216 datefmt="%Y-%m-%d %H:%M:%S",217 handlers=[logging.StreamHandler(sys.stdout)]218 )219```220221### Usage Patterns222223```python224import logging225226logger = logging.getLogger(__name__)227228class SlideService:229 async def create_slide(self, slug: str, content: str) -> Slide:230 logger.info("Creating slide", extra={"slug": slug, "content_length": len(content)})231 try:232 slide = await self._do_create(slug, content)233 logger.info("Slide created", extra={"slug": slug, "sid": slide.sid})234 return slide235 except Exception as e:236 logger.exception("Failed to create slide", extra={"slug": slug})237 raise238```239240### Log Levels241242| Level | Usage |243|----------|------------------------------------------------------|244| DEBUG | Detailed diagnostic info (disabled in prod) |245| INFO | Normal operations (request received, task completed) |246| WARNING | Unexpected but recoverable situations |247| ERROR | Failures that need attention |248| CRITICAL | System-wide failures |249250## Testing251252### Test Structure253254```255tests/256├── conftest.py # Shared fixtures257├── unit/ # Unit tests (no I/O)258│ ├── test_services.py259│ └── test_utils.py260├── integration/ # Integration tests (with I/O)261│ ├── test_repositories.py262│ └── test_api.py263└── e2e/ # End-to-end tests264 └── test_workflows.py265```266267### Running Tests268269```bash270# Run all tests271uv run pytest272273# Run with coverage274uv run pytest --cov=. --cov-report=html275276# Run specific test file277uv run pytest tests/unit/test_services.py -v278```279280## Code Style281282### Formatting & Linting283284```bash285# Format code286uv run ruff format .287288# Lint code289uv run ruff check .290291# Lint and fix292uv run ruff check --fix .293```294295### Type Hints296297Always use type hints:298299```python300from typing import Optional301from pathlib import Path302303async def get_slide(self, slug: str, sid: str) -> Optional[Slide]:304 ...305306def compute_hash(content: str) -> str:307 ...308```309310### Docstrings311312Use Google-style docstrings for public APIs:313314```python315def generate_image(self, prompt: str, style_image: Optional[bytes] = None) -> bytes:316 """Generate an image using Gemini API.317318 Args:319 prompt: Text description for image generation.320 style_image: Optional reference image for style consistency.321322 Returns:323 Generated image as PNG bytes.324325 Raises:326 ImageGenerationError: If Gemini API fails.327 """328```329330## FastAPI Best Practices331332### Router Organization333334```python335# api/routes/slides.py336from fastapi import APIRouter, Depends, status337338router = APIRouter(prefix="/api/slides", tags=["slides"])339340@router.get("/{slug}")341async def get_project(slug: str, service: SlideService = Depends(get_slide_service)):342 ...343344@router.post("/{slug}", status_code=status.HTTP_201_CREATED)345async def create_slide(slug: str, request: CreateSlideRequest, ...):346 ...347```348349### Dependency Injection350351```python352# api/dependencies.py353from functools import lru_cache354355@lru_cache356def get_settings() -> Settings:357 return Settings()358359def get_slide_repository(settings: Settings = Depends(get_settings)) -> SlideRepository:360 return SlideRepository(settings.slides_base_path)361362def get_slide_service(363 slide_repo: SlideRepository = Depends(get_slide_repository),364 image_repo: ImageRepository = Depends(get_image_repository),365) -> SlideService:366 return SlideService(slide_repo, image_repo)367```368369### Response Models370371```python372# Always specify response_model for documentation373@router.get("/{slug}", response_model=ProjectResponse)374async def get_project(slug: str, ...):375 ...376377# Use status codes explicitly378@router.delete("/{slug}/{sid}", status_code=status.HTTP_204_NO_CONTENT)379async def delete_slide(slug: str, sid: str, ...):380 ...381```382
One repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| tyrchen/geektime-bootcamp-ai.cursor/rules/python-fastapi-backend.mdc · 230 | Cursor rules | setuptestlint-formatstyle+7 | 84/100 | 9 days ago | |
| tyrchen/geektime-bootcamp-ai.cursor/rules/rust-best-practices.mdc · 230 | Cursor rules | teststylearchdependencies+1 | 81/100 | 9 days ago | |
| tyrchen/geektime-bootcamp-ai.cursor/rules/specify-rules.mdc · 230 | Cursor rules | stylearch | 52/100 | 9 days ago | |
| tyrchen/geektime-bootcamp-aisite/CLAUDE.md · 230 | CLAUDE.md | agent-behaviour | 25/100 | 9 days ago | |
| tyrchen/geektime-bootcamp-aiw3/raflow/CLAUDE.md · 230 | CLAUDE.md | agent-behaviour | 25/100 | 9 days ago | |
| tyrchen/geektime-bootcamp-aiw6/codereview-agent/CLAUDE.md · 230 | CLAUDE.md | setupbuildlint-formatarch+4 | 90/100 | 9 days ago | |
| tyrchen/geektime-bootcamp-aiw6/opencode-introspection/CLAUDE.md · 230 | CLAUDE.md | setupbuildtestlint-format+10 | 84/100 | 9 days ago | |
| tyrchen/geektime-bootcamp-aiw6/opencode-introspection/visualizer/CLAUDE.md · 230 | CLAUDE.md | setupbuildtestlint-format+9 | 78/100 | 9 days ago | |
| tyrchen/geektime-bootcamp-aiw6/simple-agent/CLAUDE.md · 230 | CLAUDE.md | setupbuildtestlint-format+10 | 84/100 | 9 days ago | |
| tyrchen/geektime-bootcamp-aiw7/genslides/frontend/CLAUDE.md · 230 | CLAUDE.md | teststylearchtypes+5 | 88/100 | 9 days ago | |
| tyrchen/geektime-bootcamp-aiw5/pg-mcp/CLAUDE.md · 230 | CLAUDE.md | setuptestlint-formatstyle+5 | 86/100 | 9 days ago |
Same format, overlapping stack, ranked by quality.
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| dotCMS/corecore-web/CLAUDE.md · 949 | CLAUDE.md | teststylearchtesting-strategy+3 | 100/100 | 14 days ago | |
| Adit-Jain-srm/NightmareNetCLAUDE.md · 46 | CLAUDE.md | buildtestlint-formatstyle+6 | 100/100 | 14 days ago | |
| dotCMS/coreCLAUDE.md · 949 | CLAUDE.md | setupbuildteststyle+7 | 99/100 | today | |
| dotCMS/corecore-web/libs/sdk/client/CLAUDE.md · 949 | CLAUDE.md | setupbuildtestlint-format+9 | 97/100 | 14 days ago | |
| dotCMS/corecore-web/libs/sdk/react/CLAUDE.md · 949 | CLAUDE.md | setupbuildtestlint-format+9 | 97/100 | 14 days ago | |
| supabase/supabase.claude/CLAUDE.md · 108k | CLAUDE.md | testlint-formatstylearch+1 | 97/100 | 14 days ago | |
| modelcontextprotocol/serversCLAUDE.md · 90k | CLAUDE.md | setupbuildtestlint-format+6 | 97/100 | 14 days ago | |
| luongnv89/claude-howtovi/CLAUDE.md · 41k | CLAUDE.md | setupbuildtestlint-format+7 | 97/100 | 9 days ago |
A badge carrying the measured quality of the strongest agent config file in this repository, out of 100. It reads from this index every time somebody loads your page, so it changes when the measurement changes and there is nothing to keep up to date. Free, no account, and the value is not something you or we can set by hand.
[](https://rulestack.kynth.studio/configs/tyrchen-geektime-bootcamp-ai-w7-genslides-backend-claude)Would rather not hotlink us? Every badge is also served in shields.io’s endpoint schema, so shields renders the image and your readers never talk to our domain:
Published by Toolproof, the masthead over this index and eight others. The method behind the number is at toolproof.kynth.studio/methodology, and the whole thing is readable as JSON with no key at /api.