Expedition. Free, virtual, Nov 3–6.

Technical tracks for practitioners, outcomes for leaders.

Snowflake for Developers/Guides/Run Python or Spark Jobs on Snowflake with Code Bundles
Quickstart

Run Python or Spark Jobs on Snowflake with Code Bundles

Gilberto Hernandez, Snowflake CoCo

Overview

Note: Code Bundles are in Public Preview.

You have Python scripts that process data already sitting in Snowflake, but the scripts run somewhere else: on a VM, a cron server, a notebook host, or a Spark cluster you provision and pay for whether it's running or not. You could rewrite everything as stored procedures, but that means matching handler signatures, re-declaring packages, and splitting a project that works as a unit into separate database objects.

In this Quickstart, we'll skip all of that. We'll take a clickstream pipeline as-is – a Python sessionizer, a PySpark analytics job, shared helpers – package it as a single Code Bundle, and run each file on Snowflake. The Python job runs on a warehouse. The PySpark job runs as a native Spark job, with no Spark cluster required. By the end, you'll have the pipeline scheduled on a Task and producing fictional funnel metrics from ~50K clickstream events.

What You'll Learn

  • How to package a project as a Code Bundle and run it on Snowflake compute
  • How to run a Python job on a warehouse with EXECUTE CODE BUNDLE
  • How to override a bundle's specification at execution time with WITH SPECIFICATION
  • How to submit a PySpark job via the REST API, SQL, or from a stage – no Spark cluster required
  • How to orchestrate a multi-stage pipeline with a Snowflake Task
  • How to version, run asynchronously, and monitor your jobs in production

What You'll Need

  • A Snowflake account with access to Code Bundles, with a role that can create warehouses and databases (for example, ACCOUNTADMIN)
  • The Snowflake CLI:
    pip install snowflake-cli
    # or: uv tool install snowflake-cli
    
  • A configured Snowflake CLI connection (verify with snow connection list)
  • Basic familiarity with Python, PySpark, and SQL

Tip: You can run this Quickstart with Cortex Code, Snowflake's AI coding assistant. Point Cortex Code at this guide and it can execute each step for you, explain what's happening, and help you adapt the pipeline to your own data. You can also use it side-by-side as you work through the guide in your terminal.

Architecture And Project Setup

Duration: 10

Before touching Snowflake, let's understand the pipeline and set up your local workspace.

The Clickstream Pipeline

The project models an e-commerce clickstream pipeline in three stages:

Raw Events (~50K)           SESSIONS (~12K)                FUNNEL_METRICS (8 rows)
(EVENTS table)             (SESSIONS table)               (FUNNEL_METRICS table)
[page_view, cart, ...]  -->  [session_id, user_id,  -->    [step, event_name,
                             duration, revenue, ...]        unique_users, ...]

                             ^^^^^^^^^^^^^^^^^^^^^^        ^^^^^^^^^^^^^^^^^^^^^^^
                             Stage 1: Python               Stage 2: PySpark
                             (warehouse compute)           (serverless Spark)
  • Stage 1 – sessionize.py (Python): Reads raw clickstream events, groups consecutive events from the same user into browsing sessions (using a configurable inactivity timeout, default 30 minutes), computes session-level aggregates (page views, carts, purchases, revenue), and writes a SESSIONS table.
  • Stage 2 – analytics.py (PySpark): Reads SESSIONS, runs an e-commerce funnel analysis using standard PySpark DataFrame operations (groupBy, agg, F.countDistinct), and writes a FUNNEL_METRICS table. This file uses native pyspark.sql imports and runs on Snowflake's native Spark engine.
  • helpers.py: Shared utilities for both jobs – creating a Snowpark or PySpark session, and structured logging.

Clone the Companion Repository

Clone the companion repository and inspect the project structure:

