WAP Data Ingestion with Bauplan

VerifiedCaution

Ingest data from S3 into bauplan using the Write-Audit-Publish pattern for safe data loading with quality checks.

Sby Skills Guide Bot
Data & AIIntermediate
7/29/2026
Claude CodeCodex
#data-ingestion#s3#write-audit-publish#data-quality#bauplan

Recommended for

Automated summary

Generates a Python script using the bauplan SDK to safely ingest data from S3 following the Write-Audit-Publish pattern on a temporary branch.

Strengths

  • Enforces a clean Write/Audit/Publish split on an ephemeral branch, never on main
  • Leverages atomic multi-table merges via merge_branch for all-or-nothing loading
  • Exposes explicit parameters for success behavior (inspect/merge) and failure behavior (keep/delete)
  • Ships a complete script template with safety assertions (branch/table must not pre-exist)

Limitations

  • The provided SKILL.md is truncated: the template cuts off mid-way through the Audit phase
  • Specific to bauplan and the Python SDK; does not cover other engines or CLI usage
  • Assumes an S3 source and automatic schema inference, with no detailed handling of transformations or explicit schemas
When to use it

Use when you need to load parquet/csv/jsonl files from S3 into bauplan safely with quality checks before publishing.

When not to use it

Avoid for direct writes to main, non-S3 sources, or workflows outside the bauplan ecosystem.

Security analysis

Caution
Quality score85/100

The skill uses the bauplan SDK to interact with a remote data platform, including creating branches, importing data from S3, and optionally merging to main. These are legitimate operations but involve network access and potential modification of production data. The main concern is the unsanitized f-string SQL query, which is a minor injection risk. No destructive or exfiltrating instructions are present.

Findings
  • •Template builds SQL query using f-string with user-provided table name and namespace, which could allow SQL injection if untrusted input is used.
  • •The script can merge changes to the main branch and delete branches if the user selects those options, potentially modifying production data.
  • •The skill declares Bash as an allowed tool but instructs to avoid CLI commands; no dangerous shell commands are present.
How this analysis is made

Examples

Load parquet files from S3
Ingest s3://my-bucket/events/*.parquet into a table called events_raw in bauplan using the WAP pattern, keeping the branch for inspection on success.
Safe ingestion with auto-merge
Use wap-ingestion to load s3://data-lake/orders/*.jsonl into namespace analytics as orders, and automatically merge to main on success.
Multi-table safe load with failure cleanup
Write a WAP script that ingests sales and returns parquet files from S3 into bauplan, deleting the branch on failure but keeping it open on success.

name: wap-ingestion description: "Ingest data from S3 into bauplan using the Write-Audit-Publish pattern for safe data loading. Use when loading new data from S3, performing safe data ingestion, or when the user mentions WAP, data ingestion, importing parquet/csv/jsonl files, or needs to safely load data with quality checks." allowed-tools:

  • Read
  • Write
  • Glob
  • Grep
  • Bash
  • WebFetch(domain:docs.bauplanlabs.com)

Write-Audit-Publish (WAP) Pattern

Implement WAP by writing a Python script using the bauplan SDK. Do NOT use CLI commands.

The three steps: Write (ingest to temp branch) → Audit (quality checks) → Publish (merge to main)

Branch safety: All operations happen on a temporary branch, NEVER on main. By default, branches are kept open for inspection after success or failure.

Atomic multi-table operations: merge_branch is atomic. You can create or modify multiple tables on a branch, and when you merge, either all changes apply to main or none do. This enables safe multi-table ingestion workflows.

Required User Input

