Skip to content
Skill · Windsurf.windsurfrulesCoding

Windsurf Rules for Python Data Pipelines

Opinionated rules for pandas/polars pipelines: validate schemas at every boundary, process incrementally by partition, and never mutate a DataFrame in place.

Use when: Use in Python repos that move data through ETL/ELT stages with pandas or polars, especially when silent schema drift or full-table reprocessing has burned you before.

@libraryNewcomer
0 upvotes0 forks0 comments

The skill file

Windsurf · .windsurfrules
.windsurfrules2883 chars
# Python Data Pipeline Rules ## DataFrame conventions - Prefer polars for new code; only use pandas when an upstream library requires it. Never mix both in one module. - All transformations are method chains on a fresh variable — never `inplace=True`, never reassigning columns on a DataFrame received as an argument. - Every pipeline stage is a pure function with the signature `def stage_name(df: pl.DataFrame, config: StageConfig) -> pl.DataFrame`. No stage reads global state or environment variables directly. - Column names are declared once as constants in `columns.py` (e.g. `COL_USER_ID = "user_id"`). String literals for column names in transformation code are a bug. - Never use `apply` with a Python lambda when a vectorized expression exists. If you must use `apply`, add a comment explaining why and the expected row count. ## Schema validation - Every stage boundary validates its input with a pandera (pandas) or explicit `df.schema` assertion (polars) before transforming. Fail fast with the stage name in the error message. - Schemas live in `schemas/` as importable objects, one per dataset, versioned by suffix (`orders_v2`). Changing a schema means adding a new version, not editing the old one. - Nullable columns must be declared nullable in the schema. An unexpected null should raise, not propagate. - After every join, assert the row count matches expectations (`how="left"` joins must not grow the left side unless explicitly documented). Add `validate="m:1"`-style checks where the library supports them. ## Incremental processing - Pipelines process one partition at a time (date, tenant, or batch ID) — never "read the whole table" unless the function name starts with `backfill_`. - Every run writes a watermark (last processed partition) to the state store before exiting successfully; on start, resume from the watermark, never from zero. - Output writes are idempotent: delete-then-insert or overwrite the target partition. Appending without deduplication is forbidden. - Backfill entry points take explicit `--start` and `--end` arguments; no defaults that silently reprocess everything. ## I/O and operations - All file I/O uses Parquet with explicit schemas; CSV is allowed only at ingestion edges and must be parsed with `dtypes` specified, never inferred. - Read paths and write paths come from config objects, never hardcoded strings or f-string bucket paths in stage code. - Log row counts in and out of every stage at INFO level. A stage that drops more than 10% of rows must log the reason at WARNING. - Never `print()` in pipeline code — use the module logger. ## Testing - Every stage gets a unit test with a 3–5 row inline DataFrame covering the happy path, a null edge case, and an empty input. - Schema objects get a test that a known-bad record is rejected. - Run `pytest -q` and `ruff check .` before suggesting any commit.

Install

drop into your repo

Save this to your project or home directory so Windsurf can load it.

path./.windsurfrules
Discussion

What people are saying

Forks

0 forks of this skill
Loading forks…