git clone https://github.com/Snowflake-Labs/sfguide-run-python-and-spark-jobs-with-code-bundles.git
cd sfguide-run-python-and-spark-jobs-with-code-bundles
sfguide-run-python-and-spark-jobs-with-code-bundles/
├── setup.sql                 # Database, warehouse, stage, synthetic data
├── teardown.sql              # Clean up all created objects
├── snowflake.yml             # Snowflake CLI project definition (with code_bundle)
└── src/
    ├── bundle.yml             # Bundle specification (entrypoints, env, warehouse)
    ├── requirements.txt       # Dependencies installed into execution environment
    ├── sessionize.py          # Python sessionization job (runs on warehouse)
    ├── analytics.py           # PySpark funnel analysis job (runs on Spark engine)
    └── helpers.py             # Shared session + logging utilities

Notice this is an ordinary Python project. The source files are in src/, with shared code imported across files. You do not need to convert anything into stored procedure handlers or flatten the directory structure.

Environment Setup

Duration: 10

Run setup.sql to create the database, warehouse, and stage, and generate ~50K synthetic clickstream events. You can run it with the Snowflake CLI or paste it into a Snowsight worksheet.

Run setup.sql

snow sql -f setup.sql

Or execute each block in Snowsight:

-- Create database, schema, and warehouse
CREATE OR REPLACE DATABASE CODE_BUNDLES_QUICKSTART;
CREATE OR REPLACE SCHEMA CODE_BUNDLES_QUICKSTART.CLICKSTREAM;

CREATE OR REPLACE WAREHOUSE CODE_BUNDLES_WH
  WAREHOUSE_SIZE = 'X-SMALL'
  AUTO_SUSPEND = 60
  AUTO_RESUME = TRUE;

USE DATABASE CODE_BUNDLES_QUICKSTART;
USE SCHEMA CLICKSTREAM;
USE WAREHOUSE CODE_BUNDLES_WH;

-- Stage for the code bundle artifacts
CREATE OR REPLACE STAGE BUNDLE_STAGE
  DIRECTORY = (ENABLE = TRUE);

-- Generate ~50K synthetic clickstream events across 2,500 users
CREATE OR REPLACE TABLE EVENTS AS
WITH RECURSIVE
  users AS (
    SELECT
      SEQ4() AS user_id,
      'user_' || LPAD(SEQ4()::VARCHAR, 6, '0') AS user_handle,
      CASE MOD(SEQ4(), 3)
        WHEN 0 THEN 'mobile'
        WHEN 1 THEN 'desktop'
        ELSE 'tablet'
      END AS device_type,
      CASE MOD(SEQ4(), 4)
        WHEN 0 THEN 'direct'
        WHEN 1 THEN 'organic_search'
        WHEN 2 THEN 'paid_ad'
        ELSE 'social'
      END AS traffic_source
    FROM TABLE(GENERATOR(ROWCOUNT => 2500))
  ),
  event_seq AS (
    SELECT
      SEQ4() AS event_idx,
      MOD(ABS(RANDOM()), 2500) AS user_id,
      -- Spread events over a 7-day window
      TIMESTAMPADD(
        SECOND,
        MOD(ABS(RANDOM()), 7 * 86400),
        '2025-01-01 00:00:00'::TIMESTAMP_NTZ
      ) AS event_time,
      UNIFORM(1, 100, RANDOM()) AS event_roll,
      ROUND(UNIFORM(500, 25000, RANDOM()) / 100.0, 2) AS price
    FROM TABLE(GENERATOR(ROWCOUNT => 50000))
  )
SELECT
  UUID_STRING() AS event_id,
  u.user_handle AS user_id,
  e.event_time,
  CASE
    WHEN e.event_roll <= 50 THEN 'page_view'
    WHEN e.event_roll <= 75 THEN 'product_detail'
    WHEN e.event_roll <= 90 THEN 'add_to_cart'
    WHEN e.event_roll <= 97 THEN 'checkout_start'
    ELSE 'purchase'
  END AS event_name,
  CASE
    WHEN e.event_roll > 75 THEN e.price
    ELSE NULL
  END AS amount,
  u.device_type,
  u.traffic_source
FROM event_seq e
JOIN users u ON e.user_id = u.user_id
ORDER BY e.event_time;

Verify the synthetic data:

SELECT event_name, COUNT(*) AS event_count, ROUND(AVG(amount), 2) AS avg_amount
FROM EVENTS
GROUP BY event_name
ORDER BY event_count DESC;

