Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds ➔
👥 Community & Social
YouTube Security VideosTechLinked: MacOS 27 launch ain't looking so good(23.09.2026 um 21:00 Uhr)
•
YouTube Security VideosNeil Patel: Don't Just Be Right. Be Repeatable. #shorts(23.09.2026 um 20:04 Uhr)
••
YouTube Security VideosMicrosoft Mechanics: How to Share a Copilot Agent With Your Team(23.09.2026 um 20:30 Uhr)
••••
Sicherheitslücken (CVE)USN-8806-1: NetworkManager vulnerability(23.09.2026 um 15:24 Uhr)
•
Sicherheitslücken (CVE)USN-8807-1: Open-iSNS vulnerability(23.09.2026 um 19:07 Uhr)
•
Unix & Linux ServerUSN-8808-1: SQL parse vulnerabilities(23.09.2026 um 20:19 Uhr)
•
YouTube Security VideosTechLinked: MacOS 27 launch ain't looking so good(23.09.2026 um 21:00 Uhr)
•
YouTube Security VideosNeil Patel: Don't Just Be Right. Be Repeatable. #shorts(23.09.2026 um 20:04 Uhr)
••
YouTube Security VideosMicrosoft Mechanics: How to Share a Copilot Agent With Your Team(23.09.2026 um 20:30 Uhr)
••••
Sicherheitslücken (CVE)USN-8806-1: NetworkManager vulnerability(23.09.2026 um 15:24 Uhr)
•
Sicherheitslücken (CVE)USN-8807-1: Open-iSNS vulnerability(23.09.2026 um 19:07 Uhr)
•
Unix & Linux ServerUSN-8808-1: SQL parse vulnerabilities(23.09.2026 um 20:19 Uhr)
•
Intelligence View
⚡ tsecurity.de Intelligence

Pydantic V2 Discriminated Unions in FastAPI: Modeling Polymorphic AI Feature Configs Without Schema Sprawl

Pydantic V2 Discriminated Unions in FastAPI: Modeling Polymorphic AI Feature Configs Without Schema Sprawl I've built nine AI features into CitizenApp, and each one has a wildly different configuration shape. A summarization feature…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!




Pydantic V2 Discriminated Unions in FastAPI: Modeling Polymorphic AI Feature Configs Without Schema Sprawl



I've built nine AI features into CitizenApp, and each one has a wildly different configuration shape. A summarization feature needs max_tokens and style. A classifier needs labels and confidence_threshold. A generator needs temperature, system_prompt, and output_format.



For months, I solved this with if/elif chains in my route handlers. It was a disaster. Schema mismatches lived in production. Clients sent invalid configs that slipped past validation. I'd catch them at runtime inside the Claude API call—expensive, embarrassing, and hard to debug.



Then I switched to Pydantic V2's discriminated unions. Now my FastAPI routes are one-liners. My database queries are type-safe. And every schema mismatch gets caught at the HTTP layer, not buried in a traceback three API calls deep.



This is how I'd tell my past self to do it.






The Problem: Polymorphism Without a Type System Is Just if/elif



Here's what my old code looked like:




# ❌ Before: Polymorphism via conditionals
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI()

class FeatureConfig(BaseModel):
feature_type: str
config: dict # God object antipattern

@app.post("/features")
async def create_feature(payload: FeatureConfig):
if payload.feature_type == "summarization":
if "max_tokens" not in payload.config:
raise HTTPException(400, "missing max_tokens")
max_tokens = payload.config["max_tokens"]
# ...
elif payload.feature_type == "classification":
if "labels" not in payload.config:
raise HTTPException(400, "missing labels")
labels = payload.config["labels"]
# ...
else:
raise HTTPException(400, "unknown feature type")






This burns in three ways:





  1. No type safety. payload.config is a dict. The type checker doesn't know what keys exist. You discover missing fields at runtime.


  2. Validation logic lives in handlers. Every endpoint that touches features repeats the same checks. One team member forgets a validation step, and garbage flows into the database.


  3. Clients guess the schema. Your API docs don't tell them what a classification config actually needs. They trial-and-error until something works.






