Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Cursor rules for FastAPI services with router/service/repository boundaries, typed provider adapters, bulkhead isolation, idempotency, and domain exceptions.
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 36% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 60% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 10% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 39% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 133% | 0% |
Cursor rules for FastAPI services with router/service/repository boundaries, typed provider adapters, bulkhead isolation, idempotency, and domain exceptions.
Synced from https://github.com/PatrickJS/awesome-cursorrules/tree/main/rules/fastapi-production-architecture-cursorrules-prompt-file.mdc.
This codebase follows strict 4-layer architecture: Router → Service → Repository → ORM/HTTP/Storage. Imports flow downward only. Each layer has hard boundaries you must NOT cross.
GOOD: @router.post("/wallet/charge", response_model=WalletResponse, status_code=201) async def charge( req: ChargeRequest, user_id: str = Depends(get_current_user_id), svc: WalletUserService = Depends(get_wallet_service), ) -> WalletResponse: wallet = await svc.charge( user_id=user_id, amount=req.amount, idempotency_key=req.idempotency_key, ) return WalletResponse.from_domain(wallet)
BAD (business logic + SQL in router): @router.post("/wallet/charge") async def charge(req: ChargeRequest, db: Session = Depends(get_db)): wallet = db.query(Wallet).filter(Wallet.user_id == user_id).with_for_update().one() ...
GOOD: from app.repositories.protocols import WalletRepoProtocol class WalletUserService: def __init__(self, repo: WalletRepoProtocol): # Protocol, not SQLAlchemy Session self._repo = repo
BAD: from sqlalchemy.orm import Session class WalletUserService: def __init__(self, db: Session): ... # Wrong — service depends on infrastructure
| LOC | State | Action | |----------|--------|---------------------------------------------| | 0–399 | Green | None. | | 400–599 | Yellow | Plan split. Add TODO(decompose) header. | | 600+ | Red | BLOCK merge. Decompose first. |
Convert file to package when ANY is true:
Safe split pattern (atomic PR):
__init__.py pattern: from .user import WalletUserService from .admin import WalletAdminService WalletService = WalletUserService # backwards-compat alias __all__ = "WalletUserService", "WalletAdminService", "WalletService"]
Providers return GenerateResult | ProviderError, never dict.
from dataclasses import dataclass from decimal import Decimal
@dataclass(frozen=True) class GenerateResult: url: str cost_usd: Decimal latency_ms: int provider_request_id: str
class ProviderError(Exception): def __init__(self, message: str, , retryable: bool, code: str | None = None): super().__init__(message); self.retryable = retryable; self.code = code
class ProviderTimeout(ProviderError): def __init__(self, message: str): super().__init__(message, retryable=True, code="timeout")
Each external provider has its OWN httpx.AsyncClient with its OWN Limits. NEVER share.
GOOD: FAL_HTTP = httpx.AsyncClient( base_url=settings.FAL_BASE_URL, timeout=httpx.Timeout(connect=5.0, read=60.0, write=10.0, pool=5.0), limits=httpx.Limits(max_connections=20, max_keepalive_connections=10), ) OPENAI_HTTP = httpx.AsyncClient( base_url="https://api.openai.com/v1", limits=httpx.Limits(max_connections=50, max_keepalive_connections=20), )
BAD: HTTP = httpx.AsyncClient() # shared across all providers — no bulkhead isolation
from contextlib import asynccontextmanager
@asynccontextmanager async def lifespan(app): yield # app startup await FAL_HTTP.aclose() await OPENAI_HTTP.aclose()
app = FastAPI(lifespan=lifespan)
Every side-effect operation accepts an idempotency_key: UUID. Look up before retrying.
Use ContextVar to thread provider, user_id, request_id through async call stacks.
import contextvars
provider_var = contextvars.ContextVarstr | None user_id_var = contextvars.ContextVarstr | None request_id_var = contextvars.ContextVarstr | None
For safety-critical state: exactly ONE service-layer module does the writing. Only the designated writer service for a domain may call repo.hold(). Routers and providers must NOT call repo.hold() directly. Admin services implemented in the service layer may call repo.hold() only if they are the designated writer for that domain. Enforce via: code-review grep check (grep -r "repo\.hold(" --include="*.py") and unit tests that assert call-origin of repo.hold().
Use FastAPI Depends() + factory functions in app/core/deps.py. Do NOT install dependency-injector, punq, or any DI container.
def get_wallet_service(db: Session = Depends(get_db)) -> WalletUserService: return WalletUserService(repo=SQLAlchemyWalletRepo(db))
Services raise domain errors. Routers map to HTTP.
class InsufficientFundsError(Exception): ... class WalletNotFoundError(Exception): ...
try: wallet = await svc.charge(...) except InsufficientFundsError: raise HTTPException(402, detail="insufficient funds")
Other measured skills in the registry, with their headline benchmark lift.