Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsBlogPro
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges
  • Chrome Extension
  • Skill Manager

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Python Fastapi

ASecurity

Opinionated rules for production Python APIs built with FastAPI, Pydantic v2, SQLAlchemy 2.0 (async), Alembic, and PostgreSQL, including JWT/OAuth authentication, authorization, error handling, pagination, background jobs, and testing. Use when creating or reviewing FastAPI endpoints, request/response schemas, services, database access, dependencies, auth, settings, workers, or API tests, or when debugging slow or failing API requests.

2 stars
0 votes
0 copies
0 views
Added 10/6/2026
developmenttypescriptpythonrustgoswiftsqlnextjsfastapidockertesting

Works with

cursorcliapi

Security Analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned 10/6/2026

$npx -y skills add suleyman/fullstack-agent-skills --skill python-fastapi --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Python Fastapi?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Python Fastapi
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/suleyman-python-fastapi/badge)](https://www.skillsdirectory.com/skills/suleyman-python-fastapi)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
Files
SKILL.md
---
name: python-fastapi
description: Opinionated rules for production Python APIs built with FastAPI, Pydantic v2, SQLAlchemy 2.0 (async), Alembic, and PostgreSQL, including JWT/OAuth authentication, authorization, error handling, pagination, background jobs, and testing. Use when creating or reviewing FastAPI endpoints, request/response schemas, services, database access, dependencies, auth, settings, workers, or API tests, or when debugging slow or failing API requests.
license: MIT
metadata:
  author: suleyman
  version: "1.0.0"
---

# Python FastAPI

Build the API that **owns business logic, authorization, and the database** for every client: Next.js web, Expo mobile, and native iOS. Stack: FastAPI, Pydantic v2, SQLAlchemy 2.0 with `AsyncSession` + asyncpg, Alembic, and PostgreSQL. Tooling: `uv`, `ruff`, and a type checker (mypy or pyright) in strict mode on `app/`.

## When to use

- Adding or changing endpoints, schemas, services, models, dependencies, middleware, or settings.
- Implementing authentication, authorization, pagination, uploads, or background work.
- Writing API tests. Debugging 4xx/5xx responses, slow endpoints, or connection pool problems.

Use `postgresql` for schema design, indexes, query plans, and migration safety. Use `fullstack-architect` for API contract and auth decisions that affect clients.

## Core rules

1. **Three layers, one direction.** The router handles HTTP: parsing, auth dependencies, status codes. The service holds business rules and the transaction. Queries use SQLAlchemy. Routers contain no queries. Services never import `Request` and never raise `HTTPException`. They raise domain errors, which a handler maps to HTTP.
2. **Explicit schemas per operation.** Separate `OrderCreate`, `OrderUpdate`, and `OrderRead` (or `OrderSummary`). Never return ORM objects without a response schema, and never accept ORM models as input. Input schemas use `extra="forbid"`. Fields the client must not control (`owner_id`, `role`, `status`, `is_verified`) don't appear in input schemas at all.
3. **One commit per unit of work, in the service.** Do all writes, then commit once with `await session.commit()`. Never commit in repositories, and **never commit in a dependency's teardown**: if that commit fails after the response is sent, the client sees success for a write that didn't happen.
4. **Async all the way, or not at all.** In an `async def` endpoint every I/O call must be awaited (asyncpg, `httpx.AsyncClient`). A blocking call such as `requests`, `boto3` network calls, `time.sleep`, or heavy CPU work stalls every request on the worker. Move it to `anyio.to_thread.run_sync`, use a `def` endpoint, or send it to a job.
5. **Authorization lives in queries.** Scope every read and write by the principal: `WHERE id = :id AND author_id = :user_id`, or by tenant. Return 404 for resources the caller can't see.
6. **Fail fast on configuration.** Settings come from `pydantic-settings` and are validated at import. A missing secret stops the process at startup, not on first use.
7. **One error format.** Every error, including validation errors and unhandled exceptions, returns the same Problem Details body with a stable `code` and the `requestId`.

## Architecture

```
api/
├── pyproject.toml              # uv, ruff, mypy/pyright, pytest
├── alembic.ini
├── migrations/versions/
├── app/
│   ├── main.py                 # create_app(): routers, middleware, error handlers, lifespan
│   ├── core/
│   │   ├── config.py           # Settings(BaseSettings)
│   │   ├── db.py               # engine, async_sessionmaker, Base, get_session
│   │   ├── security.py         # token verification, password hashing
│   │   ├── errors.py           # DomainError hierarchy + exception handlers
│   │   ├── schemas.py          # APISchema base (camelCase), Page[T]
│   │   └── pagination.py       # opaque cursor encode/decode
│   ├── auth/                   # dependencies.py (get_current_user, require_role), router.py, service.py
│   ├── orders/                 # one package per domain
│   │   ├── models.py
│   │   ├── schemas.py
│   │   ├── service.py
│   │   └── router.py
│   └── worker/                 # job handlers (same codebase, different entrypoint)
└── tests/
```

- **Package by domain** (`orders/`, `users/`), not by layer (`models/`, `schemas/`, `routers/`).
- **camelCase on the wire.** All schemas inherit one `APISchema` base with `alias_generator=to_camel` and `populate_by_name=True`. FastAPI serializes by alias by default. TypeScript and Swift clients receive camelCase without client-side key transforms.
- **Protected by default.** Include routers with `dependencies=[Depends(get_current_user)]`. Public endpoints (sign-in, health, app config) live in a separate router so that exposing one is a deliberate choice.
- **Startup and shutdown use `lifespan`,** not `@app.on_event`. Create one shared `httpx.AsyncClient` with explicit timeouts there, and dispose of the engine on shutdown.
- **Migrations never run on startup.** Alembic runs as a separate deploy step (see the `postgresql` skill).

Reference code for each core module is in [references/project-skeleton.md](references/project-skeleton.md).

## Preferred patterns

```python
# app/orders/schemas.py
class OrderCreate(APISchema):
    model_config = ConfigDict(extra="forbid")  # no price, total, status, or customer fields

    items: Annotated[list[OrderItemIn], Field(min_length=1, max_length=50)]
    note: Annotated[str | None, StringConstraints(strip_whitespace=True, max_length=500)] = None


class OrderRead(APISchema):
    id: UUID
    status: OrderStatus
    items: list[OrderItemRead]
    total: Money                    # integer minor units + currency
    created_at: datetime            # serialized as createdAt, ISO 8601 with offset


class OrderPage(Page[OrderRead]):
    """Named subclass, so OpenAPI and generated clients see `OrderPage`, not `Page_OrderRead_`."""
```

```python
# app/orders/router.py
router = APIRouter(prefix="/orders", tags=["orders"])

@router.post("", status_code=status.HTTP_201_CREATED)
async def create_order(payload: OrderCreate, session: SessionDep, user: CurrentUser) -> OrderRead:
    return await service.create_order(session, customer=user, data=payload)
```

```python
# app/orders/service.py
async def create_order(session: AsyncSession, *, customer: User, data: OrderCreate) -> OrderRead:
    prices = await current_prices(session, [item.product_id for item in data.items])  # never trust client prices
    order = Order(customer_id=customer.id, note=data.note, items=build_items(data.items, prices))
    session.add(order)
    await session.flush()                     # assigns order.id inside the same transaction
    session.add(Job(kind="email.order_confirmation", payload={"orderId": str(order.id)}))  # commits atomically with the order
    await session.commit()
    return await get_order(session, order_id=order.id, customer_id=customer.id)
```

- **Dependencies:** `SessionDep = Annotated[AsyncSession, Depends(get_session)]` and `CurrentUser = Annotated[User, Depends(get_current_user)]`. Dependencies load things (session, user, the resource being acted on) and enforce auth. They don't run business logic.
- **PATCH:** use `data.model_dump(exclude_unset=True)` to tell "not sent" apart from "set to null".
- **N+1 prevention:** declare relationships with `lazy="raise"`, so implicit lazy loads fail loudly in development. Load explicitly with `selectinload` for collections and `joinedload` for many-to-one.
- **Sessions:** `async_sessionmaker(engine, expire_on_commit=False)`, so reading attributes after commit doesn't trigger implicit I/O (`MissingGreenlet`).
- **Pagination:** cursor (keyset) by default. Use `limit: Annotated[int, Query(ge=1, le=100)] = 20` and return `{items, nextCursor}` with an opaque cursor encoding the sort key. Fetch `limit + 1` rows to compute `nextCursor` without a `COUNT(*)`.
- **Constraint violations:** catch `IntegrityError`, inspect the constraint name (`uq_users_email`), and raise `Conflict(code="email_taken")`. The database enforces the rule, and the service translates it.
- **Upserts and races:** use `insert(...).on_conflict_do_update/do_nothing` (PostgreSQL dialect) instead of select-then-insert.
- **Outbound HTTP:** use the shared `httpx.AsyncClient`, explicit timeouts, and retries only for idempotent calls. Never keep a database transaction open across an external call.
- **Background work:** use FastAPI `BackgroundTasks` only for best-effort work you can afford to lose. Push notifications, emails, media processing, and webhooks go to a **durable job queue**, enqueued in the same transaction as the business write (outbox or a Postgres-backed queue). Jobs are idempotent.
- **Auth:** verify JWTs with PyJWT. Pin `algorithms`, verify `exp`, `iss`, and `aud`, and require `sub`. Hash passwords with Argon2 via `pwdlib[argon2]`. Load the user by `sub` on every request (a primary key lookup), so bans and role changes apply immediately. Token lifecycle: see the `fullstack-architect` skill.

## Patterns to avoid

| Avoid | Why / instead |
|---|---|
| Returning ORM objects or `dict`s without a response schema | Leaks columns and couples the API to the schema. Use explicit `*Read` schemas |
| One `PostSchema` with every field optional, used for create, update, and read | Ambiguous contract. Use a schema per operation |
| `session.commit()` in repositories, or several commits per request | Partial writes on failure. Commit once in the service |
| Commit in a `yield` dependency's teardown | Commit failures surface after the response is sent |
| `requests`, a sync DB driver, or `time.sleep` inside `async def` | Blocks the event loop for every request |
| `HTTPException` raised from services | Couples domain logic to HTTP. Raise domain errors |
| Offset pagination on growing tables | Slow at depth and unstable under inserts. Use cursors |
| `Base.metadata.create_all()` outside tests | Bypasses migrations. Use Alembic |
| `alembic upgrade` in `lifespan` or the container entrypoint | Races between replicas and crash loops. Use a release step |
| Bare `except Exception: pass` | Hides failures. Catch specific exceptions, or log and re-raise |
| `python-jose` and `passlib` in new code | Poorly maintained. Use PyJWT and pwdlib |
| `allow_origins=["*"]` with `allow_credentials=True` | Lets any site make credentialed calls. Use an explicit allowlist |
| Logging request bodies, tokens, or passwords | Credential and PII leaks. Log IDs and request IDs |

## Debugging

- **`MissingGreenlet` / "greenlet_spawn has not been called":** an implicit lazy load or expired attribute was touched in async code. Add `selectinload`/`joinedload`, keep `expire_on_commit=False`, or `await session.refresh(obj, ["relationship"])`.
- **422 you didn't expect:** read `errors[].field`. Common causes are a camelCase/snake_case alias mismatch, a scalar parameter that FastAPI treats as a query parameter when you meant the body, or `extra="forbid"` rejecting a field the client sends.
- **Slow endpoint:** count queries per request (SQLAlchemy event listener or `echo=True` locally) to find N+1, then `EXPLAIN (ANALYZE, BUFFERS)` the slowest query (see the `postgresql` skill).
- **Latency spikes across unrelated endpoints under load:** something is blocking the event loop. Run with `PYTHONASYNCIODEBUG=1` to log slow callbacks, and grep for sync clients in `async def`.
- **`QueuePool limit ... reached` / connection timeouts:** sessions are leaking, or transactions are held open across slow awaits (external HTTP, file uploads). Check `pg_stat_activity` for `idle in transaction`.
- **"prepared statement does not exist" behind PgBouncer:** transaction pooling conflicts with asyncpg's statement cache. Configure the cache as described in the SQLAlchemy asyncpg docs, or enable PgBouncer ≥ 1.21 `max_prepared_statements`.
- Include the request ID in every log line, so you can trace one request from client to database.

## Performance

- **Pool math:** (`pool_size` + `max_overflow`) × processes × replicas must stay below Postgres `max_connections` minus headroom. Add PgBouncer before raising `max_connections`.
- In containers, run one Uvicorn process per container and scale replicas. Use `--workers` on bare VMs.
- List endpoints select only the columns they serialize. Cap page size. Never return unbounded collections.
- Set `statement_timeout` and `idle_in_transaction_session_timeout` on the application database role, so a runaway query can't hold a connection indefinitely.
- Add Redis caching only after measurement shows a hot, expensive, read-mostly query, with keys that include every scoping dimension (user, tenant).

## Security

- **Object-level authorization** on every resource endpoint, with a test that requests another user's resource and gets 404 or 403.
- **Mass assignment:** `extra="forbid"` on input. Ownership and roles come from the principal, never from the body.
- **JWT:** pin algorithms (never accept `none` or follow the token's `alg` blindly) and verify `aud`/`iss`. Use RS256/ES256 with JWKS when more than one service verifies tokens.
- **Secrets:** compare tokens with `secrets.compare_digest`. Store refresh tokens hashed (SHA-256 is fine for high-entropy tokens) and passwords with Argon2.
- **SQL:** bound parameters only. `text()` gets `:params`. Never build SQL with f-strings.
- **Rate limits** on sign-in, token refresh, password reset, OTP, uploads, and content creation, at the edge or with a shared store.
- **Docs exposure:** disable `docs_url`/`openapi_url` in production for private APIs. Generate `openapi.json` in CI from `app.openapi()` instead.
- **Webhooks in:** verify signatures, and deduplicate on the provider's event ID.
- Audit dependencies with `pip-audit` in CI.

## Testing

- Use pytest with the anyio plugin (or pytest-asyncio) and `httpx.AsyncClient(transport=ASGITransport(app=app))`.
- **Use real PostgreSQL** with the same major version and extensions (PostGIS image) via docker compose or testcontainers. Never SQLite, which differs in types, constraints, locking, and has no PostGIS.
- Build the schema by running Alembic migrations once per test session, which tests the migrations too. Run each test inside a transaction that is rolled back (`join_transaction_mode="create_savepoint"`).
- Override auth with `app.dependency_overrides[get_current_user]`.
- Cover per endpoint: the happy path, validation failure (422 with the field), unauthenticated (401), another user's resource (404/403), constraint conflict (409), and pagination boundaries (empty page, last page, invalid cursor).
- In CI: `alembic upgrade head` on an empty database, `alembic check` (no model/migration drift), `ruff check`, `ruff format --check`, and the type checker.

## Production-readiness checklist

- [ ] Every endpoint has explicit request and response schemas. The generated OpenAPI is reviewed.
- [ ] Routers are protected by default. Object-level authorization is tested for every resource.
- [ ] No blocking I/O in `async def`. All outbound HTTP has timeouts.
- [ ] One commit per unit of work in the service, with no commit in dependency teardown.
- [ ] Relationships use `lazy="raise"`. List endpoints eager-load what they serialize.
- [ ] Growing lists use cursor pagination with a capped limit.
- [ ] One Problem Details error format, including 422 and 500. No stack traces in responses.
- [ ] Structured JSON logs with `requestId`. No tokens, passwords, or unnecessary PII.
- [ ] `/health/live` (process up) and `/health/ready` (database reachable). Neither runs migrations.
- [ ] Settings are validated at startup. Secrets come from the environment or a secret manager.
- [ ] Migrations run as a separate release step. `alembic check` is clean.
- [ ] Durable jobs for must-happen side effects, with retries, idempotency, and dead-letter alerting.
- [ ] Rate limits on auth and write-heavy endpoints.
- [ ] Graceful shutdown: lifespan closes HTTP clients and disposes of the engine.
- [ ] Tests run against PostgreSQL + PostGIS in CI.

Attribution

suleymansuleyman
View sourceSee grades on GitHubMore from suleyman →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Clean Code

Pragmatic coding standards - concise, direct, no over-engineering, no unnecessary comments

304955 votes

Browser Extension Developer

Use this skill when developing or maintaining browser extension code in the `browser/` directory, including Chrome/Firefox/Edge compatibility, content scripts, background scripts, or i18n updates.

286712 votes

Seo Optimizer

SEO optimization with keyword analysis, readability assessment, technical validation, content quality. Use for search rankings, blog posts, content audits, or encountering keyword density, readability scores, meta tags, schema markup errors.

2222 votes

Google Official Seo Guide

Official Google SEO guide covering search optimization, best practices, Search Console, crawling, indexing, and improving website search visibility based on official Google documentation

1862 votes

Writing Plans

Use when you have a spec or requirements for a multi-step task, before touching code

2927051 votes
View all in development →