Markdown Converter
Agent skill for markdown-converter
This document helps Claude (and other AI assistants) understand the project structure and provide better assistance to developers working on this codebase.
Sign in to like and favorite skills
This document helps Claude (and other AI assistants) understand the project structure and provide better assistance to developers working on this codebase.
Type: FastAPI REST API with SQLite database Purpose: Interview skeleton project / rapid prototyping template Architecture: Layered (Model → Schema → CRUD → API) Database: SQLite with SQLAlchemy ORM Migrations: Alembic Validation: Pydantic v2 Package Manager: uv
The project follows a strict layered architecture:
User Request ↓ API Layer (FastAPI routes) ← Validates input via Pydantic schemas ↓ CRUD Layer (Database operations) ← Business logic ↓ Model Layer (SQLAlchemy ORM) ← Data structure ↓ Database (SQLite)
app/ ├── main.py # Application entry point ├── api/ # API route handlers (controllers) │ ├── __init__.py │ └── users.py # User endpoints ├── core/ # Core configuration │ ├── config.py # Settings (from .env) │ └── database.py # Database setup ├── crud/ # Database operations (data access layer) │ ├── __init__.py │ └── user.py # User CRUD operations ├── models/ # SQLAlchemy models (database schema) │ ├── __init__.py │ └── user.py # User model └── schemas/ # Pydantic models (validation/serialization) ├── __init__.py └── user.py # User schemas
app/models/)User class defines the users table structureapp/schemas/)app/crud/)app/api/)When a developer wants to add a new resource (e.g., "Add a Posts feature"):
File:
app/models/post.py
from sqlalchemy import Column, Integer, String, Text, ForeignKey, DateTime from sqlalchemy.sql import func from sqlalchemy.orm import relationship from app.core.database import Base class Post(Base): __tablename__ = "posts" id = Column(Integer, primary_key=True, index=True) title = Column(String, nullable=False, index=True) content = Column(Text, nullable=False) author_id = Column(Integer, ForeignKey("users.id")) created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) # Relationship author = relationship("User", back_populates="posts")
Don't forget: Update
app/models/__init__.py
File:
app/schemas/post.py
from datetime import datetime from typing import Optional from pydantic import BaseModel, ConfigDict class PostBase(BaseModel): title: str content: str class PostCreate(PostBase): author_id: int class PostUpdate(BaseModel): title: Optional[str] = None content: Optional[str] = None class PostResponse(PostBase): id: int author_id: int created_at: datetime updated_at: Optional[datetime] = None model_config = ConfigDict(from_attributes=True)
Don't forget: Update
app/schemas/__init__.py
File:
app/crud/post.py
from typing import List, Optional from sqlalchemy.orm import Session from app.models.post import Post from app.schemas.post import PostCreate, PostUpdate def get_post(db: Session, post_id: int) -> Optional[Post]: return db.query(Post).filter(Post.id == post_id).first() def get_posts(db: Session, skip: int = 0, limit: int = 100) -> List[Post]: return db.query(Post).offset(skip).limit(limit).all() def create_post(db: Session, post: PostCreate) -> Post: db_post = Post(**post.model_dump()) db.add(db_post) db.commit() db.refresh(db_post) return db_post def update_post(db: Session, post_id: int, post: PostUpdate) -> Optional[Post]: db_post = get_post(db, post_id) if not db_post: return None update_data = post.model_dump(exclude_unset=True) for field, value in update_data.items(): setattr(db_post, field, value) db.commit() db.refresh(db_post) return db_post def delete_post(db: Session, post_id: int) -> bool: db_post = get_post(db, post_id) if not db_post: return False db.delete(db_post) db.commit() return True
Don't forget: Update
app/crud/__init__.py
File:
app/api/posts.py
from typing import List from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session from app.core.database import get_db from app.crud import post as crud_post from app.schemas.post import PostCreate, PostUpdate, PostResponse router = APIRouter(prefix="/posts", tags=["posts"]) @router.post("/", response_model=PostResponse, status_code=status.HTTP_201_CREATED) def create_post(post: PostCreate, db: Session = Depends(get_db)): return crud_post.create_post(db=db, post=post) @router.get("/", response_model=List[PostResponse]) def read_posts(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): return crud_post.get_posts(db, skip=skip, limit=limit) @router.get("/{post_id}", response_model=PostResponse) def read_post(post_id: int, db: Session = Depends(get_db)): db_post = crud_post.get_post(db, post_id=post_id) if not db_post: raise HTTPException(status_code=404, detail="Post not found") return db_post @router.patch("/{post_id}", response_model=PostResponse) def update_post(post_id: int, post: PostUpdate, db: Session = Depends(get_db)): db_post = crud_post.update_post(db, post_id=post_id, post=post) if not db_post: raise HTTPException(status_code=404, detail="Post not found") return db_post @router.delete("/{post_id}", status_code=status.HTTP_204_NO_CONTENT) def delete_post(post_id: int, db: Session = Depends(get_db)): if not crud_post.delete_post(db, post_id=post_id): raise HTTPException(status_code=404, detail="Post not found")
Don't forget: Update
app/api/__init__.py
File:
app/main.py
from app.api.posts import router as posts_router app.include_router(posts_router)
uv run alembic revision --autogenerate -m "Add posts table" uv run alembic upgrade head
CRITICAL: Always generate migrations after modifying models!
When a developer wants to add fields to an existing model:
make db-migrate MSG="Add field_name to table"make db-upgradeExample:
# app/models/user.py class User(Base): # ... existing fields ... bio = Column(Text, nullable=True) # New field # app/schemas/user.py class UserBase(BaseModel): # ... existing fields ... bio: Optional[str] = None # New field # Then run: # make db-migrate MSG="Add bio field to users" # make db-upgrade
CORRECT ✅
from pydantic import BaseModel, ConfigDict class User(BaseModel): model_config = ConfigDict(from_attributes=True) data = user.model_dump()
INCORRECT ❌ (Deprecated v1 syntax)
class User(BaseModel): class Config: orm_mode = True data = user.dict()
200 OK - GET, PATCH success201 Created - POST success204 No Content - DELETE success400 Bad Request - Business logic errors (duplicate email, etc.)404 Not Found - Resource doesn't exist422 Unprocessable Entity - Validation errors (automatic via Pydantic)ALWAYS use HTTPException, NEVER return None:
# ✅ CORRECT from fastapi import HTTPException if not user: raise HTTPException(status_code=404, detail="User not found") # ❌ WRONG if not user: return None # Don't do this in API routes!
ALWAYS commit and refresh after modifications:
# Create db.add(db_obj) db.commit() db.refresh(db_obj) return db_obj # Update for field, value in update_data.items(): setattr(db_obj, field, value) db.commit() db.refresh(db_obj) return db_obj # Delete db.delete(db_obj) db.commit() return True
"Add a new feature/resource"
/docs"Add a field to existing model"
/docs"Add authentication"
"Add search/filtering"
"Write tests"
tests/ directoryWhen users ask about testing:
Unit tests: Test individual components (schemas, CRUD)
tests/unit/uv run pytest tests/unit/Integration tests: Test API + database together
tests/integration/uv run pytest tests/integration/E2E tests: Test complete user scenarios
tests/e2e/uv run pytest tests/e2e/All tests use an in-memory SQLite database for speed and isolation.
__init__.py - Required when adding new modulesapp/main.py# Setup bash setup.sh # Complete setup in one command make setup # Alternative using Makefile # Development make dev # Start development server make help # Show all available commands # Database make db-init # Initialize database (first time) make db-migrate MSG="..." # Create migration make db-upgrade # Apply migrations uv run alembic current # Show current migration uv run alembic history # Show migration history # Testing make test # Run all tests uv run pytest -v # Verbose test output uv run pytest tests/unit/ # Run only unit tests # Cleanup make clean # Remove database and cache files
Configured in
.env (copy from .env.example):
APP_NAME=FastAPI Demo DEBUG=true DATABASE_URL=sqlite:///./app.db
Accessed via:
from app.core.config import settings print(settings.database_url)
Once server is running:
When a developer asks for help:
__init__.py updates or migrations/docs or test filesUser: "I want to add a Posts feature where users can create blog posts"
Claude Response:
/docs endpoint/posts/"This project prioritizes:
Remember: This is an interview/showcase project. Code should demonstrate professional software engineering practices!