Skip to content
Back to skills

Tpl Ai Ml Data Pipeline Airflow

ASecurity

Template do pack (ai-ml/08-data-pipeline-airflow.md). Orienta o agente em integracao de IA/ML, LLM e pipelines de dados alinhado a esse contexto.

  • 11 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 8, 2026
devopspythonbashsqldockerkubernetesawsdatabasebackendci/cd

Works with

  • cli

Security analysis

A100/100

Scanned September 8, 2026

npx -y skills add artubss/SKILLS-CLAUDE-CODE --skill tpl-ai-ml-data-pipeline-airflow --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Tpl Ai Ml Data Pipeline Airflow?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Tpl Ai Ml Data Pipeline Airflow
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/artubss-tpl-ai-ml-data-pipeline-airflow/badge)](https://www.skillsdirectory.com/skills/artubss-tpl-ai-ml-data-pipeline-airflow)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: tpl-ai-ml-data-pipeline-airflow
description: Template do pack (ai-ml/08-data-pipeline-airflow.md). Orienta o agente em integracao de IA/ML, LLM e pipelines de dados alinhado a esse contexto.
metadata:
  version: 1.0.0
  source_template: ai-ml/08-data-pipeline-airflow.md
  generated_by: install_pack_templates_as_claude_skills
---

# PROJECT: Data Pipeline with Apache Airflow 2.9

Skill gerado a partir do pack `templates-claude-code`. Arquivo de origem: `ai-ml/08-data-pipeline-airflow.md`. Use como baseline e adapte ao projeto antes de mudancas grandes.

## Conteudo do template

## STACK
- **Orchestration:** Apache Airflow 2.9 (Celery executor)
- **Language:** Python 3.12
- **Metadata DB:** PostgreSQL 16
- **Data Storage:** AWS S3 (data lake) + Parquet via pandas/polars
- **Workers:** Celery + Redis broker
- **Secrets:** Airflow Connections (UI) + environment variables
- **Deployment:** Docker Compose (dev) / Helm on Kubernetes (prod)

---

## PROJECT STRUCTURE
```
airflow/
├── dags/
│   ├── etl_customers.py          # Customer data pipeline
│   ├── ml_feature_refresh.py     # ML feature computation
│   ├── daily_report.py           # Business report generation
│   └── utils/
│       ├── s3.py                 # S3 helper operators
│       ├── notifications.py      # Slack/email alerts
│       └── quality.py            # Data quality checks
├── plugins/
│   └── operators/
│       └── postgres_upsert.py   # Custom operator
├── tests/
│   └── test_etl_customers.py    # DAG unit tests
├── Dockerfile
└── docker-compose.yml
```

---

## ARCHITECTURE RULES
1. **DAG = orchestration only** — no data processing logic in the DAG file itself; delegate to functions/operators.
2. **Task atomicity** — each task can succeed/fail independently and be retried safely (idempotent).
3. **XCom sparingly** — XCom for IDs/paths only (< 1KB); never pass dataframes via XCom.
4. **Connections via Airflow UI or env vars** — never hardcode DB strings, S3 keys in DAG files.
5. **Backfill strategy defined upfront** — `catchup=True` only if historical backfill is intended; default `catchup=False`.
6. **SLA miss = alert, not failure** — SLAs send notifications but don't fail the DAG; failures are separate.
7. **Test DAGs without Airflow** — pure Python unit tests on all task functions; `dag.test()` for integration.
8. **Dynamic tasks for fan-out** — `@task.expand` / XCom-driven dynamic task generation instead of branching.

---

## DAG STRUCTURE EXAMPLE

```python
# dags/etl_customers.py
from __future__ import annotations
from datetime import datetime, timedelta
from airflow.decorators import dag, task
from airflow.models import Variable
import logging

logger = logging.getLogger(__name__)

default_args = {
    "owner":             "data-team",
    "retries":           2,
    "retry_delay":       timedelta(minutes=5),
    "retry_exponential_backoff": True,
    "max_retry_delay":   timedelta(minutes=30),
    "email_on_failure":  True,
    "email_on_retry":    False,
    "email":             ["data-team@company.com"],
}

@dag(
    dag_id="etl_customers",
    description="Extract customers from Postgres, transform, load to S3 data lake",
    schedule="0 2 * * *",      # 2 AM UTC daily
    start_date=datetime(2024, 1, 1),
    catchup=False,             # Don't backfill historical runs
    max_active_runs=1,         # Prevent overlapping runs
    default_args=default_args,
    tags=["etl", "customers", "daily"],
    doc_md="""
### Customer ETL
Extracts daily customer snapshot, runs quality checks, loads to S3 as Parquet.
SLA: Must complete by 4 AM UTC.
Owner: #data-team Slack channel.
    """,
    sla_miss_callback=lambda *args: notify_slack_sla_miss(*args),
)
def etl_customers():

    @task(task_id="extract_customers")
    def extract() -> str:
        """Extract from Postgres, upload raw CSV to S3. Returns S3 path."""
        import pandas as pd
        from airflow.providers.postgres.hooks.postgres import PostgresHook
        from utils.s3 import upload_to_s3

        hook = PostgresHook(postgres_conn_id="postgres_prod")  # From Airflow Connections
        df = hook.get_pandas_df("""
            SELECT customer_id, email, created_at, plan, mrr
            FROM customers
            WHERE updated_at >= CURRENT_DATE - 1
        """)

        logger.info(f"Extracted {len(df)} customer records")

        # Push path, not data
        s3_path = f"raw/customers/{datetime.utcnow().strftime('%Y/%m/%d')}/customers.parquet"
        upload_to_s3(df, bucket="my-data-lake", key=s3_path)

        return s3_path   # XCom: just the path string

    @task(task_id="data_quality_check")
    def quality_check(s3_path: str) -> str:
        """Validate data quality. Fails task if critical checks fail."""
        import pandas as pd
        from utils.s3 import read_from_s3
        from utils.quality import run_quality_suite

        df = read_from_s3(bucket="my-data-lake", key=s3_path)
        report = run_quality_suite(df, suite_name="customers")

        if not report.success:
            critical_failures = [c for c in report.results if c.severity == "critical" and not c.passed]
            if critical_failures:
                raise ValueError(f"Critical quality checks failed: {[c.name for c in critical_failures]}")

        return s3_path

    @task(task_id="transform_customers")
    def transform(s3_path: str) -> str:
        """Apply business transformations. Write to curated zone."""
        import polars as pl
        from utils.s3 import read_from_s3_polars, upload_polars_to_s3

        df = read_from_s3_polars(bucket="my-data-lake", key=s3_path)

        transformed = (
            df
            .with_columns([
                pl.col("email").str.to_lowercase().alias("email_normalized"),
                pl.col("mrr").cast(pl.Float64),
                (pl.col("mrr") * 12).alias("arr"),
            ])
            .filter(pl.col("customer_id").is_not_null())
        )

        curated_path = s3_path.replace("raw/", "curated/").replace(".parquet", "_curated.parquet")
        upload_polars_to_s3(transformed, bucket="my-data-lake", key=curated_path)

        return curated_path

    @task(task_id="load_to_warehouse")
    def load(curated_path: str) -> None:
        """Load to data warehouse (Redshift/BigQuery/Snowflake)."""
        from airflow.providers.amazon.aws.hooks.redshift_sql import RedshiftSQLHook
        from utils.s3 import get_s3_url

        hook = RedshiftSQLHook(redshift_conn_id="redshift_prod")
        s3_url = get_s3_url(bucket="my-data-lake", key=curated_path)

        hook.run(f"""
            COPY customers_staging
            FROM '{s3_url}'
            IAM_ROLE 'arn:aws:iam::123456789:role/RedshiftS3Role'
            FORMAT PARQUET;

            BEGIN;
            DELETE FROM customers WHERE customer_id IN (SELECT customer_id FROM customers_staging);
            INSERT INTO customers SELECT * FROM customers_staging;
            TRUNCATE TABLE customers_staging;
            COMMIT;
        """)

    # Task dependencies (linear pipeline)
    raw_path = extract()
    validated_path = quality_check(raw_path)
    curated_path = transform(validated_path)
    load(curated_path)

etl_customers()
```

---

## DYNAMIC TASK GENERATION

```python
# dags/ml_feature_refresh.py — process N tables dynamically
from __future__ import annotations
from airflow.decorators import dag, task

FEATURE_TABLES = ["orders", "sessions", "events", "products"]

@dag(schedule="@hourly", catchup=False, tags=["ml", "features"])
def ml_feature_refresh():

    @task
    def get_tables_to_process() -> list[str]:
        """Could query a config table or return static list."""
        return FEATURE_TABLES

    @task
    def compute_features(table_name: str) -> dict:
        logger.info(f"Computing features for {table_name}")
        # ... feature computation logic ...
        return {"table": table_name, "rows_processed": 1000}

    @task
    def summarize_runs(results: list[dict]) -> None:
        total = sum(r["rows_processed"] for r in results)
        logger.info(f"Feature refresh complete. Total rows: {total}")

    tables = get_tables_to_process()
    results = compute_features.expand(table_name=tables)   # Fan-out!
    summarize_runs(results)

ml_feature_refresh()
```

---

## XCOM LIMITS + RULES
```
XCom storage limit: 48KB (SQLite) / 1GB (PostgreSQL with blob)

✅ Pass via XCom:
  - File paths: "s3://bucket/path/file.parquet" (< 200 chars)
  - IDs: "job-123", "batch-abc"
  - Small counts/stats: {"rows": 1000, "errors": 5}

❌ Never pass via XCom:
  - DataFrames (any size)
  - File contents
  - Large JSON objects (> 10KB)
  - Database connections

Pattern: Tasks write to S3/DB, pass only the path/key via XCom.
```

---

## CONNECTION SECRETS

```bash
# Add connections via environment variables (no UI clicks in CI/CD)
# Format: AIRFLOW_CONN_{UPPER_CONN_ID}=connection-uri

export AIRFLOW_CONN_POSTGRES_PROD="postgresql://user:pass@rds-host:5432/mydb"
export AIRFLOW_CONN_REDSHIFT_PROD="redshift+psycopg2://user:pass@endpoint:5439/mydb"
export AIRFLOW_CONN_AWS_DEFAULT="aws://:@?region_name=us-east-1"  # Uses IAM role

# Or via Airflow Secrets Backend (Secrets Manager):
# In airflow.cfg:
# [secrets]
# backend = airflow.providers.amazon.aws.secrets.secrets_manager.SecretsManagerBackend
# backend_kwargs = {"connections_prefix": "airflow/connections"}
```

---

## SLA MISS ALERT

```python
# dags/utils/notifications.py
from airflow.models import TaskInstance, DagRun
from airflow.utils.email import send_email
import requests

SLACK_WEBHOOK = Variable.get("SLACK_WEBHOOK_URL", default_var=None)

def notify_slack_sla_miss(dag, task_list, blocking_task_list, slas, blocking_tis):
    if not SLACK_WEBHOOK:
        return
    msg = f":warning: SLA Miss: `{dag.dag_id}` — Tasks: {[t.task_id for t in task_list]}"
    requests.post(SLACK_WEBHOOK, json={"text": msg}, timeout=5)
```

---

## DAG UNIT TESTS

```python
# tests/test_etl_customers.py
import pytest
from airflow.models import DagBag

def test_dag_loads_without_errors():
    dagbag = DagBag(dag_folder="dags/", include_examples=False)
    dag = dagbag.get_dag("etl_customers")
    assert dag is not None
    assert len(dagbag.import_errors) == 0

def test_dag_task_count():
    dagbag = DagBag(dag_folder="dags/", include_examples=False)
    dag = dagbag.get_dag("etl_customers")
    assert len(dag.tasks) == 4   # extract, quality_check, transform, load

def test_dag_schedule():
    dagbag = DagBag(dag_folder="dags/", include_examples=False)
    dag = dagbag.get_dag("etl_customers")
    assert dag.schedule_interval == "0 2 * * *"
    assert dag.catchup is False
```

Attribution

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

Loading comments…