Skip to content
Back to skills

1507 Etl Patterns C0d97b2d

ASecurity

| Pattern | When to Use | | ---------------------- | ---------------------------------------------------- | | File import assets | Loading CSVs, Parquet, JSON from local/cloud storage | | API resource + assets | Fetching data from REST APIs | | dlt integration | Embedded ETL with schema inference and pagination | | Sling integration | Database-to-database replication | | Config

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 11, 2026
databasespythongobashgitapidatabasedocumentation

Works with

  • cli
  • api

Security analysis

A100/100

Scanned October 11, 2026

npx -y skills add tools-only/X-Skills --skill 1507-etl-patterns_c0d97b2d --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 1507 Etl Patterns C0d97b2d?

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

Security grade badge for 1507 Etl Patterns C0d97b2d
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-1507-etl-patterns-c0d97b2d/badge)](https://www.skillsdirectory.com/skills/tools-only-1507-etl-patterns-c0d97b2d)

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

SKILL.md
# ETL Patterns Reference

## Pattern Summary

| Pattern                | When to Use                                          |
| ---------------------- | ---------------------------------------------------- |
| File import assets     | Loading CSVs, Parquet, JSON from local/cloud storage |
| API resource + assets  | Fetching data from REST APIs                         |
| dlt integration        | Embedded ETL with schema inference and pagination    |
| Sling integration      | Database-to-database replication                     |
| Configurable ingestion | Dynamic file/endpoint selection at runtime           |

---

## File Import Pattern

### Basic File Import with Config

```python
import dagster as dg
from pathlib import Path

class IngestionFileConfig(dg.Config):
    path: str

@dg.asset
def import_file(config: IngestionFileConfig) -> str:
    """Resolve file path from config."""
    file_path = Path(__file__).parent / f"../../../data/source/{config.path}"
    return str(file_path.resolve())
```

### Load File into Database

```python
from dagster_duckdb import DuckDBResource

@dg.asset(kinds={"duckdb"})
def raw_data_table(
    context: dg.AssetExecutionContext,
    database: DuckDBResource,
    import_file: str,
) -> None:
    """Load CSV file into DuckDB table."""
    table_name = "raw_data"

    with database.get_connection() as conn:
        # Create table if not exists
        conn.execute(f"""
            CREATE TABLE IF NOT EXISTS {table_name} (
                date DATE,
                value FLOAT,
                category VARCHAR
            )
        """)

        # Load data using COPY
        conn.execute(f"COPY {table_name} FROM '{import_file}'")
```

### Running with Config

**CLI:**

```bash
dg launch --assets import_file,raw_data_table \
  --config-json '{"ops": {"import_file": {"config": {"path": "2024-01-01.csv"}}}}'
```

**UI YAML:**

```yaml
ops:
  import_file:
    config:
      path: 2024-01-01.csv
```

---

## API Integration Pattern

### API Resource

```python
import dagster as dg
import requests

class NASAResource(dg.ConfigurableResource):
    api_key: str
    base_url: str = "https://api.nasa.gov"

    def get_near_earth_asteroids(self, start_date: str, end_date: str) -> list:
        url = f"{self.base_url}/neo/rest/v1/feed"
        params = {
            "start_date": start_date,
            "end_date": end_date,
            "api_key": self.api_key,
        }

        response = requests.get(url, params=params)
        response.raise_for_status()
        return response.json()["near_earth_objects"][start_date]
```

### API Asset

```python
@dg.asset
def asteroid_data(
    context: dg.AssetExecutionContext,
    nasa: NASAResource,
) -> list[dict]:
    """Fetch asteroid data from NASA API."""
    data = nasa.get_near_earth_asteroids("2024-01-01", "2024-01-07")
    context.log.info(f"Retrieved {len(data)} asteroids")
    return data
```

### Register API Resource

```python
@dg.definitions
def resources():
    return dg.Definitions(
        resources={
            "nasa": NASAResource(
                api_key=dg.EnvVar("NASA_API_KEY"),
            ),
        },
    )
```

---

## dlt Integration

dlt (data load tool) handles schema inference, pagination, and loading automatically.

### Basic dlt Pipeline

```python
import dlt

@dlt.source
def simple_source():
    @dlt.resource
    def load_dict():
        data = [
            {"id": 1, "name": "Alice"},
            {"id": 2, "name": "Bob"},
        ]
        yield data

    return load_dict

# Standalone execution
pipeline = dlt.pipeline(
    pipeline_name="simple_pipeline",
    destination="duckdb",
    dataset_name="mydata",
)
load_info = pipeline.run(simple_source())
```

### dlt with Dagster

```python
import dagster as dg
import dlt
from dagster_dlt import DagsterDltResource, dlt_assets

@dlt.source
def simple_source():
    @dlt.resource
    def load_dict():
        data = [
            {"id": 1, "name": "Alice"},
            {"id": 2, "name": "Bob"},
        ]
        yield data

    return load_dict

@dlt_assets(
    dlt_source=simple_source(),
    dlt_pipeline=dlt.pipeline(
        pipeline_name="simple_pipeline",
        dataset_name="simple",
        destination="duckdb",
        progress="log",
    ),
)
def dlt_assets(context: dg.AssetExecutionContext, dlt: DagsterDltResource):
    yield from dlt.run(context=context)
```

### dlt Resource Registration

```python
from dagster_dlt import DagsterDltResource

@dg.definitions
def resources():
    return dg.Definitions(
        resources={
            "dlt": DagsterDltResource(),
        },
    )
```

### dlt Benefits

- **Schema inference**: Automatically detects and creates table schemas
- **Type inference**: Maps Python types to database types
- **Pagination handling**: Built-in support for paginated APIs
- **Incremental loading**: Track state for incremental updates
- **Multiple destinations**: DuckDB, Snowflake, BigQuery, Postgres, etc.

---

## Sling Integration

Sling is declarative database-to-database replication.

### Sling Connection Resources

```python
from dagster_sling import SlingConnectionResource, SlingResource

# Source database
source = SlingConnectionResource(
    name="MY_POSTGRES",
    type="postgres",
    host="localhost",
    port=5432,
    database="source_db",
    user="user",
    password="password",
)

# Destination database
destination = SlingConnectionResource(
    name="MY_DUCKDB",
    type="duckdb",
    connection_string="duckdb:///data/staging/data.duckdb",
)

# Combine into SlingResource
sling = SlingResource(
    connections=[source, destination],
)
```

### Sling Replication Config

```yaml
# sling_replication.yaml
source: MY_POSTGRES
target: MY_DUCKDB

defaults:
  mode: full-refresh
  object: "{stream_schema}_{stream_table}"

streams:
  data.customers:
  data.products:
  data.orders:
```

### Sling Replication Modes

| Mode           | Description                              |
| -------------- | ---------------------------------------- |
| `full-refresh` | Drop and recreate table each run         |
| `incremental`  | Append new records                       |
| `truncate`     | Truncate table before loading            |
| `snapshot`     | Full refresh with point-in-time snapshot |

### Sling Assets

```python
from dagster_sling import SlingResource, sling_assets

replication_config = dg.file_relative_path(__file__, "sling_replication.yaml")

@sling_assets(replication_config=replication_config)
def postgres_sling_assets(context: dg.AssetExecutionContext, sling: SlingResource):
    yield from sling.replicate(context=context).fetch_column_metadata()
```

### Register Sling Resource

```python
@dg.definitions
def resources():
    return dg.Definitions(
        resources={
            "sling": sling,
        },
    )
```

---

## Choosing ETL Approach

| Scenario                      | Recommended Approach  |
| ----------------------------- | --------------------- |
| Load local files              | File import pattern   |
| Custom REST API               | API resource + assets |
| Standard API (Stripe, GitHub) | dlt verified sources  |
| Schema-on-read ingestion      | dlt                   |
| Database replication          | Sling                 |
| Complex transformations       | Custom assets or dbt  |

---

## Partitioned ETL

### Partitioned File Import

```python
daily_partition = dg.DailyPartitionsDefinition(start_date="2024-01-01")

@dg.asset(partitions_def=daily_partition)
def daily_import(context: dg.AssetExecutionContext) -> str:
    partition_date = context.partition_key  # "2024-01-01"
    file_path = f"data/source/{partition_date}.csv"
    return file_path

@dg.asset(partitions_def=daily_partition)
def daily_load(
    context: dg.AssetExecutionContext,
    database: DuckDBResource,
    daily_import: str,
) -> None:
    partition = context.partition_key

    with database.get_connection() as conn:
        # Delete existing partition data
        conn.execute(f"DELETE FROM raw_data WHERE date = '{partition}'")
        # Load new data
        conn.execute(f"COPY raw_data FROM '{daily_import}'")
```

### Partitioned API Fetch

```python
@dg.asset(partitions_def=daily_partition)
def daily_api_data(
    context: dg.AssetExecutionContext,
    api: MyAPIResource,
) -> list[dict]:
    partition_date = context.partition_key
    return api.fetch_data_for_date(partition_date)
```

---

## Data Validation

### Validate After Load

```python
@dg.asset(deps=["raw_data_table"])
def validated_data(database: DuckDBResource) -> dg.MaterializeResult:
    with database.get_connection() as conn:
        # Check row count
        result = conn.execute("SELECT COUNT(*) FROM raw_data").fetchone()
        row_count = result[0]

        # Check for nulls
        null_check = conn.execute(
            "SELECT COUNT(*) FROM raw_data WHERE value IS NULL"
        ).fetchone()
        null_count = null_check[0]

    return dg.MaterializeResult(
        metadata={
            "row_count": row_count,
            "null_values": null_count,
            "null_percentage": round(null_count / row_count * 100, 2) if row_count > 0 else 0,
        }
    )
```

### Asset Check for Data Quality

```python
@dg.asset_check(asset=raw_data_table)
def no_duplicate_records(database: DuckDBResource) -> dg.AssetCheckResult:
    with database.get_connection() as conn:
        result = conn.execute("""
            SELECT COUNT(*) - COUNT(DISTINCT id) as duplicates
            FROM raw_data
        """).fetchone()
        duplicates = result[0]

    return dg.AssetCheckResult(
        passed=duplicates == 0,
        metadata={"duplicate_count": duplicates},
    )
```

---

## Error Handling

### Retry Pattern

```python
import time

class RobustAPIResource(dg.ConfigurableResource):
    api_key: str
    max_retries: int = 3

    def fetch_with_retry(self, endpoint: str) -> dict:
        for attempt in range(self.max_retries):
            try:
                response = requests.get(endpoint, headers={"Authorization": self.api_key})
                response.raise_for_status()
                return response.json()
            except requests.RequestException as e:
                if attempt == self.max_retries - 1:
                    raise
                time.sleep(2 ** attempt)  # Exponential backoff
```

### Graceful Failure

```python
@dg.asset
def api_data_with_fallback(
    context: dg.AssetExecutionContext,
    api: MyAPIResource,
) -> dg.MaterializeResult:
    try:
        data = api.fetch_data()
        return dg.MaterializeResult(
            metadata={"status": "success", "row_count": len(data)}
        )
    except Exception as e:
        context.log.error(f"API fetch failed: {e}")
        raise  # Let Dagster handle the failure
```

---

## Anti-Patterns to Avoid

| Anti-Pattern         | Better Approach                        |
| -------------------- | -------------------------------------- |
| Hardcoded file paths | Use `Config` for dynamic paths         |
| No schema validation | Add asset checks for data quality      |
| Ignoring pagination  | Use dlt or implement proper pagination |
| Full refresh always  | Consider incremental loading           |
| No retry logic       | Add retries with exponential backoff   |

---

## References

- [dlt Documentation](https://dlthub.com/docs)
- [Sling Documentation](https://docs.slingdata.io/)
- [Dagster dlt Integration](https://docs.dagster.io/integrations/libraries/dlt)
- [Dagster Sling Integration](https://docs.dagster.io/integrations/libraries/sling)
- [ETL Pipeline Tutorial](https://docs.dagster.io/etl-pipeline-tutorial)

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…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.