DEV Community

Anna lilith
Anna lilith

Posted on

Python Data Pipeline Automation: Build ETL Workflows That Scale

Python Data Pipeline Automation: Build ETL Workflows That Scale

Last updated: July 2026

Data pipelines are the backbone of modern data engineering. Learn how to build robust, scalable ETL workflows in Python.

What is a Data Pipeline?

A data pipeline automates the flow of data from source to destination:

  1. Extract — Pull data from databases, APIs, files, or streams
  2. Transform — Clean, filter, aggregate, and enrich the data
  3. Load — Push the data to a data warehouse, database, or file

Why Python for Data Pipelines?

  • Rich ecosystem — Pandas, NumPy, SQLAlchemy, Airflow
  • Easy to learn — Simple syntax, great documentation
  • Scalable — Works for small scripts to enterprise pipelines
  • Community support — Thousands of packages and tutorials

Building Your First Pipeline

Simple ETL Pipeline

import pandas as pd
from sqlalchemy import create_engine
from datetime import datetime

class DataPipeline:
    def __init__(self, source_db, dest_db):
        self.source = create_engine(source_db)
        self.dest = create_engine(dest_db)

    def extract(self, query):
        """Extract data from source."""
        print(f"[{datetime.now()}] Extracting data...")
        return pd.read_sql(query, self.source)

    def transform(self, df):
        """Transform the data."""
        print(f"[{datetime.now()}] Transforming {len(df)} rows...")

        # Clean data
        df = df.dropna()
        df = df.drop_duplicates()

        # Add computed columns
        df['processed_at'] = datetime.now()
        df['total'] = df['quantity'] * df['price']

        # Filter
        df = df[df['total'] > 0]

        return df

    def load(self, df, table_name):
        """Load data to destination."""
        print(f"[{datetime.now()}] Loading {len(df)} rows to {table_name}...")
        df.to_sql(table_name, self.dest, if_exists='append', index=False)
        print(f"[{datetime.now()}] Done!")

    def run(self, query, table_name):
        """Run the full pipeline."""
        df = self.extract(query)
        df = self.transform(df)
        self.load(df, table_name)

# Usage
pipeline = DataPipeline(
    "postgresql://user:pass@source-db/data",
    "postgresql://user:pass@dest-db/warehouse"
)
pipeline.run("SELECT * FROM raw_sales", "clean_sales")
Enter fullscreen mode Exit fullscreen mode

Scheduling with Cron

# pipeline_scheduler.py
import schedule
import time
from pipeline import DataPipeline

def run_daily_pipeline():
    pipeline = DataPipeline(SOURCE_DB, DEST_DB)
    pipeline.run("SELECT * FROM raw_data WHERE date = CURRENT_DATE", "daily_data")

# Schedule daily at 2 AM
schedule.every().day.at("02:00").do(run_daily_pipeline)

while True:
    schedule.run_pending()
    time.sleep(60)
Enter fullscreen mode Exit fullscreen mode

Error Handling & Retry

import time
from functools import wraps

def retry(max_attempts=3, delay=5):
    """Decorator for retrying failed operations."""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_attempts):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_attempts - 1:
                        raise
                    print(f"Attempt {attempt + 1} failed: {e}")
                    time.sleep(delay)
        return wrapper
    return decorator

@retry(max_attempts=3, delay=10)
def extract_data(query):
    return pd.read_sql(query, engine)
Enter fullscreen mode Exit fullscreen mode

Advanced Patterns

1. Parallel Processing

from concurrent.futures import ThreadPoolExecutor
import pandas as pd

def process_chunk(chunk):
    """Process a data chunk."""
    return chunk.apply(transform_row, axis=1)

def parallel_process(df, n_workers=4):
    """Process DataFrame in parallel."""
    chunks = np.array_split(df, n_workers)

    with ThreadPoolExecutor(max_workers=n_workers) as executor:
        results = list(executor.map(process_chunk, chunks))

    return pd.concat(results)
Enter fullscreen mode Exit fullscreen mode

2. Incremental Loading

def incremental_load(table_name, last_checkpoint):
    """Load only new data since last checkpoint."""
    query = f"""
        SELECT * FROM source_table 
        WHERE updated_at > '{last_checkpoint}'
        ORDER BY updated_at
    """

    df = extract(query)
    if not df.empty:
        load(df, table_name)
        new_checkpoint = df['updated_at'].max()
        save_checkpoint(table_name, new_checkpoint)
Enter fullscreen mode Exit fullscreen mode

3. Data Validation

from pydantic import BaseModel, validator
from datetime import datetime

class SalesRecord(BaseModel):
    product_id: str
    quantity: int
    price: float
    sale_date: datetime

    @validator('quantity')
    def quantity_must_be_positive(cls, v):
        if v <= 0:
            raise ValueError('quantity must be positive')
        return v

    @validator('price')
    def price_must_be_positive(cls, v):
        if v <= 0:
            raise ValueError('price must be positive')
        return v

def validate_dataframe(df):
    """Validate each row in DataFrame."""
    errors = []
    for idx, row in df.iterrows():
        try:
            SalesRecord(**row.to_dict())
        except Exception as e:
            errors.append(f"Row {idx}: {e}")
    return errors
Enter fullscreen mode Exit fullscreen mode

Production Checklist

  • [ ] Error handling and logging
  • [ ] Retry logic for transient failures
  • [ ] Data validation
  • [ ] Incremental loading
  • [ ] Monitoring and alerting
  • [ ] Documentation
  • [ ] Tests

Get the Production-Ready Version

We have a complete data pipeline toolkit at our store.

What's included:

  • Pre-built ETL templates
  • Scheduling utilities
  • Error handling decorators
  • Data validation framework
  • Monitoring dashboard

Browse the collection →

Top comments (0)