You should see ~50,000 rows across five event types (page_view, product_detail, add_to_cart, checkout_start, purchase).

Anatomy Of A Code Bundle

Duration: 10

A Code Bundle is defined by two files: snowflake.yml (how Snowflake CLI packages and deploys the project) and src/bundle.yml (the bundle specification that tells Snowflake how to execute it).

snowflake.yml

Open snowflake.yml in the project root:

definition_version: '2'
entities:
  clickstream_bundle:
    type: code_bundle
    identifier:
      name: CLICKSTREAM_BUNDLE
      database: CODE_BUNDLES_QUICKSTART
      schema: CLICKSTREAM
    stage: BUNDLE_STAGE
    spec: bundle.yml
    artifacts:
      - src/*

Key fields:

  • type: code_bundle – identifies this entity as a Code Bundle
  • identifier – the fully qualified database object name (CODE_BUNDLES_QUICKSTART.CLICKSTREAM.CLICKSTREAM_BUNDLE)
  • stage – where the packaged artifacts are staged during deployment
  • spec – path to the bundle specification relative to src/
  • artifacts – files included in the bundle; here, everything in src/

src/bundle.yml

Open src/bundle.yml:

spec_version: 1.0

default:
  entrypoint: sessionize.py
  compute:
    warehouse: CODE_BUNDLES_WH
  runtime:
    language: python
    version: '3.11'
    dependencies:
      requirements: requirements.txt

entrypoints:
  sessionize:
    file: sessionize.py
    compute:
      warehouse: CODE_BUNDLES_WH
    runtime:
      language: python
      version: '3.11'
      dependencies:
        requirements: requirements.txt

  analytics:
    file: analytics.py
    runtime:
      language: python
      version: '3.11'
      dependencies:
        requirements: requirements.txt

Let's break this down:

  1. default block: Specifies what runs if you execute the bundle without naming an entrypoint. It targets sessionize.py on warehouse CODE_BUNDLES_WH.
  2. entrypoints.sessionize: Explicit entrypoint for the Python sessionizer. Notice compute.warehouse – this tells Snowflake to run the script on warehouse compute.
  3. entrypoints.analytics: Entrypoint for the PySpark funnel analysis. Notice there is no compute.warehouse – PySpark jobs run on Snowflake's native Spark engine, which does not require a warehouse or a Spark cluster.
  4. runtime: Declares Python 3.11 and points to requirements.txt. Snowflake installs these dependencies into the job's execution environment automatically.

Inspect the Code

Before deploying, take a quick look at src/sessionize.py and src/analytics.py.

src/sessionize.py uses standard Snowpark Python:

  • Reads EVENTS using the active session
  • Uses window functions (LAG) to detect session breaks when inactivity exceeds the threshold
  • Aggregates events within each session into metrics (page views, carts, purchases, revenue)
  • Writes a SESSIONS table – one row per session with dimensions (device, traffic source) carried forward

The key detail is get_session() in helpers.py. It calls get_active_session(), which returns the Snowpark session that Snowflake injects at runtime when the bundle executes:

# sessionize.py
from helpers import get_session, log_step

def main() -> None:
    args = parse_args()
    session = get_session()

    log_step(f"Sessionizing {args.source_table} (timeout: {args.inactivity_minutes} min)")
    
    session.sql(f"""
        -- Sessionization with window-based inactivity timeout
        ... 
    """).collect()

src/analytics.py uses standard PySpark:

# analytics.py
from pyspark.sql import functions as F
from helpers import get_spark_session, log_step

def main() -> None:
    spark = get_spark_session()
    log_step("Running funnel analysis with PySpark...")

    df = spark.table("CODE_BUNDLES_QUICKSTART.CLICKSTREAM.SESSIONS")
    
    # Funnel analysis using PySpark DataFrame operations
    funnel = df.select(
        F.countDistinct("USER_ID").alias("total_users"),
        F.countDistinct(F.when(F.col("PAGE_VIEWS") > 0, F.col("USER_ID"))).alias("viewed_page"),
        F.countDistinct(F.when(F.col("CART_ADDS") > 0, F.col("USER_ID"))).alias("added_to_cart"),
        F.countDistinct(F.when(F.col("PURCHASES") > 0, F.col("USER_ID"))).alias("purchased"),
    )
    
    funnel.write.mode("overwrite").saveAsTable(
        "CODE_BUNDLES_QUICKSTART.CLICKSTREAM.FUNNEL_METRICS"
    )

Notice from pyspark.sql import functions as F. This is unmodified PySpark code. You do not need to rewrite it with Snowpark DataFrame syntax or configure Spark driver/executor memory.

Deploy The Code Bundle

Duration: 5

Deploy the project to Snowflake using the Snowflake CLI.

Deploy with snow code-bundle

From the project root directory:

snow code-bundle deploy

You should see output similar to:

Uploading artifacts to @CODE_BUNDLES_QUICKSTART.CLICKSTREAM.BUNDLE_STAGE/CLICKSTREAM_BUNDLE/...
Creating or updating code bundle CLICKSTREAM_BUNDLE...
Code bundle CLICKSTREAM_BUNDLE successfully deployed.

What Just Happened?

The CLI did two things:

  1. Packaged the files matching artifacts in snowflake.yml (src/*) and uploaded them to the stage @BUNDLE_STAGE.
  2. Executed a CREATE OR REPLACE CODE BUNDLE statement in Snowflake, registering the bundle with its specification and linking it to the staged files.

Verify the bundle in Snowflake:

USE DATABASE CODE_BUNDLES_QUICKSTART;
USE SCHEMA CLICKSTREAM;

SHOW CODE BUNDLES;

You should see CLICKSTREAM_BUNDLE listed with its database, schema, and owner.

Describe the bundle to inspect its registered specification:

DESCRIBE CODE BUNDLE CLICKSTREAM_BUNDLE;

Run Python Jobs On Warehouse Compute

Duration: 10

Now execute the bundle's Python sessionizer on your warehouse using EXECUTE CODE BUNDLE.

Run the Default Entrypoint

Because bundle.yml declares sessionize.py as the default entrypoint with warehouse compute, you can run the bundle with no extra arguments:

USE DATABASE CODE_BUNDLES_QUICKSTART;
USE SCHEMA CLICKSTREAM;
USE WAREHOUSE CODE_BUNDLES_WH;

EXECUTE CODE BUNDLE CLICKSTREAM_BUNDLE;

Or run it from your terminal using the Snowflake CLI:

snow sql -q "EXECUTE CODE BUNDLE CODE_BUNDLES_QUICKSTART.CLICKSTREAM.CLICKSTREAM_BUNDLE;"

When execution completes, query the generated SESSIONS table:

SELECT
  COUNT(*) AS total_sessions,
  COUNT(DISTINCT user_id) AS unique_users,
  ROUND(AVG(duration_minutes), 1) AS avg_duration_min,
  ROUND(AVG(page_views), 1) AS avg_page_views,
  SUM(has_purchase) AS sessions_with_purchase,
  ROUND(SUM(total_revenue), 2) AS total_revenue
FROM SESSIONS;

You should see ~10K–15K sessions grouped from the ~50K raw events, with metrics for duration, page views, and revenue.

Run a Named Entrypoint

You can explicitly name the entrypoint to run:

EXECUTE CODE BUNDLE CLICKSTREAM_BUNDLE
  ENTRYPOINT = 'sessionize';

Pass CLI Arguments to the Script

The sessionize.py script accepts --inactivity-minutes, --source-table, and --target-table command-line arguments. Pass them using the ARGS parameter:

EXECUTE CODE BUNDLE CLICKSTREAM_BUNDLE
  ENTRYPOINT = 'sessionize'
  ARGS = ('--inactivity-minutes', '45', '--target-table', 'SESSIONS_45MIN');

Verify that the 45-minute timeout produced fewer, longer sessions:

SELECT
  '30-min timeout' AS run_type,
  COUNT(*) AS total_sessions,
  ROUND(AVG(duration_minutes), 1) AS avg_duration_min
FROM SESSIONS
UNION ALL
SELECT
  '45-min timeout' AS run_type,
  COUNT(*) AS total_sessions,
  ROUND(AVG(duration_minutes), 1) AS avg_duration_min
FROM SESSIONS_45MIN;

Override the Specification at Runtime

You can override any part of the bundle specification at execution time with WITH SPECIFICATION. For example, run on a different warehouse or set an environment variable:

EXECUTE CODE BUNDLE CLICKSTREAM_BUNDLE
  ENTRYPOINT = 'sessionize'
  WITH SPECIFICATION = $$
    compute:
      warehouse: CODE_BUNDLES_WH
    env:
      LOG_LEVEL: DEBUG
  $$;

This is powerful for testing: you can point a job at an ad-hoc warehouse or enable debug logging without redeploying the bundle.

Run PySpark Jobs With No Spark Cluster

Duration: 10

Now run the PySpark analytics job from the same bundle. It uses native pyspark.sql imports, runs on Snowflake's managed Spark engine, and requires no Spark cluster to be configured, provisioned, or maintained.

Submit via the Spark REST API

Snowflake provides a Spark-compatible REST API for submitting Spark jobs. You can submit directly with curl.

First, get a session token using the Snowflake CLI:

export SNOWFLAKE_TOKEN=$(snow connection test --format json | python3 -c "import sys, json; print(json.load(sys.stdin).get('token', ''))")

Or generate an authorization token from your current connection. Alternatively, retrieve your account identifier:

export SNOWFLAKE_ACCOUNT=$(snow connection test --format json | python3 -c "import sys, json; print(json.load(sys.stdin).get('account', ''))")

Submit the PySpark job by referencing the staged file from the bundle:

curl -X POST \
  "https://${SNOWFLAKE_ACCOUNT}.snowflakecomputing.com/api/v2/spark/jobs" \
  -H "Authorization: Bearer ${SNOWFLAKE_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "mainApplicationFile": "@CODE_BUNDLES_QUICKSTART.CLICKSTREAM.BUNDLE_STAGE/CLICKSTREAM_BUNDLE/analytics.py",
    "appArgs": [
      "--source-table", "CODE_BUNDLES_QUICKSTART.CLICKSTREAM.SESSIONS",
      "--target-table", "CODE_BUNDLES_QUICKSTART.CLICKSTREAM.FUNNEL_METRICS"
    ],
    "sparkProperties": {
      "spark.snowflake.database": "CODE_BUNDLES_QUICKSTART",
      "spark.snowflake.schema": "CLICKSTREAM"
    }
  }'

The response returns a job ID:

{
  "jobId": "spark-job-01b4c8a2-7e3f-4a1d-9e0a-123456789abc",
  "status": "SUBMITTED"
}

Monitor the Spark Job

Check the job status using the job ID returned above:

curl -X GET \
  "https://${SNOWFLAKE_ACCOUNT}.snowflakecomputing.com/api/v2/spark/jobs/<JOB_ID>" \
  -H "Authorization: Bearer ${SNOWFLAKE_TOKEN}"

The status transitions from SUBMITTEDRUNNINGSUCCEEDED.

Run the Spark Entrypoint via SQL

You can also execute the Spark entrypoint directly from SQL using EXECUTE CODE BUNDLE:

USE DATABASE CODE_BUNDLES_QUICKSTART;
USE SCHEMA CLICKSTREAM;

EXECUTE CODE BUNDLE CLICKSTREAM_BUNDLE
  ENTRYPOINT = 'analytics';

Verify Funnel Metrics

Once the PySpark job completes, query the FUNNEL_METRICS table:

SELECT
  step_number,
  step_name,
  unique_users,
  conversion_rate_pct,
  dropoff_rate_pct
FROM FUNNEL_METRICS
ORDER BY step_number ASC;

You should see the full e-commerce funnel calculated by PySpark:

+-------------+------------------+--------------+---------------------+------------------+
| STEP_NUMBER | STEP_NAME        | UNIQUE_USERS | CONVERSION_RATE_PCT | DROPOFF_RATE_PCT |
+-------------+------------------+--------------+---------------------+------------------+
|           1 | Homepage / Land  |         2500 |              100.00 |             0.00 |
|           2 | Product Detail   |         2180 |               87.20 |            12.80 |
|           3 | Add to Cart      |         1425 |               57.00 |            34.63 |
|           4 | Checkout Start   |          710 |               28.40 |            50.18 |
|           5 | Purchase         |          380 |               15.20 |            46.48 |
+-------------+------------------+--------------+---------------------+------------------+

The PySpark job ran natively on Snowflake compute, read from a Snowflake table, processed data using the Spark DataFrame API, and wrote the results back – with zero Spark infrastructure for you to manage.

Orchestrate With Snowflake Tasks

Duration: 10

Now connect both stages into an automated, scheduled pipeline using Snowflake Tasks. The first task runs the Python sessionizer; the second runs the PySpark funnel analytics upon completion.

Create the Task Graph

USE DATABASE CODE_BUNDLES_QUICKSTART;
USE SCHEMA CLICKSTREAM;
USE WAREHOUSE CODE_BUNDLES_WH;

-- Root task: Run Python sessionizer on a daily schedule
CREATE OR REPLACE TASK CLICKSTREAM_SESSIONIZE_TASK
  WAREHOUSE = CODE_BUNDLES_WH
  SCHEDULE = 'USING CRON 0 2 * * * UTC'  -- Daily at 2:00 AM UTC
AS
  EXECUTE CODE BUNDLE CLICKSTREAM_BUNDLE
    ENTRYPOINT = 'sessionize';

-- Child task: Run PySpark funnel analysis after sessionization completes
CREATE OR REPLACE TASK CLICKSTREAM_ANALYTICS_TASK
  AFTER CLICKSTREAM_SESSIONIZE_TASK
AS
  EXECUTE CODE BUNDLE CLICKSTREAM_BUNDLE
    ENTRYPOINT = 'analytics';

Notice the child task CLICKSTREAM_ANALYTICS_TASK does not specify a WAREHOUSE – the Spark engine manages its own serverless compute.

Test the Pipeline Manually

Resume both tasks (tasks are created in a suspended state by default) and trigger an immediate run:

-- Child tasks must be resumed before root tasks
ALTER TASK CLICKSTREAM_ANALYTICS_TASK RESUME;
ALTER TASK CLICKSTREAM_SESSIONIZE_TASK RESUME;

-- Trigger an immediate manual run of the root task
EXECUTE TASK CLICKSTREAM_SESSIONIZE_TASK;

Monitor Task Execution

Track the execution of both tasks through the task graph history:

SELECT
  name,
  state,
  scheduled_time,
  completed_time,
  TIMESTAMPDIFF('second', scheduled_time, completed_time) AS duration_seconds,
  error_message
FROM TABLE(INFORMATION_SCHEMA.TASK_HISTORY(
  TASK_NAME => 'CLICKSTREAM_SESSIONIZE_TASK',
  SCHEDULED_TIME_RANGE_START => DATEADD('hour', -1, CURRENT_TIMESTAMP())
))
ORDER BY scheduled_time DESC;

SELECT
  name,
  state,
  scheduled_time,
  completed_time,
  TIMESTAMPDIFF('second', scheduled_time, completed_time) AS duration_seconds,
  error_message
FROM TABLE(INFORMATION_SCHEMA.TASK_HISTORY(
  TASK_NAME => 'CLICKSTREAM_ANALYTICS_TASK',
  SCHEDULED_TIME_RANGE_START => DATEADD('hour', -1, CURRENT_TIMESTAMP())
))
ORDER BY scheduled_time DESC;

You'll see CLICKSTREAM_SESSIONIZE_TASK enter SUCCESS, which immediately triggers CLICKSTREAM_ANALYTICS_TASK to run.

Production Patterns: Versioning, Async, Monitoring

Duration: 10

Here are three essential patterns for running Code Bundles in production.

Pattern 1: Versioning and Aliases

Code Bundles support versioning and aliases, allowing you to deploy new code without breaking downstream consumers or tasks.

# Deploy a new version
snow code-bundle deploy --version v1.1.0

# Or deploy with an alias
snow code-bundle deploy --version v1.1.0 --alias prod

In SQL, you can execute a specific version or alias:

-- Execute a specific version
EXECUTE CODE BUNDLE CLICKSTREAM_BUNDLE
  VERSION = 'v1.1.0';

-- Execute via alias
EXECUTE CODE BUNDLE CLICKSTREAM_BUNDLE
  ALIAS = 'prod';

Manage versions with SQL DDL:

-- List registered versions
SHOW VERSIONS IN CODE BUNDLE CLICKSTREAM_BUNDLE;

-- Point the prod alias to a different version (instant rollback)
ALTER CODE BUNDLE CLICKSTREAM_BUNDLE
  SET ALIAS prod = 'v1.0.0';

-- Drop an old version
ALTER CODE BUNDLE CLICKSTREAM_BUNDLE
  DROP VERSION 'v0.9.0';

Your Snowflake Tasks can point to ALIAS = 'prod', giving you zero-downtime updates and one-statement rollbacks.

Pattern 2: Asynchronous Execution and Status Checking

For long-running jobs, execute the bundle asynchronously and poll for completion:

snow sql -q "EXECUTE CODE BUNDLE CODE_BUNDLES_QUICKSTART.CLICKSTREAM.CLICKSTREAM_BUNDLE;" --async

The --async flag returns a Query ID immediately:

Query ID: 01b4c8a2-0001-2345-0000-123456789abc
Status: RUNNING

Check the status of an in-flight job using the Query ID:

SELECT
  query_id,
  query_text,
  execution_status,
  warehouse_name,
  start_time,
  end_time,
  total_elapsed_time / 1000 AS elapsed_seconds,
  error_code,
  error_message
FROM TABLE(INFORMATION_SCHEMA.QUERY_HISTORY())
WHERE query_id = '01b4c8a2-0001-2345-0000-123456789abc';

Pattern 3: Centralized Monitoring and Query History

Because EXECUTE CODE BUNDLE runs on Snowflake compute, every execution appears in standard Snowflake governance views:

-- Find all Code Bundle executions in the last 24 hours
SELECT
  query_id,
  user_name,
  warehouse_name,
  execution_status,
  start_time,
  end_time,
  ROUND(total_elapsed_time / 1000.0, 1) AS elapsed_sec,
  ROUND(compilation_time / 1000.0, 1) AS compile_sec,
  ROUND(execution_time / 1000.0, 1) AS exec_sec
FROM TABLE(INFORMATION_SCHEMA.QUERY_HISTORY(
  END_TIME_RANGE_START => DATEADD('hour', -24, CURRENT_TIMESTAMP())
))
WHERE query_text ILIKE '%EXECUTE CODE BUNDLE%'
ORDER BY start_time DESC;

You get complete auditability, credit attribution, and performance history out of the box – no external monitoring agent required.

Clean Up

Duration: 5

To remove all objects created in this Quickstart, run teardown.sql:

snow sql -f teardown.sql

Or execute in Snowsight:

-- Suspend tasks first
ALTER TASK IF EXISTS CLICKSTREAM_SESSIONIZE_TASK SUSPEND;
ALTER TASK IF EXISTS CLICKSTREAM_ANALYTICS_TASK SUSPEND;

-- Drop all objects
DROP DATABASE IF EXISTS CODE_BUNDLES_QUICKSTART;
DROP WAREHOUSE IF EXISTS CODE_BUNDLES_WH;

Conclusion And Resources

Duration: 5

Congratulations! You've successfully packaged and executed a multi-stage data pipeline on Snowflake using Code Bundles.

What You Learned

  • How to package a multi-file Python project into a Code Bundle using the Snowflake CLI
  • How to define entrypoints, dependencies, and compute targets in bundle.yml
  • How to run Python jobs on a warehouse with EXECUTE CODE BUNDLE and pass dynamic arguments
  • How to override specifications at runtime with WITH SPECIFICATION
  • How to run PySpark jobs natively on Snowflake with no Spark cluster or driver/executor sizing
  • How to chain Python and PySpark bundle executions into a scheduled pipeline with Snowflake Tasks
  • How to apply production patterns: versioning with aliases, asynchronous runs, and centralized observability

Related Resources

Updated Sep 21, 2026

This content is provided as is, and is not maintained on an ongoing basis. It may be out of date with current Snowflake instances