Python — Data Processing Pipelines
Data Validation with Pydantic in Pipelines
Direct answer
Pydantic gives pipelines typed schema contracts at every boundary: validate raw records as they enter, quarantine failures instead of crashing the whole batch, and pass typed models between stages so downstream code never re-checks fields. With Pydantic v2's Rust-backed core, validation is fast enough for most batch workloads, and the quarantine rate itself becomes your earliest warning that an upstream system changed its schema.
Pipelines don't usually die from exotic bugs — they die from a renamed field, a string where an integer used to be, a null nobody expected. Pydantic turns those surprises from silent corruption into explicit, quarantined, countable events, and this is how I wire it into every pipeline I build.
Key facts, with sources
- JetBrains' State of Python 2025 survey of more than 30,000 developers found that 51% of all Python developers are involved in data exploration and processing, with pandas and NumPy the most commonly used tools. (The JetBrains Blog)
- Apache Airflow reached more than 77,000 organizations using it as of November 2024, up from about 25,000 in 2020, with monthly downloads growing from 888,000 to over 31 million in the same period. (Astronomer State of Airflow 2025)
- The official Apache Airflow 2025 survey collected more than 5,250 responses from 116 countries, and event-driven scheduling introduced in Airflow 3 already showed almost 25% adoption. (Apache Airflow Blog)
- pandas has accumulated roughly 15.3 billion total downloads on PyPI, with around 700 million downloads in a single recent month. (pepy.tech)
- The Rust-based Polars DataFrame library passed 24 million monthly downloads and over 250 million total downloads as of September 2025, five years after its first commit. (Wikipedia)
- Gartner research estimates poor data quality costs organizations an average of at least $12.9 million per year. (Gartner)
Validate at the boundaries, trust the interior
The discipline is simple: every record entering the pipeline from the outside world — an API payload, a Kafka message, a CSV row, a vendor export — passes through a Pydantic model exactly once, at the edge. From that point inward, stages exchange typed models, not dictionaries, so transformation code stops being littered with defensive gets and isinstance checks.
This concentrates all schema knowledge in one place. When a source changes, there is one model to update and one diff to review, instead of a scavenger hunt through every stage that happened to touch the field. In code audits, pipelines that validate everywhere and pipelines that validate nowhere fail the same way — inconsistently. Boundary validation is the maintainable middle.
The quarantine pattern: one bad row never kills the batch
Raising on the first invalid record is the default behavior and the wrong one for pipelines — one malformed row from a vendor shouldn't discard a million good ones. Instead I split every batch into validated models and quarantined rejects, load the good rows, and write rejects to a quarantine table with the original payload and Pydantic's structured errors.
The quarantine count per run becomes a monitored metric with a threshold alert. A slow trickle of rejects is normal life with external data; a sudden spike is almost always an upstream deploy that changed the schema, caught within one run instead of discovered weeks later in an analytics review.
from pydantic import BaseModel, ValidationError, field_validator
class PaymentEvent(BaseModel):
user_id: str
amount_cents: int
currency: str = "USD"
@field_validator("currency")
@classmethod
def normalize_currency(cls, v: str) -> str:
return v.strip().upper()
def validate_batch(
rows: list[dict],
) -> tuple[list[PaymentEvent], list[dict]]:
valid: list[PaymentEvent] = []
quarantined: list[dict] = []
for row in rows:
try:
valid.append(PaymentEvent.model_validate(row))
except ValidationError as e:
quarantined.append({"row": row, "errors": e.errors()})
return valid, quarantinedCoercion and normalization belong in the model
Pydantic's lax mode already coerces the obvious cases — numeric strings to ints, ISO strings to datetimes — which suits messy real-world feeds. Field validators handle the rest: trimming whitespace, uppercasing currency codes, mapping legacy enum values to current ones. Putting normalization in the model means every consumer of the data gets the same cleaned values, instead of each stage re-implementing slightly different cleanup.
Use strict mode selectively where coercion would mask bugs — IDs are the classic case, where an integer silently becoming a string hides a producer-side type change you wanted to know about. The point is deliberateness: every field is either intentionally lax or intentionally strict, not whatever the default happened to be.
Schema evolution without breaking yesterday's data
Pipelines replay history — backfills, dead-letter reprocessing, restated vendor files — so models must accept every schema version still present in your data, not just today's. The practical rules: new fields arrive optional with defaults, removed fields stay tolerated (Pydantic ignores unknown keys by default), and renames are bridged with validation aliases so old and new payloads both parse.
When a source makes a genuinely breaking change, I add an explicit schema version field and a small dispatch that routes payloads to the matching model rather than contorting one model to accept everything. A model that accepts everything validates nothing — versioned models keep each contract honest while the pipeline handles all of history.
Performance: fast enough, if you use it right
Pydantic v2's validation core is implemented in Rust, and for typical batch pipelines validation is a small fraction of runtime next to I/O, parsing, and database writes. The overhead conversations that matter are about usage patterns, not the library. Validate each record once at the boundary — re-validating between internal stages buys nothing. For homogeneous lists, a single TypeAdapter for the list type validates the whole collection in one call, and building the adapter once at module level avoids repeated schema construction.
If profiling ever shows validation dominating a specific hot path — rare, in my experience — narrow the model for that path or drop to lighter checks there, while keeping full validation everywhere else. Don't preemptively strip validation from an entire pipeline to optimize a stage nobody measured.
When to hire senior help
Bring in a senior data engineer when pipeline failures start silently corrupting business metrics, or before committing to an orchestration and warehouse architecture, since storage layout and idempotency decisions are expensive to reverse once terabytes flow through them. A few weeks of experienced design work on schemas, retries, and backfill strategy routinely saves months of firefighting later. If your stack includes React Native + Python + AI, a senior engineer who owns the full product beats coordinating multiple juniors.
Bottom line
Dhairya Senjaliya ships Python — Data Processing Pipelines projects worldwide — book a scoping call to discuss your specific situation.
Common pitfalls to avoid
- ✕Running pipelines as cron jobs plus scripts with no idempotency or retry semantics, so a mid-run failure leaves half-written tables that corrupt downstream reports
- ✕Loading entire datasets into pandas in memory instead of chunking or using Polars or DuckDB, causing out-of-memory crashes the first time data volume grows 10x
- ✕Skipping schema and data-quality checks at ingestion, letting silent schema drift from a source API propagate wrong numbers into dashboards for weeks
- ✕Designing pipelines that cannot deterministically backfill historical data, making every bug fix or logic change a manual one-off reprocessing project
Frequently asked questions
Is Pydantic fast enough for large data pipelines?
For most batch workloads, yes. Pydantic v2's validation core is written in Rust, and in real pipelines validation time is typically dwarfed by I/O, parsing, and database writes. Validate once at the boundary rather than between every stage, and use a TypeAdapter for validating homogeneous lists in one call. Only optimize further if profiling shows validation actually dominating.
How should a pipeline handle records that fail Pydantic validation?
Quarantine them instead of crashing the batch: catch ValidationError per record, write the original payload plus the structured errors to a quarantine table, and continue processing the valid rows. Monitor the quarantine rate with an alert threshold — a sudden spike almost always means an upstream schema change, and the stored payloads let you reprocess after fixing the model.
Should I use Pydantic or pandas for data validation?
They solve different problems. Pydantic validates individual records against a typed schema, which fits streaming and record-oriented ETL where you want per-row quarantine. DataFrame-level checks fit columnar, statistical validation like distribution shifts across a whole batch. Many pipelines use both: Pydantic at ingestion boundaries for structure, plus aggregate checks downstream for data quality.
Do we need an orchestrator like Airflow, or is cron enough?
Cron is fine for one or two independent jobs. Once tasks have dependencies, need retries, backfills, or alerting, an orchestrator pays for itself, which is why Airflow adoption tripled to 77,000+ organizations between 2020 and 2024. Managed options remove most of the operational burden for small teams.
When do we outgrow pandas?
Typically when datasets no longer fit comfortably in one machine's memory or single-threaded transforms become the bottleneck. Polars and DuckDB extend single-machine processing by 10x or more in published benchmarks before you need distributed systems like Spark, which add significant operational cost.
What does bad pipeline engineering actually cost?
Gartner puts the average cost of poor data quality at $12.9 million per year per organization, mostly through bad decisions and wasted rework. For startups the more common cost is losing trust in metrics, which stalls decision-making until someone rebuilds the pipeline with validation and lineage.
Bottom line: Dhairya Senjaliya ships Python — Data Processing Pipelines projects worldwide. Book a scoping call at https://dhairyasenjaliya.com/#book-call.