Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
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
  • 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

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

Back to skills

Fastapi Pydantic Orm Patterns Skill

ASecurity

Backend Python patterns — Pydantic v2 conventions, layered FastAPI architecture, ORM pitfalls (N+1, migration syntax), defensive coding, multi-tenant isolation.

6 stars
0 votes
0 copies
0 views
Added 9/20/2026
developmentpythongosqlfastapidebugginggitapidatabasebackendsecurity

Works with

cliapi

Security Analysis

A100/100

Scanned 9/20/2026

Install to Claude Code

$npx -y skills add darellchua2/opencode-config-template --skill fastapi-pydantic-orm-patterns-skill --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Fastapi Pydantic Orm Patterns Skill?

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

Security grade badge for Fastapi Pydantic Orm Patterns Skill
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/darellchua2-fastapi-pydantic-orm-patterns-skill/badge)](https://www.skillsdirectory.com/skills/darellchua2-fastapi-pydantic-orm-patterns-skill)

More formats (shields.io, HTML) on the badges page.

Download Zip
Files
SKILL.md
---
name: fastapi-pydantic-orm-patterns-skill
description: >-
  Backend Python patterns — Pydantic v2 conventions, layered FastAPI
  architecture, ORM pitfalls (N+1, migration syntax), defensive coding,
  multi-tenant isolation.
license: Apache-2.0
compatibility: opencode
category: Language-Specific
---

<!-- Provenance: canvastekk-workflow-engine + canvastekk-defect-service LEARNINGS. PLAN-GIT-312. Excludes 12 patterns already in python-backend-skill, design-patterns-skill, object-design-skill. -->

## What I do

I provide battle-tested patterns for FastAPI + Pydantic v2 + SQLAlchemy/asyncpg backends, extracted from production incidents across multiple repos. Each pattern caused a real bug and has a concrete fix.

## When to use me

Use this skill when:
- Writing or reviewing FastAPI endpoints, especially with async sessions
- Configuring Pydantic v2 models, validators, or serializers
- Writing Alembic migrations (especially with asyncpg + JSONB)
- Auditing multi-tenant isolation or auth flows
- Debugging race conditions in state transitions
- Reviewing error handling in service-to-service calls

## Related Skills

- **python-backend-skill**: Project scaffolding. This skill covers the runtime patterns python-backend-skill references.
- **database-migration-skill**: Migration workflow patterns. This skill covers the asyncpg-specific pitfalls.
- **security-audit-skill**: Security auditing. This skill covers the FastAPI-specific auth/encryption patterns.

---

## A. Pydantic v2 Conventions

### A1. Canonical Checklist

Use V2 APIs exclusively — the V1 compatibility layer is deprecated and hides bugs.

| Concern | V2 Correct | V1 Legacy (reject) |
|---------|-----------|-------------------|
| Config | `model_config = ConfigDict(...)` | `class Config:` |
| Extra fields | `extra="forbid"` on API boundary, `"ignore"` on embedded spec | `Extra.ignore` |
| Validators | `@field_validator`, `@model_validator` | `@validator` |
| Serialization | `.model_dump()`, `.model_validate()` | `.dict()`, `.parse_obj()` |
| Computed | `@computed_field` | `@property` only |
| Immutable | `model_config = ConfigDict(frozen=True)` | custom `__setattr__` |
| Serializer | `@field_serializer` / `@model_serializer` | `json_encoders` |
| Type adapter | `TypeAdapter[T]` for non-model validation | custom `parse_*` |
| Polymorphism | `SerializeAsAny[Base]` | manual discriminated unions |
| Rebuild | `model_rebuild()` after forward refs | silent failure |
| Name mode | `validate_by_name=True` (v2.11+) | `Config.allow_population_by_field_name` |
| Init hook | `model_post_init()` | `__init__` override |

### A2. Annotated Type Aliases for Reusable Constraints

```python
from typing import Annotated
from pydantic import Field, AfterValidator

Slug = Annotated[str, Field(min_length=1, max_length=128, pattern=r'^[a-z0-9-]+$')]
UuidStr = Annotated[str, Field(pattern=r'^[0-9a-f]{8}-[0-9a-f]{4}')]
PositiveInt = Annotated[int, Field(gt=0)]

# Composable with AfterValidator
TrimmedSlug = Annotated[Slug, AfterValidator(lambda s: s.strip())]
```

### A3. Drop `Field(...)` Ellipsis

```python
# BAD — ellipsis confuses type checkers, adds nothing
name: str = Field(..., min_length=1)

# GOOD — field without default is already required
name: str = Field(min_length=1)
```

---

## B. FastAPI Architecture

### B1. Layered Architecture (API → Service → Domain → Infra)

Routers must NEVER import DB or Temporal directly.

```
api/routers/     → request parsing, response serialization, HTTP status
services/        → business logic, orchestration, transactions
domain/          → pure models, no I/O, no framework deps
infrastructure/  → DB sessions, external API clients, Temporal workers
```

**Violation signal:** a router file imports `sqlalchemy` or `temporal`.

### B2. Multi-Source Response Schema Drift

When a response schema is constructed from multiple code paths (DB fast path + async slow path), a field added to one path but not the other causes silent `None` in production.

**Fix:** Use a factory/builder that accepts ALL data sources, or enforce a single construction path.

---

## C. ORM & Migration Pitfalls

### C1. Migration Syntax Errors Invisible to pytest

Alembic loads migration files dynamically — `SyntaxError` passes all tests and crashes at deploy.

```python
# tests/test_migrations_compile.py
import py_compile, glob

def test_all_migrations_compile():
    for path in glob.glob("alembic/versions/*.py"):
        py_compile.compile(path, doraise=True)
```

### C2. N+1 Enrichment Queries

```python
# BAD — one query per item
for defect in defects:
    defect.files = await get_files(defect.id)

# GOOD — batch fetch with IN clause
all_files = await get_files_for_ids([d.id for d in defects])
files_by_defect = defaultdict(list)
for f in all_files:
    files_by_defect[f.defect_id].append(f)
for d in defects:
    d.files = files_by_defect.get(d.id, [])
```

### C3. Two-Step Lookup (UUID then slug+is_latest)

Versioned tables with both UUID PK and natural-key slug need a two-step query to avoid `MultipleResultsFound`:

```python
# Step 1: exact ID match (any version)
definition = await session.get(Definition, definition_id)
if not definition:
    # Step 2: slug + is_latest=True fallback
    definition = await session.scalar(
        select(Definition).where(
            Definition.slug == slug, Definition.is_latest.is_(True)
        )
    )
```

---

## D. Defensive Coding

### D1. Broad `except Exception` Masks Bugs

```python
# BAD — swallows programming errors as "unreachable"
try:
    result = await service.call()
except Exception:
    pass  # hides bugs

# GOOD — narrow catches, let bugs propagate
from httpx import ConnectError, TimeoutException
try:
    result = await service.call()
except (ConnectError, TimeoutException):
    retry()
# Programming errors (KeyError, AttributeError) propagate to Sentry
```

**Rule:** Never swallow unexpected exceptions in auth/security paths — fail closed.

### D2. Auth Early-Return on NULL Optional Input

```python
# BAD — skips auth check when account_id is missing
async def handler(account_id: str | None = Header(None)):
    if account_id:
        account = await validate_account(account_id)
    # proceeds without validation if account_id is None

# GOOD — separate null-resource-allow from null-caller-skip
async def handler(account_id: str | None = Header(None)):
    caller = await get_caller()
    if account_id is None:
        return await list_public_resources(caller)
    account = await validate_account(account_id, caller)
    # always validates ownership
```

### D3. Encryption Key Length Validation at Config Time

```python
# BAD — base64-valid but wrong byte length; crashes at first encrypt()
key = base64.b64decode(settings.encryption_key_b64)

# GOOD — validate decoded length at startup
key = base64.b64decode(settings.encryption_key_b64)
if len(key) != 32:
    raise ValueError(f"Expected 32-byte AES-256 key, got {len(key)}")
```

### D4. Fail-Closed/Fail-Open via Single Config Toggle

```python
# One boolean governs the fail branch
if not settings.STRICT_ACCOUNT_VALIDATION:
    # Fail-open: log and proceed (dev/staging)
    logger.warning("Auth service unavailable, failing open")
else:
    # Fail-closed: deny request (production)
    raise HTTPException(403)
```

Reinforce with circuit breaker + positive cache on the external auth service.

---

## E. Multi-Tenant Security

### E1. Missing Tenant Isolation

Every multi-tenant table MUST have `tenant_id` with filtered queries. Metadata leak is still a breach.

```python
# BAD — no tenant filter on a shared table
definitions = await session.execute(select(Definition))

# GOOD — always filter by tenant
definitions = await session.execute(
    select(Definition).where(Definition.tenant_id == caller.tenant_id)
)
```

---

## F. Concurrency & Caching

### F1. Claim-Check Pattern for Ephemeral Secret Caching

When secrets cross durable workflow history (Temporal, Celery), never pass the raw secret. Store in a TTL cache keyed by opaque UUID; pass only the claim ID; `pop()` for single-read semantics.

```python
_secret_cache: dict[str, tuple[bytes, float]] = {}

def store_secret(secret: bytes, ttl: float = 300) -> str:
    claim_id = str(uuid4())
    _secret_cache[claim_id] = (secret, time.monotonic() + ttl)
    return claim_id

def claim_secret(claim_id: str) -> bytes | None:
    entry = _secret_cache.pop(claim_id, None)
    if entry and entry[1] > time.monotonic():
        return entry[0]
    return None
```

**Caveat:** `_secret_cache` is not thread-safe. For multi-worker deployments, wrap access in `threading.Lock` or use a TTL'd Redis key.

### F2. Placeholder Swap for Transport Value Validation

When validating inputs containing URI/ref values (s3://, CDS) that strict JSON Schema rejects:

```python
PLACEHOLDER = "__SCHEMA_PLACEHOLDER__"

def validate_with_swap(data: dict) -> dict:
    originals = {}
    try:
        for key, val in data.items():
            if isinstance(val, str) and "://" in val:
                originals[key] = val
                data[key] = PLACEHOLDER
        validate(data)  # strict schema
        return data
    finally:
        data.update(originals)  # restore originals
```

---

## G. Operational Patterns

### G1. Hardcoded Magic Timeout

```python
# BAD — hardcoded, can't tune per-environment
result = await client.get(url, timeout=30.0)

# GOOD — configurable, per-entity override
result = await client.get(url, timeout=settings.http_timeout)
```

### G2. Inline Imports — Fix Architecture, Don't Hide

```python
# BAD — hides circular dependency
def process(data):
    from app.services import HeavyService  # inline import = smell
    return HeavyService().run(data)

# GOOD — fix the cycle, use TYPE_CHECKING for type-only imports
from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from app.services import HeavyService
```

Attribution

darellchua2darellchua2
View sourceMore from darellchua2 →
SSkills DirectorySkills Directory

Your tool, in front of Claude Code builders.

3 founder slots · $299/mo · GSC-verified traffic · sponsors can never buy grades.

See placements

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

Your tool, in front of Claude Code builders.

3 founder slots · $299/mo · GSC-verified traffic · sponsors can never buy grades.

See placements

Related Skills

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.

281612 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.

2132 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

Tanstack Start

Build a full-stack TanStack Start app on Cloudflare Workers from scratch — SSR, file-based routing, server functions, D1+Drizzle, better-auth, Tailwind v4+shadcn/ui. Use whenever the user mentions TanStack Start, asks to scaffold a full-stack Cloudflare app with SSR, wants an SSR dashboard, or asks for a React 19 + Cloudflare Workers app with file-based routing and server functions — even if they don't name TanStack Start specifically. No template repo — Claude generates every file fresh per ...

9881 votes

Pentest

PTES-aligned adversarial security audit for backend, frontend, and mobile applications. Produces a CVSS-scored Hacker Report with verified PoCs and phased remediation.

5491 votes
View all in development →