The Solution: Discriminated Unions Force the Shape



Pydantic V2's Discriminator field makes polymorphism a first-class citizen. You define each variant as its own model, tag it with a discriminator field, and Pydantic does the rest:




# ✅ After: Discriminated unions
from pydantic import BaseModel, Field
from typing import Annotated, Literal, Union
from fastapi import FastAPI

app = FastAPI()

# Each feature type is its own model
class SummarizationConfig(BaseModel):
feature_type: Literal["summarization"]
max_tokens: int = Field(gt=0, le=4096)
style: Literal["bullet", "paragraph", "executive"] = "paragraph"

class ClassificationConfig(BaseModel):
feature_type: Literal["classification"]
labels: list[str] = Field(min_items=2, max_items=50)
confidence_threshold: float = Field(ge=0, le=1)
allow_multi_label: bool = False

class GenerationConfig(BaseModel):
feature_type: Literal["generation"]
temperature: float = Field(ge=0, le=2)
system_prompt: str = Field(min_length=10)
output_format: Literal["text", "json", "markdown"] = "text"
max_tokens: int = Field(gt=0, le=8000)

# Union of all variants, discriminated by feature_type
AIFeatureConfig = Annotated[
Union[SummarizationConfig, ClassificationConfig, GenerationConfig],
Field(discriminator="feature_type")
]

@app.post("/features")
async def create_feature(config: AIFeatureConfig):
# config is already the correct type
# Type checker knows exactly what fields exist
if isinstance(config, SummarizationConfig):
print(config.max_tokens) # Type checker sees this exists
print(config.style)
elif isinstance(config, ClassificationConfig):
print(config.labels)
print(config.confidence_threshold)
elif isinstance(config, GenerationConfig):
print(config.temperature)
print(config.system_prompt)






What just happened:





  1. Validation is automatic. Pydantic reads feature_type, routes to the correct model, validates all fields. If a client sends classification with missing labels, they get a 422 response with a clear error message—before your handler runs.


  2. Type narrowing works. Once you check isinstance(config, SummarizationConfig), the type checker knows config.max_tokens exists. No dict casting, no runtime guessing.


  3. The API contract is self-documenting. Your OpenAPI schema now has three distinct input shapes, each with its own validation rules. Clients can read the docs and know exactly what they need.






Real-World: Database Storage and Retrieval



In CitizenApp, feature configs live in PostgreSQL as JSONB. Here's how I handle polymorphic queries:




# models.py
from sqlalchemy import Column, String, JSON
from sqlalchemy.orm import DeclarativeBase

class Base(DeclarativeBase):
pass

class AIFeature(Base):
__tablename__ = "ai_features"

id = Column(String, primary_key=True)
feature_type = Column(String, nullable=False, index=True)
config = Column(JSON, nullable=False) # Stored as JSONB

# crud.py
from sqlalchemy.orm import Session
from pydantic import ValidationError

async def create_feature(db: Session, config: AIFeatureConfig) -> AIFeature:
"""
The discriminated union validated the config shape.
Now we just store it.
"""
db_feature = AIFeature(
id=generate_id(),
feature_type=config.feature_type,
config=config.model_dump() # Always valid
)
db.add(db_feature)
db.commit()
return db_feature

async def get_feature(db: Session, feature_id: str) -> AIFeatureConfig:
"""
Retrieve from DB and re-validate against the union.
"""
row = db.query(AIFeature).filter(AIFeature.id == feature_id).one()

# This will fail loudly if the DB contains a schema mismatch
# (which shouldn't happen, but you catch accidental updates)
config = AIFeatureConfig.model_validate(
{"feature_type": row.feature_type, **row.config}
)
return config






Why this matters: If someone accidentally updates the database with a malformed config, model_validate catches it immediately. You don't ship a broken feature to production because a schema migration went sideways.