Before writing the WAP script, you MUST ask the user for the following parameters:

  1. S3 path (required): The S3 URI pattern for the source data (e.g., s3://bucket/path/*.parquet)
  2. Table name (required): The name for the target table
  3. On success behavior (optional):
    • inspect (default): Keep the branch open for user inspection before merging
    • merge: Automatically merge to main and delete the branch
  4. On failure behavior (optional):
    • keep (default): Leave the branch open for inspection/debugging
    • delete: Delete the failed branch

WAP Script Template

This is the complete template for WAP:

"""
WAP (Write-Audit-Publish) Template for bauplan data ingestion.

Usage:
    python wap_template.py

Or import and call wap_ingest() with your parameters.
"""
import bauplan
from datetime import datetime


def wap_ingest(
    table_name: str,
    s3_path: str,
    namespace: str = "bauplan",
    on_success: str = "inspect",  # "inspect" (default) or "merge"
    on_failure: str = "keep",  # "keep" (default) or "delete"
):
    """
    Write-Audit-Publish flow for safe data ingestion.

    Args:
        table_name: Target table name
        s3_path: S3 URI pattern (e.g., 's3://bucket/path/*.parquet')
        namespace: Target namespace (default: 'bauplan')
        on_success: "inspect" to keep branch for review, "merge" to auto-merge
        on_failure: "keep" to preserve branch for debugging, "delete" to cleanup

    Returns:
        tuple: (branch_name, success)
    """
    client = bauplan.Client()

    # Generate unique branch name using username
    info = client.info()
    username = info.user.username
    branch_name = f"{username}.wap_{table_name}_{int(datetime.now().timestamp())}"

    success = False
    try:
        # === WRITE PHASE ===
        # 1. Create temporary branch from main (must not exist)
        assert not client.has_branch(branch_name), (
            f"Branch '{branch_name}' already exists - this should be an ephemeral branch"
        )
        client.create_branch(branch_name, from_ref="main")

        # 2. Verify table doesn't exist on branch before creating
        assert not client.has_table(
            table=table_name, ref=branch_name, namespace=namespace
        ), (
            f"Table '{namespace}.{table_name}' already exists on branch - refusing to overwrite"
        )

        # 3. Create table (schema inferred from S3 files)
        client.create_table(
            table=table_name,
            search_uri=s3_path,
            namespace=namespace,
            branch=branch_name,
        )

        # 4. Import data into table
        client.import_data(
            table=table_name,
            search_uri=s3_path,
            namespace=namespace,
            branch=branch_name,
        )

        # === AUDIT PHASE ===
        # 5. Run quality check: verify data was imported
        fq_table = f"{namespace}.{table_name}"
        result = client.query(
            query=f"SELECT COUNT(*) as row_count FROM {fq_table}", ref=branch_name
        )
        row_count = result.column("row_count")[0].as_py()
        assert row_count > 0, "No data was imported"
        print(f"Imported {row_count} rows")

        success = True

        # === PUBLISH PHASE ===
        if on_success == "merge":
            # 6. Merge to main and cleanup
            client.merge_branch(source_ref=branch_name, into_branch="main")
            print(f"Successfully published {table_name} to main")
            client.delete_branch(branch_name)
            print(f"Cleaned up branch: {branch_name}")
        else:
            # Keep branch for inspection
            print(
                f"WAP completed successfully. Branch '{branch_name}' ready for inspection."
            )
            print(
                f"To merge manually: client.merge_branch(source_ref='{branch_name}', into_branch='main')"
            )

    except Exception as e:
        print(f"WAP failed: {e}")
        if on_failure == "delete":
            if client.has_branch(branch_name):
                client.delete_branch(branch_name)
                print(f"Cleaned up failed branch: {branch_name}")
        else:
            print(f"Branch '{branch_name}' preserved for inspection/debugging.")
        raise

    return branch_name, success


if __name__ == "__main__":
    # Example: customize these parameters
    branch, success = wap_ingest(
        table_name="my_table",
        s3_path="s3://my-bucket/data/*.parquet",
        namespace="bauplan",
        on_success="inspect",
        on_failure="keep",
    )

Minimal usage:

from wap_template import wap_ingest

branch, success = wap_ingest(
    table_name="orders",
    s3_path="s3://my-bucket/data/*.parquet",
    namespace="bauplan",
    on_success="inspect",  # or "merge"
    on_failure="keep"      # or "delete"
)

Key SDK Methods

| Method | Description | | ---------------------------------------------- | ----------------------------------------------------- | | bauplan.Client() | Initialize the bauplan client | | client.info() | Get client info; access username via .user.username | | client.create_branch(name, from_ref="main") | Create a new branch from specified ref | | client.has_branch(name) | Check if branch exists | | client.delete_branch(name) | Delete a branch | | client.create_table(table, search_uri, ...) | Create table with schema inferred from S3 | | client.import_data(table, search_uri, ...) | Import data from S3 into table | | client.query(query, ref) | Run SQL query, returns PyArrow Table | | client.merge_branch(source_ref, into_branch) | Merge branch into target | | client.has_table(table, ref, namespace) | Check if table exists on branch |

SDK Reference: For detailed method signatures, check https://docs.bauplanlabs.com/reference/bauplan

Workflow Checklist

Copy and track progress:

WAP Progress:

  • [ ] Ask user for: S3 path, table name, on_success, on_failure
  • [ ] Write script using wap_template.py
  • [ ] Run script: python wap_script.py
  • [ ] Verify output shows row count > 0
  • [ ] If on_success="inspect": confirm branch ready for review
  • [ ] If on_success="merge": confirm merge to main succeeded

Example Output

Successful run (on_success="inspect"):

$ python wap_script.py
Imported 15234 rows
WAP completed successfully. Branch 'alice.wap_orders_1704067200' ready for inspection.
To merge manually: client.merge_branch(source_ref='alice.wap_orders_1704067200', into_branch='main')

Successful run (on_success="merge"):

$ python wap_script.py
Imported 15234 rows
Successfully published orders to main
Cleaned up branch: alice.wap_orders_1704067200

Failed run (on_failure="keep"):

$ python wap_script.py
WAP failed: No data was imported
Branch 'alice.wap_orders_1704067200' preserved for inspection/debugging.

WAP on Existing Tables

To append data to an existing table, skip create_table and only call import_data:

# Table already exists on main - just import new data
client.import_data(
    table=table_name,
    search_uri=s3_path,
    namespace=namespace,
    branch=branch_name
)

This appends rows to the existing table schema. The audit and publish phases remain the same: the new rows are automatically sandboxed on the branch until merged.

CLI Merge After Inspection

When on_success="inspect" (default), the branch is left open for user review. If the user asks to merge after inspecting the data, use the CLI:

# 1. Checkout to main first (required before merging)
bauplan checkout main

# 2. Merge the WAP branch into main
bauplan branch merge <username>.wap_<table_name>_<timestamp>

# 3. Optionally delete the branch after successful merge
bauplan branch rm <username>.wap_<table_name>_<timestamp>

Note: You must be on main to run bauplan branch merge. The branch name is printed by the WAP script upon completion.

From the file to a first result

The download contains exactly the content displayed on this page. This copy may differ from the latest version in the source repository.

View the source and supporting files

This download contains SKILL.md only. Any scripts, references, templates and dependencies are not included. Get the required files from the source, preserving their directory structure, before continuing.

1. Download the file

Download SKILL.md

From the root of a test project, save the file as .skill-downloads/wap-data-ingestion-with-bauplan-ms5lrgik/SKILL.md, or use the command below. This folder is for inspecting the skill before installation.

Command for macOS/Linux or WSL: requires a POSIX shell and curl. On Windows without WSL, use the download button and create the folder manually.

mkdir -p '.skill-downloads/wap-data-ingestion-with-bauplan-ms5lrgik' && curl --fail --show-error --location 'https://skills-guides.com/api/skills/wap-data-ingestion-with-bauplan-ms5lrgik/download' --output '.skill-downloads/wap-data-ingestion-with-bauplan-ms5lrgik/SKILL.md.part' && mv '.skill-downloads/wap-data-ingestion-with-bauplan-ms5lrgik/SKILL.md.part' '.skill-downloads/wap-data-ingestion-with-bauplan-ms5lrgik/SKILL.md'

2. Check prerequisites

Read SKILL.md before using it. If it references scripts or external services, inspect them and prepare the required access in your test environment.

No recognized prerequisites section in this file. This does not mean it is self-contained: libraries, tools and access requirements may be mentioned elsewhere in the content or source repository.

The platforms on this page are catalog indications. The paths below follow tool documentation; they do not prove this skill has been run in those tools. No minimum tool version is confirmed for this listing.

3. Load the skill

After inspection, copy the complete folder into your project using one of the commands below, then open a new tool session. The command stops if a folder with the same name already exists: compare the files before replacing it.

Claude Code
test -f '.skill-downloads/wap-data-ingestion-with-bauplan-ms5lrgik/SKILL.md' && test -s '.skill-downloads/wap-data-ingestion-with-bauplan-ms5lrgik/SKILL.md' && mkdir -p '.claude/skills' && test ! -e '.claude/skills/wap-ingestion' && cp -R '.skill-downloads/wap-data-ingestion-with-bauplan-ms5lrgik' '.claude/skills/wap-ingestion'

Codex
test -f '.skill-downloads/wap-data-ingestion-with-bauplan-ms5lrgik/SKILL.md' && test -s '.skill-downloads/wap-data-ingestion-with-bauplan-ms5lrgik/SKILL.md' && mkdir -p '.agents/skills' && test ! -e '.agents/skills/wap-ingestion' && cp -R '.skill-downloads/wap-data-ingestion-with-bauplan-ms5lrgik' '.agents/skills/wap-ingestion'

OpenSkills

OpenSkills must already be installed. The local install/read procedure is checked with OpenSkills 1.5.0, which requires Node.js ≥ 20.6.0. Catalog slugs are not resolved automatically.

Official OpenSkills installation
openskills install './.skill-downloads/wap-data-ingestion-with-bauplan-ms5lrgik'

openskills read 'wap-data-ingestion-with-bauplan-ms5lrgik'

Reading should display the installed file content and its base directory. It does not run the skill or install its dependencies.

For an agent that reads AGENTS.md, then sync the index. This command changes AGENTS.md: review the diff before using it.

openskills sync

Manual method or another tool: attach SKILL.md to a conversation, or open it in an agent that can read files. Include any required supporting resources. No universal folder is recognized by every tool.

4. Try a first task

Use a short, synthetic input: an API endpoint, a small document or an interface brief. In a new conversation, attach the file or give the agent its path, then adapt this request:

Read .skill-downloads/wap-data-ingestion-with-bauplan-ms5lrgik/SKILL.md and apply its instructions to this example: [describe a test input]. Identify any missing files, dependencies or access. Produce the result requested by the skill without inventing data or running unreviewed scripts.

Expected result: output matching the skill instructions, or a precise list of missing prerequisites. A read confirmation alone does not validate a first use. If an external resource is unavailable, that path remains untested.

Related skills