A Practical Guide to Snowflake Code Bundles — Part 1: Python
A New Way to Run Code on Snowflake
Notebooks are nice. But after writing code for so many years, I often prefer directly doing work in my favorite IDE and enjoy the perks of automatic linting, good formatting, and rapid testing. Furthermore, file-based organization makes it easier to configure environments, compose reusable modules, manage dependencies, and orchestrate more complex logic. They provide the structure and flexibility needed to turn exploratory work into reliable, repeatable automation workloads.
If that’s also your style, there is good news. With Snowflake Code Bundles, you can now directly execute your code files in Snowflake: develop and test code locally, then run it on Snowflake compute. Your code comes to your data, eliminating the need to transfer data out of Snowflake, which offers both scalability and peace of mind.
This article is the first one of a series explaining how Code Bundles works in Snowflake and this one is focused on Python runtimes.
In this article, we’ll cover:
- What is Snowflake Code Bundle
- Code Bundle Observability
- Keeping Code Bundle Versions
- Advanced Code Bundle Patterns
- Schedule Code Bundles as Jobs
What is Snowflake Code Bundle
Snowflake CODE BUNDLE is a new schema-level object type that allows you to package your code, dependencies, and configuration into a portable unit that can be tested locally and executed on Snowflake-managed compute. You can upload your files to a stage, or keep them in a workspace (private or shared). Then, create a CODE BUNDLE object with a unique name.
CREATE OR REPLACE CODE BUNDLE dummy_project
FROM @<database>.<schema>.<stage>/<path_to_project_directory>;
SHOW CODE BUNDLES;Alternatively, create it from workspace (in my example, the workspace name is “Code Bundle Test”, which needs to be wrapped in double quote for qualification due to the spaces in the name:
CREATE OR REPLACE CODE BUNDLE dummy_project
FROM 'snow://workspace/"USER$".PUBLIC."Code Bundle Test"/versions/live/'The above path ending with live/ is the root of the workspace, to specify a lower level path, append the project path to it.
A code bundle object is mapped to a specific path, which can be the project directory holding all of your project files. All of your project files, along with their dependencies, stay together, and you define the entry point. Hence it’s called a code bundle. Your file tree might look like this one:
my_project/
├── code_bundle.yml # runtime config file
├── requirements.txt # dependency
├── helper.py # dependency
├── main.py # entry point
└── README.mdAll of your compute requirements need to be configured in a YAML file and it has to take the name code_bundle.yml —
bundle.typeidentifies the workload pattern: eithersparkfor spark workload orcustomfor other type of workload.compute.typeallows you to choose your ideal compute resources —options includewarehouseorcompute_pool.bundle.languagespecifies the runtime, frompython,java, orscala.compute.optionsprovide more execution details, including the runtime version, warehouse or pool, and related settings.
Below is an example of a starter version code bundle config:
bundle:
type: custom
compute_type: warehouse
language: python
compute_options:
runtime_version: '3.12'Executing a code bundle can be as simple as below:
EXECUTE CODE BUNDLE dummy_project
ENTRYPOINT = 'main.py';You can effectively eliminate runtime spin-up time if you choose to execute in warehouse especially when the warehouse is already warm.
Code Bundle Observability
One thing extremely important to remember is that anything you intend to print in a code bundle will not be surfaced in the SnowSight UI. This is quite different from directly executing a Python file interactively. The old print method will still be executed, making your code portable between different environments, just the results are not displayed anywhere.
To have better observability into the events that happened during the code bundle execution, it’s best to utilize the logging capability which is tightly integrated with Snowflake event table.
1. Logging / Event Table
Logging observability is one of the most useful features in Snowflake. In Python, it supports the standard logging library for telemetry messaging. The results will end up in the event table of your choice (either the account default or your custom database-level event table).
CREATE EVENT TABLE <database>.<schema>.code_bundle_events;
ALTER DATABASE <database>
SET EVENT_TABLE = <database>.<schema>.code_bundle_events;
-- requires MODIFY LOG LEVEL ON ACCOUNT grant
ALTER DATABASE <database>
SET LOG_LEVEL = 'INFO';Include below snippet or alike in the entry file, re-create the code bundle and then execute the code:
# main.py
import logging
import platform
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
if __name__ == "__main__":
version = str(platform.python_version())
logger.info(f"Python Version: {version}")
if not version.startswith('3.12'):
logger.error("Wrong Version!")
raise Exception("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")To query the event table, follow the standard Snowflake event table format to locate the records (the records may take a few minutes to show up):
SELECT
TIMESTAMP AS event_ts,
RESOURCE_ATTRIBUTES:"snow.query.id"::STRING AS query_id,
RECORD:"severity_text"::STRING AS log_level,
VALUE::STRING AS log_value
FROM code_bundle_events
WHERE TIMESTAMP >= DATEADD('hour', -1, CURRENT_TIMESTAMP())
AND RECORD_TYPE = 'LOG';2. Code Bundle Execution History
SNOWFLAKE.INFORMATION_SCHEMA.CODE_BUNDLE_HISTORY is a table function that returns execution history for code bundles. It provides run-level metadata, including bundle identity, entry point, status, timing, runtime and compute type, execution identifiers, and return values. You can apply filters such as bundle name, time range, status, language, etc.
SELECT * FROM TABLE(SNOWFLAKE.INFORMATION_SCHEMA.CODE_BUNDLE_HISTORY(
DATABASE => '<database>',
SCHEMA => '<schema>',
BUNDLE_NAME => 'DUMMY_PROJECT',
RESULT_LIMIT => 20
));Here’s the documentation of how to query the CODE_BUNDLE_HISTORY table.
3. Code Bundle Execution History UI
If you prefer a visualized dashboard view for the above execution history data, it is also readily available in SnowSight UI. Open the left navigation bar, under Catalog > Explorer > choose database > choose schema > then select the code bundle that you need to observe. Navigate the tabs on the right to open its Run History page. The page displays the history in a table format and supports filtering, and provides links to execution details, logs, or query history directly.
Because I have explicitly asked you to re-create the code bundle in prior step, the execution history in both the table function results and in SnowSight UI only returns the execution records after it was re-created. To not lose historical status and insights, we can instead use ADD VERSION to an existing code bundle object, as explained below.
Keeping Code Bundle Versions
Another benefit of file-based automation program is that you get easier version control. Not only you can easily compare code delta in your local IDE or using a git service, you can also keep tracking of the versions of the code bundle objects in Snowflake directly. A Code Bundle version is an immutable snapshot of the bundle’s source files. Create the initial bundle, then add a new snapshot with ALTER CODE BUNDLE ... ADD VERSION FROM ...
ALTER CODE BUNDLE dummy_project
ADD VERSION FROM @<database>.<schema>.<stage>/<new_path>;Or add a new version from workspace:
ALTER CODE BUNDLE dummy_project
ADD VERSION FROM
'snow://workspace/"USER$".PUBLIC.<workspace>/versions/live/<new_path>';There are more benefits than keeping all the history. You can easily deploy changes to the same code bundle object without breaking any existing workflow, while keeping a copy of each version of your project files until you are ready to drop them. Simply organize your project folder like this:
<stage_name>/<project_folder>/V1 # initial version
<stage_name>/<project_folder>/V2 # new version
<stage_name>/<project_folder>/v20260808 # to be very specificThis way you can retain full visibility into the past versions, which makes it super easy to recover a previously working version if anything breaks. To find out the current version and path of your code bundle, simply use:
DESCRIBE CODE BUNDLE dummy_project;Note that even if you didn’t make any changes to the files and deployed from exactly the same path, as long as you use the ADD VERSION method, your code bundle version number metadata still goes up.
Advanced Code Bundle Patterns
1. Use a Container Runtime
Sometimes Snowflake users choose a Compute Pool as runtime because containers let them use an isolated and scalable application environment. You can package the libraries and runtime your code expects, use GPUs when needed, and run substantial Python processing without forcing everything into warehouse execution. It offers flexibility when workloads outgrow SQL-centric patterns.
Get Sabrina Liu’s stories in your inbox
Join Medium for free to get updates from this writer.
To use compute pool runtime in code bundles, a compute pool needs to be provisioned, then we can slightly modify the code_bundle.yml file to include the compute pool configuration. Note that a query warehouse still needs to be specified which is used to to execute queries.
bundle:
type: custom
compute_type: compute_pool
language: python
compute_options:
compute_pool: <compute pool name>
query_warehouse: <warehouse name>
runtime_version: 'V2.5-CPU-PY3.12'As of the date of this article, code bundle only sends telemetry to account-level event table when container runtime is used.
2. Customize Runtime on the Run
Sometimes you might need to dynamically control the runtime specs at the time of execution, instead of hard-coding it. Code Bundles allow you to use a simple keyword WITH SPECIFICATION in the EXECUTE statement to provide the runtime requirements as a string literal, which will completely override any existing YAML file in your project folder at the time of execution. This is especially useful when testing new environment compatibility.
EXECUTE CODE BUNDLE dummy_project
ENTRYPOINT = 'main.py'
WITH SPECIFICATION
$$
bundle:
type: custom
compute_type: warehouse
language: python
compute_options:
runtime_version: '3.13'
$$;This execution failed as expected because we included a custom exception in the code to raise error if Python version is not 3.12. The process failed but the information was captured through error logging.
3. Flexible Argument Parsing
argparse is my favorite Python library for parsing input arguments. Update your main.py to something like this in your entry point file:
# main.py
import argparse
...
if __name__ == "__main__":
...
parser = argparse.ArgumentParser(description="Demo Code Bundles")
parser.add_argument("-d", "--report-date", type=str, help="YYYY-MM-DD")
parser.add_argument("-f", "--backfill", action="store_true")
args = parser.parse_args()
logger.info(f"{vars(args)}")
...In regular Python runtime you would be doing this:
/usr/bin/python3 /home/user/my_project/main.py -d 2026-08-01 -fTo include the input arguments in code bundle execution you need to supply them as a tuple like below:
EXECUTE CODE BUNDLE dummy_project
ENTRYPOINT = 'main.py'
ARGUMENTS = ('--report-date', '2026-08-01', '--backfill');Querying the event table shows that the input arguments were successfully passed into the runtime:
4. Using Custom Python Package
Sometimes a runtime’s default Python packages are not the versions your application needs. When you require a specific library version, add a properties.requirements_file entry under code_bundle.yml, then list the pinned dependency in that file.
Using jellyfish as example, modify your main file to use it:
if __name__ == "__main__":
import importlib.metadata
import jellyfish
version = importlib.metadata.version("jellyfish")
logger.info(f"jellyfish version: {version}")Because jellyfish is not in the default runtime, trying to execute this code bundle will not work. Add this to a file named requirements.txt
# requirements.txt
snowpark-connect[jdk]==1.42.0Then add these two lines to code_bundle.yml:
# code_bundle.yml
...
properties:
requirements_file: requirements.txtAnd the results are successfully captured in the event table:
5. Stage Mounts Storage Path
Stage mounts are useful when code or libraries expect filesystem paths, such as open(), rather than Snowflake-specific file APIs. You can read files with SnowflakeFile, but writing back to the stage needs path resolution; @stage/path is not a local path. Currently stage mounts on compute pools are readable and writable. On warehouses, they are read-only.
bundle:
...
stage_mounts:
input_data:
stage_url: "@<database>.<schema>/<project_dir_path>"
mount_path: "/mnt/data"Your code can then access the files normally:
with open("/mnt/data/file.csv") as f:
contents = f.read()6. Anonymous Code Bundle Execution
An anonymous code bundle execution runs source directly from a stage or workspace without creating a named code bundle object. It is convenient for experimentation, versioned artifacts, and one-off jobs with no object lifecycle to manage.
Anonymous code bundle execution comes with limitations, such as weaker database/schema lineage, telemetry routing, less discoverability, and limited governance and history benefits.
-- anonymous code bundle execution
EXECUTE CODE BUNDLE FROM '@<stage_name>/<temp_dir>'
ENTRYPOINT = 'main.py';
EXECUTE CODE BUNDLE
FROM 'snow://workspace/"USER$".PUBLIC.<workspace>/versions/live/'
ENTRYPOINT = 'main.py';Schedule Code Bundle as Jobs
In order to put your Code Bundle to run automatically based on a schedule, simply include it in a Snowflake TASK:
CREATE OR REPLACE TASK <database>.<schema>.<task>
WAREHOUSE = MY_WAREHOUSE
SCHEDULE = 'USING CRON 0 2 * * * UTC'
AS
EXECUTE CODE BUNDLE <database>.<schema>.<code_bundle>
ENTRYPOINT = 'main.py';
ALTER TASK <database>.<schema>.<task> RESUME;Note that you usuallly need to define a compute resource for your TASK object and a compute resource for your CODE BUNDLE object. These are two distinct concepts and they don’t necessarily have to be the same. If you choose WAREHOUSE compute for the task itself, your code bundle can infer the warehouse resource from the session inherently and you don’t need to define it again.
What’s more? You can chain these tasks together to achieve more complex orchestration. To get a better read about Snowflake task DAG capabilities, please visit my article Orchestrate Airflow-Style Task DAGs in Snowflake.
Thank you for reading this article! Please try to run the code examples yourself and do not hesitate to contact me if you run into any issues. If you would like to see similar content in the future, please tell me what challenges you are facing or what solutions you are looking for. Let’s learn together!









