This commit is contained in:
221
plugins/compound-engineering/skills/fastapi-style/SKILL.md
Normal file
221
plugins/compound-engineering/skills/fastapi-style/SKILL.md
Normal file
@@ -0,0 +1,221 @@
|
||||
---
|
||||
name: fastapi-style
|
||||
description: This skill should be used when writing Python and FastAPI code following opinionated best practices. It applies when building APIs, creating Pydantic models, working with SQLAlchemy, or any FastAPI application. Triggers on FastAPI code generation, API design, refactoring requests, code review, or when discussing async Python patterns. Embodies thin routers, rich Pydantic models, dependency injection, async-first design, and the "explicit is better than implicit" philosophy.
|
||||
---
|
||||
|
||||
<objective>
|
||||
Apply opinionated FastAPI conventions to Python API code. This skill provides comprehensive domain expertise for building maintainable, performant FastAPI applications following established patterns from production codebases.
|
||||
</objective>
|
||||
|
||||
<essential_principles>
|
||||
## Core Philosophy
|
||||
|
||||
"Explicit is better than implicit. Simple is better than complex."
|
||||
|
||||
**The FastAPI Way:**
|
||||
- Thin routers, rich Pydantic models with validation
|
||||
- Dependency injection for everything
|
||||
- Async-first with SQLAlchemy 2.0
|
||||
- Type hints everywhere - let the tools help you
|
||||
- Settings via pydantic-settings, not raw env vars
|
||||
- Database-backed solutions where possible
|
||||
|
||||
**What to deliberately avoid:**
|
||||
- Flask patterns (global request context)
|
||||
- Django ORM in FastAPI (use SQLAlchemy 2.0)
|
||||
- Synchronous database calls (use async)
|
||||
- Manual JSON serialization (Pydantic handles it)
|
||||
- Global state (use dependency injection)
|
||||
- `*` imports (explicit imports only)
|
||||
- Circular imports (proper module structure)
|
||||
|
||||
**Development Philosophy:**
|
||||
- Type everything - mypy should pass
|
||||
- Fail fast with descriptive errors
|
||||
- Write-time validation over read-time checks
|
||||
- Database constraints complement Pydantic validation
|
||||
- Tests are documentation
|
||||
</essential_principles>
|
||||
|
||||
<intake>
|
||||
What are you working on?
|
||||
|
||||
1. **Routers** - Route organization, dependency injection, response models
|
||||
2. **Models** - Pydantic schemas, SQLAlchemy models, validation patterns
|
||||
3. **Database** - SQLAlchemy 2.0 async, Alembic migrations, transactions
|
||||
4. **Testing** - pytest, httpx TestClient, fixtures, async testing
|
||||
5. **Security** - OAuth2, JWT, permissions, CORS, rate limiting
|
||||
6. **Background Tasks** - Celery, ARQ, or FastAPI BackgroundTasks
|
||||
7. **Code Review** - Review code against FastAPI best practices
|
||||
8. **General Guidance** - Philosophy and conventions
|
||||
|
||||
**Specify a number or describe your task.**
|
||||
</intake>
|
||||
|
||||
<routing>
|
||||
|
||||
| Response | Reference to Read |
|
||||
|----------|-------------------|
|
||||
| 1, router, route, endpoint | [routers.md](./references/routers.md) |
|
||||
| 2, model, pydantic, schema, sqlalchemy | [models.md](./references/models.md) |
|
||||
| 3, database, db, alembic, migration, transaction | [database.md](./references/database.md) |
|
||||
| 4, test, testing, pytest, fixture | [testing.md](./references/testing.md) |
|
||||
| 5, security, auth, oauth, jwt, permission | [security.md](./references/security.md) |
|
||||
| 6, background, task, celery, arq, queue | [background_tasks.md](./references/background_tasks.md) |
|
||||
| 7, review | Read all references, then review code |
|
||||
| 8, general task | Read relevant references based on context |
|
||||
|
||||
**After reading relevant references, apply patterns to the user's code.**
|
||||
</routing>
|
||||
|
||||
<quick_reference>
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
app/
|
||||
├── main.py # FastAPI app creation, middleware
|
||||
├── config.py # Settings via pydantic-settings
|
||||
├── dependencies.py # Shared dependencies
|
||||
├── database.py # Database session, engine
|
||||
├── models/ # SQLAlchemy models
|
||||
│ ├── __init__.py
|
||||
│ ├── base.py # Base model class
|
||||
│ └── user.py
|
||||
├── schemas/ # Pydantic models
|
||||
│ ├── __init__.py
|
||||
│ └── user.py
|
||||
├── routers/ # API routers
|
||||
│ ├── __init__.py
|
||||
│ └── users.py
|
||||
├── services/ # Business logic (if needed)
|
||||
├── utils/ # Shared utilities
|
||||
└── tests/
|
||||
├── conftest.py # Fixtures
|
||||
└── test_users.py
|
||||
```
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
**Pydantic Schemas:**
|
||||
- `UserCreate` - input for creation
|
||||
- `UserUpdate` - input for updates (all fields Optional)
|
||||
- `UserRead` - output representation
|
||||
- `UserInDB` - internal with hashed password
|
||||
|
||||
**SQLAlchemy Models:** Singular nouns (`User`, `Item`, `Order`)
|
||||
|
||||
**Routers:** Plural resource names (`users.py`, `items.py`)
|
||||
|
||||
**Dependencies:** Verb phrases (`get_current_user`, `get_db_session`)
|
||||
|
||||
## Type Hints
|
||||
|
||||
```python
|
||||
# Always type function signatures
|
||||
async def get_user(
|
||||
user_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
) -> User:
|
||||
...
|
||||
|
||||
# Use Annotated for dependency injection
|
||||
from typing import Annotated
|
||||
CurrentUser = Annotated[User, Depends(get_current_user)]
|
||||
DBSession = Annotated[AsyncSession, Depends(get_db)]
|
||||
```
|
||||
|
||||
## Response Patterns
|
||||
|
||||
```python
|
||||
# Explicit response_model
|
||||
@router.get("/users/{user_id}", response_model=UserRead)
|
||||
async def get_user(user_id: int, db: DBSession) -> User:
|
||||
...
|
||||
|
||||
# Status codes
|
||||
@router.post("/users", status_code=status.HTTP_201_CREATED)
|
||||
async def create_user(...) -> UserRead:
|
||||
...
|
||||
|
||||
# Multiple response types
|
||||
@router.get("/users/{user_id}", responses={404: {"model": ErrorResponse}})
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
```python
|
||||
from fastapi import HTTPException, status
|
||||
|
||||
# Specific exceptions
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="User not found",
|
||||
)
|
||||
|
||||
# Custom exception handlers
|
||||
@app.exception_handler(ValidationError)
|
||||
async def validation_exception_handler(request, exc):
|
||||
return JSONResponse(status_code=422, content={"detail": exc.errors()})
|
||||
```
|
||||
|
||||
## Dependency Injection
|
||||
|
||||
```python
|
||||
# Simple dependency
|
||||
async def get_db() -> AsyncGenerator[AsyncSession, None]:
|
||||
async with async_session() as session:
|
||||
yield session
|
||||
|
||||
# Parameterized dependency
|
||||
def get_pagination(
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(100, ge=1, le=1000),
|
||||
) -> dict:
|
||||
return {"skip": skip, "limit": limit}
|
||||
|
||||
# Class-based dependency
|
||||
class CommonQueryParams:
|
||||
def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
|
||||
self.q = q
|
||||
self.skip = skip
|
||||
self.limit = limit
|
||||
```
|
||||
</quick_reference>
|
||||
|
||||
<reference_index>
|
||||
## Domain Knowledge
|
||||
|
||||
All detailed patterns in `references/`:
|
||||
|
||||
| File | Topics |
|
||||
|------|--------|
|
||||
| [routers.md](./references/routers.md) | Route organization, dependency injection, response models, middleware, versioning |
|
||||
| [models.md](./references/models.md) | Pydantic schemas, SQLAlchemy models, validation, serialization, mixins |
|
||||
| [database.md](./references/database.md) | SQLAlchemy 2.0 async, Alembic migrations, transactions, connection pooling |
|
||||
| [testing.md](./references/testing.md) | pytest, httpx TestClient, fixtures, async testing, mocking patterns |
|
||||
| [security.md](./references/security.md) | OAuth2, JWT, permissions, CORS, rate limiting, secrets management |
|
||||
| [background_tasks.md](./references/background_tasks.md) | FastAPI BackgroundTasks, Celery, ARQ, task patterns |
|
||||
</reference_index>
|
||||
|
||||
<success_criteria>
|
||||
Code follows FastAPI best practices when:
|
||||
- Routers are thin, focused on HTTP concerns only
|
||||
- Pydantic models handle all validation and serialization
|
||||
- SQLAlchemy 2.0 async patterns used correctly
|
||||
- Dependencies injected, not imported as globals
|
||||
- Type hints on all function signatures
|
||||
- Settings via pydantic-settings
|
||||
- Tests use pytest with async support
|
||||
- Error handling is explicit and informative
|
||||
- Security follows OAuth2/JWT standards
|
||||
- Background tasks use appropriate tool for the job
|
||||
</success_criteria>
|
||||
|
||||
<credits>
|
||||
Based on FastAPI best practices from the official documentation, real-world production patterns, and the Python community's collective wisdom.
|
||||
|
||||
**Key Resources:**
|
||||
- [FastAPI Documentation](https://fastapi.tiangolo.com/)
|
||||
- [SQLAlchemy 2.0 Documentation](https://docs.sqlalchemy.org/)
|
||||
- [Pydantic V2 Documentation](https://docs.pydantic.dev/)
|
||||
</credits>
|
||||
Reference in New Issue
Block a user