The Claude Integration: Type-Safe Prompts



Now that config is type-safe, Claude calls are cleaner:




# ai_service.py
import anthropic

async def invoke_feature(config: AIFeatureConfig, input_text: str) -> str:
"""
Type narrowing means we know exactly what fields exist.
No runtime schema lookups, no conditional prompt building.
"""
client = anthropic.Anthropic()

if isinstance(config, SummarizationConfig):
prompt = f"""Summarize the following in {config.style} format.
Max output:
{config.max_tokens} tokens.

{input_text}"""

elif isinstance(config, ClassificationConfig):
prompt = f"""Classify the following text into one of these categories:
{', '.join(config.labels)}

Confidence threshold:
{config.confidence_threshold}
Multi-label allowed:
{config.allow_multi_label}

{input_text}"""

elif isinstance(config, GenerationConfig):
prompt = f"""{config.system_prompt}

User input:
{input_text}

Respond in
{config.output_format} format."""

response = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=config.max_tokens if hasattr(config, "max_tokens") else 1024,
messages=[{"role": "user", "content": prompt}]
)

return response.content[0].text






This is the real win: once validation passes, your business logic doesn't need defensive coding. No checking if fields exist. No runtime type guessing. Just type-safe field access.






Gotcha: The Discriminator Must Be Consistent



I learned this the hard way. If you have nested discriminated unions, the discriminator field must be the same across all levels:




# ❌ This breaks
class OuterConfig(BaseModel):
config_type: Literal["outer"] # Different name!
inner: AIFeatureConfig

# ✅ This works
class OuterConfig(BaseModel):
feature_type: Literal["outer"] # Same discriminator name
inner: AIFeatureConfig






Also: Pydantic uses the discriminator value for routing, so if your models have overlapping Literal values, validation becomes ambiguous. Keep discriminator values unique across your entire union tree.






Missing: Versioning Polymorphic Schemas



I didn't plan for schema evolution. A year in, I needed to add a new field to ClassificationConfig. I should have baked in a schema_version field from the start:




class ClassificationConfig(BaseModel):
feature_type: Literal["classification"]
schema_version: Literal[1] = 1
labels: list[str]
confidence_threshold: float






Then when v2 ships, I can fork the union and migrate gradually.

IR-PLAYBOOK-RCE
HIGH
SOC Incident Playbook: Remote Code Execution (RCE) Defense
1-Click Detection Engineering: Sigma & YARA Rules
SOC Ready
title: Detect Exploitation - Pydantic V2 Discriminated Unions in FastAPI: Modeling Polymorphic AI Feature Configs Without Schema Sprawl
id: c581fc83-ba89-4fd5-9b34-d89b537651c7
status: experimental
description: Automatisch generierte SIEM-Erkennungsregel basierend auf CTI Intelligence
references:
  - https://tsecurity.de/
author: iShareStuff CTI Automated Detection Engine
date: 2026-09-23
logsource:
  category: network_connection
  product: any
detection:
  selection:
      CommandLine|contains:
        - 'exploit'
  condition: selection
falsepositives:
  - Legitime administrative Zugriffe oder Penetrationstests
level: high
tags:
  - attack.initial_access
rule CTI_Threat_Indicator {
    meta:
        author = "iShareStuff CTI Automated Detection Engine"
        date = "2026-09-23"
        description = "YARA Signature for "
    strings:
        $str = "Pydantic V2 Discriminated Unio" ascii wide
    condition:
        any of them
}
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Pydantic V2 Discriminated Unions in FastAPI: Modeling Polymorphic AI Feature Configs Without Schema Sprawl

Thematisch verwandte Begriffe: Pydantic, Discriminated, Unions, FastAPI · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-90904 | Joomla Extension - joomshaper.com - Broken Access Control (ACL Bypass) i…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel • Rechts: nächster Artikel • unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel TTP ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger • Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick