From 7abc9d7da37e94417cdc94c307128ba634bd5172 Mon Sep 17 00:00:00 2001
From: Stefan Krawczyk <stefan@dagworks.io>
Date: Mon, 16 Dec 2024 12:21:42 -0800
Subject: [PATCH 1/2] Adds code for snowflake container example running the UI

---
 examples/snowflake/hamilton_ui/Dockerfile     |  14 ++
 examples/snowflake/hamilton_ui/README.md      |   9 ++
 .../snowflake/hamilton_ui/my_functions.py     |  21 +++
 .../hamilton_ui/pipeline_endpoint.py          | 104 ++++++++++++++
 .../hamilton_ui/pipeline_endpoint_.py         | 128 ++++++++++++++++++
 examples/snowflake/hamilton_ui/snowflake.sql  |  93 +++++++++++++
 6 files changed, 369 insertions(+)
 create mode 100644 examples/snowflake/hamilton_ui/Dockerfile
 create mode 100644 examples/snowflake/hamilton_ui/README.md
 create mode 100644 examples/snowflake/hamilton_ui/my_functions.py
 create mode 100644 examples/snowflake/hamilton_ui/pipeline_endpoint.py
 create mode 100644 examples/snowflake/hamilton_ui/pipeline_endpoint_.py
 create mode 100644 examples/snowflake/hamilton_ui/snowflake.sql

diff --git a/examples/snowflake/hamilton_ui/Dockerfile b/examples/snowflake/hamilton_ui/Dockerfile
new file mode 100644
index 000000000..0433ae7a8
--- /dev/null
+++ b/examples/snowflake/hamilton_ui/Dockerfile
@@ -0,0 +1,14 @@
+FROM python:3.13.1-slim
+
+RUN apt update
+RUN apt upgrade sqlite3 -y
+RUN pip install "sf-hamilton[ui,sdk]"
+RUN pip install flask
+
+ENV HAMILTON_ALLOWED_HOSTS=".snowflakecomputing.app"
+ENV SERVER_PORT=8001
+
+COPY pipeline_endpoint.py /pipeline_endpoint.py
+COPY my_functions.py /my_functions.py
+
+ENTRYPOINT /bin/bash -c "(hamilton ui --base-dir /hamilton-basedir &) && python /pipeline_endpoint.py"
diff --git a/examples/snowflake/hamilton_ui/README.md b/examples/snowflake/hamilton_ui/README.md
new file mode 100644
index 000000000..86aff275d
--- /dev/null
+++ b/examples/snowflake/hamilton_ui/README.md
@@ -0,0 +1,9 @@
+# Running the Hamilton & the Hamilton UI in Snowflake
+
+This example is code for the ["TODO" post]().
+
+Here we show the code required to be packaged up for
+use on Snowflake.
+
+TODO:
+ - cut & paste instructions from blog post?
diff --git a/examples/snowflake/hamilton_ui/my_functions.py b/examples/snowflake/hamilton_ui/my_functions.py
new file mode 100644
index 000000000..387d0b9fc
--- /dev/null
+++ b/examples/snowflake/hamilton_ui/my_functions.py
@@ -0,0 +1,21 @@
+import pandas as pd
+
+
+def spend_mean(spend: pd.Series) -> float:
+    """Shows function creating a scalar. In this case it computes the mean of the entire column."""
+    return spend.mean()
+
+
+def spend_zero_mean(spend: pd.Series, spend_mean: float) -> pd.Series:
+    """Shows function that takes a scalar. In this case to zero mean spend."""
+    return spend - spend_mean
+
+
+def spend_std_dev(spend: pd.Series) -> float:
+    """Function that computes the standard deviation of the spend column."""
+    return spend.std()
+
+
+def spend_zero_mean_unit_variance(spend_zero_mean: pd.Series, spend_std_dev: float) -> pd.Series:
+    """Function showing one way to make spend have zero mean and unit variance."""
+    return spend_zero_mean / spend_std_dev
diff --git a/examples/snowflake/hamilton_ui/pipeline_endpoint.py b/examples/snowflake/hamilton_ui/pipeline_endpoint.py
new file mode 100644
index 000000000..ce56dc9f9
--- /dev/null
+++ b/examples/snowflake/hamilton_ui/pipeline_endpoint.py
@@ -0,0 +1,104 @@
+import logging
+import os
+import sys
+
+import pandas as pd
+from flask import Flask, make_response, request
+
+from hamilton import registry
+
+registry.disable_autoload()
+import my_functions  # we import the module here!
+
+from hamilton import driver
+from hamilton_sdk import adapters
+
+# WRAPER CODE FOR SNOWFLAKE FUNCTION ######
+
+SERVICE_HOST = os.getenv("SERVER_HOST", "0.0.0.0")
+SERVICE_PORT = os.getenv("SERVER_PORT", 8080)
+CHARACTER_NAME = os.getenv("CHARACTER_NAME", "I")
+
+
+def get_logger(logger_name):
+    logger = logging.getLogger(logger_name)
+    logger.setLevel(logging.DEBUG)
+    handler = logging.StreamHandler(sys.stdout)
+    handler.setLevel(logging.DEBUG)
+    handler.setFormatter(logging.Formatter("%(name)s [%(asctime)s] [%(levelname)s] %(message)s"))
+    logger.addHandler(handler)
+    return logger
+
+
+logger = get_logger("echo-service")
+
+app = Flask(__name__)
+
+
+@app.get("/healthcheck")
+def readiness_probe():
+    return "OK"
+
+
+@app.post("/echo")
+def echo():
+    message = request.json
+    logger.debug(f"Received request: {message}")
+
+    if message is None or not message["data"]:
+        logger.info("Received empty message")
+        return {}
+
+    input_rows = message["data"]
+    logger.info(f"Received {len(input_rows)} rows")
+
+    output_rows = [[row[0], get_response(row[1], row[2], row[3], row[4])] for row in input_rows]
+    logger.info(f"Produced {len(output_rows)} rows")
+
+    response = make_response({"data": output_rows})
+    response.headers["Content-type"] = "application/json"
+    logger.debug(f"Sending response: {response.json}")
+    return response
+
+
+# END OF WRAPER CODE FOR SNOWFLAKE FUNCTION ######
+
+
+def get_response(prj_id, spend, signups, output_columns):
+    tracker = adapters.HamiltonTracker(
+        project_id=prj_id,
+        username="admin",
+        dag_name="MYDAG",
+        tags={"environment": "R&D", "team": "MY_TEAM", "version": "Beta"},
+    )
+    initial_columns = {  # load from actuals or wherever -- this is our initial data we use as input.
+        "signups": pd.Series(spend),
+        "spend": pd.Series(signups),
+    }
+    dr = (
+        driver.Builder()
+        .with_config({})  # we don't have any configuration or invariant data for this example.
+        .with_modules(
+            my_functions
+        )  # we need to tell hamilton where to load function definitions from
+        .with_adapters(tracker)  # we want a pandas dataframe as output
+        .build()
+    )
+
+    df = dr.execute(output_columns, inputs=initial_columns)
+
+    serializable_df = {}
+
+    for key, value in df.items():
+        if isinstance(value, pd.Series):
+            # Convert Series to dict (or .tolist() for just values)
+            serializable_df[key] = {str(k): v for k, v in value.to_dict().items()}
+        else:
+            # Pass other values as is
+            serializable_df[key] = value
+
+    return serializable_df
+
+
+if __name__ == "__main__":
+    app.run(host=SERVICE_HOST, port=SERVICE_PORT)
diff --git a/examples/snowflake/hamilton_ui/pipeline_endpoint_.py b/examples/snowflake/hamilton_ui/pipeline_endpoint_.py
new file mode 100644
index 000000000..21e5b2ef0
--- /dev/null
+++ b/examples/snowflake/hamilton_ui/pipeline_endpoint_.py
@@ -0,0 +1,128 @@
+import json
+import logging
+import os
+import sys
+
+from flask import Flask, make_response, request
+
+SERVICE_HOST = os.getenv("SERVER_HOST", "0.0.0.0")
+SERVICE_PORT = os.getenv("SERVER_PORT", 8080)
+CHARACTER_NAME = os.getenv("CHARACTER_NAME", "I")
+
+
+def get_logger(logger_name):
+    logger = logging.getLogger(logger_name)
+    logger.setLevel(logging.DEBUG)
+    handler = logging.StreamHandler(sys.stdout)
+    handler.setLevel(logging.DEBUG)
+    handler.setFormatter(logging.Formatter("%(name)s [%(asctime)s] [%(levelname)s] %(message)s"))
+    logger.addHandler(handler)
+    return logger
+
+
+logger = get_logger("echo-service")
+
+app = Flask(__name__)
+
+
+@app.get("/healthcheck")
+def readiness_probe():
+    return "OK"
+
+
+@app.post("/echo")
+def echo():
+    """
+    Main handler for input data sent by Snowflake.
+    """
+    message = request.json
+    logger.debug(f"Received request: {message}")
+
+    if message is None or not message["data"]:
+        logger.info("Received empty message")
+        return {}
+
+    # input format:
+    #   {"data": [
+    #     [row_index, column_1_value, column_2_value, ...],
+    #     ...
+    #   ]}
+    input_rows = message["data"]
+    logger.info(f"Received {len(input_rows)} rows")
+
+    # output format:
+    #   {"data": [
+    #     [row_index, column_1_value, column_2_value, ...}],
+    #     ...
+    #   ]}
+    output_rows = [[row[0], get_response(row[1], row[2])] for row in input_rows]
+    logger.info(f"Produced {len(output_rows)} rows")
+
+    response = make_response({"data": output_rows})
+    response.headers["Content-type"] = "application/json"
+    logger.debug(f"Sending response: {response.json}")
+    return response
+
+
+import pandas as pd
+
+# We add this to speed up running things if you have a lot in your python environment.
+from hamilton import registry
+
+registry.disable_autoload()
+import my_functions  # we import the module here!
+
+from hamilton import driver
+from hamilton_sdk import adapters
+
+
+def get_response(prj_id, output_columns):
+    tracker = adapters.HamiltonTracker(
+        project_id=prj_id,
+        username="admin",
+        dag_name="MYDAG",
+        tags={"environment": "DEV", "team": "MY_TEAM", "version": "X"},
+    )
+    # Instantiate a common spine for your pipeline
+    index = pd.date_range("2022-01-01", periods=6, freq="w")
+    initial_columns = {  # load from actuals or wherever -- this is our initial data we use as input.
+        # Note: these do not have to be all series, they could be scalar inputs.
+        "signups": pd.Series([1, 10, 50, 100, 200, 400], index=index),
+        "spend": pd.Series([10, 10, 20, 40, 40, 50], index=index),
+    }
+    dr = (
+        driver.Builder()
+        .with_config({})  # we don't have any configuration or invariant data for this example.
+        .with_modules(
+            my_functions
+        )  # we need to tell hamilton where to load function definitions from
+        .with_adapters(tracker)  # we want a pandas dataframe as output
+        .build()
+    )
+    # we need to specify what we want in the final dataframe (these could be function pointers).
+    output_columns = ["spend", "signups", "spend_std_dev", "spend_zero_mean_unit_variance"]
+    # let's create the dataframe!
+    df = dr.execute(output_columns, inputs=initial_columns)
+    # `pip install sf-hamilton[visualization]` earlier you can also do
+    # dr.visualize_execution(output_columns,'./my_dag.png', {})
+    print(df)
+
+    serializable_df = {}
+
+    for key, value in df.items():
+        if isinstance(value, pd.Series):
+            # Convert Series to dict (or .tolist() for just values)
+            serializable_df[key] = {str(k): v for k, v in value.to_dict().items()}
+        else:
+            # Pass other values as is
+            serializable_df[key] = value
+
+    # Serialize to JSON
+    print(serializable_df)
+    return serializable_df
+    return json.dumps(serializable_df, indent=4)
+    # return json.dumps(df, indent=4)
+
+
+if __name__ == "__main__":
+    app.run(host=SERVICE_HOST, port=SERVICE_PORT)
diff --git a/examples/snowflake/hamilton_ui/snowflake.sql b/examples/snowflake/hamilton_ui/snowflake.sql
new file mode 100644
index 000000000..bc702b169
--- /dev/null
+++ b/examples/snowflake/hamilton_ui/snowflake.sql
@@ -0,0 +1,93 @@
+https://medium.com/@pkantyka/observability-of-python-code-and-application-logic-with-hamilton-ui-on-snowflake-container-services-a26693b46635
+
+CREATE SERVICE public.hamilton_ui
+IN COMPUTE POOL TEST_POOL
+FROM SPECIFICATION $$
+spec:
+  containers:
+  - name: hamiltonui
+    image: <account-url-registry-host>/<db-name>/<schema-name>/<repo-name>/snowflake-hamilton-ui
+    volumeMounts:
+    - name: hamilton-basedir
+      mountPath: /hamilton-basedir
+  endpoints:
+   - name: entrypoint
+     port: 8001
+   - name: hamilton
+     port: 8241
+     public: true
+  volumes:
+   - name: hamilton-basedir
+     source: "@<db-name>.<schema-name>.hamilton_base"
+  $$
+QUERY_WAREHOUSE = <warehause-name>
+;
+
+CALL SYSTEM$GET_SERVICE_STATUS('<db-name>.<schema>.hamilton_ui');
+
+CALL SYSTEM$GET_SERVICE_LOGS('<db-name>.<schema>.hamilton_ui', '0', 'hammiltonui', 1000);
+
+SHOW ENDPOINTS IN SERVICE public.hamilton_ui;
+
+CREATE OR REPLACE FUNCTION public.hamilton_pipeline (prj_id number, signups variant, spend variant, output_columns variant)
+  RETURNS VARIANT
+  SERVICE=public.hamilton_ui
+  ENDPOINT=entrypoint
+  AS '/echo';
+
+
+SELECT
+  public.hamilton_pipeline (
+    1,
+    [1, 10, 50, 100, 200, 400],
+    [10, 10, 20, 40, 40, 50],
+    [ 'spend', 'signups', 'spend_std_dev', 'spend_zero_mean_unit_variance' ]
+  ) as data;
+
+WITH input_data AS (
+  SELECT
+      public.hamilton_pipeline (
+        1,
+        [1, 10, 50, 100, 200, 400],
+        [10, 10, 20, 40, 40, 50],
+        [ 'spend', 'signups', 'spend_std_dev', 'spend_zero_mean_unit_variance' ]
+      ) as data
+),
+flattened AS (
+  SELECT
+    key AS metric_key,
+    value AS metric_value
+  FROM
+    input_data
+    left join LATERAL FLATTEN(input_data.data)
+)
+SELECT
+  *
+FROM
+  flattened f;
+
+WITH input_data AS (
+  SELECT
+      public.hamilton_pipeline (
+        1,
+        [1, 10, 50, 100, 200, 400],
+        [10, 10, 20, 40, 40, 50],
+        [ 'spend', 'signups', 'spend_std_dev', 'spend_zero_mean_unit_variance' ]
+      ) as data
+),
+flattened AS (
+  SELECT
+    key AS metric_key,
+    value AS metric_value
+  FROM
+    input_data
+    left join LATERAL FLATTEN(input_data.data)
+)
+SELECT
+  f2.key,
+  f2.value
+FROM
+  flattened f
+  left join lateral flatten(metric_value) f2
+where
+  metric_key = 'spend_zero_mean_unit_variance';

From 7dd1890ea61513723d2187bcbf568f34038e7b65 Mon Sep 17 00:00:00 2001
From: Stefan Krawczyk <stefan@dagworks.io>
Date: Mon, 16 Dec 2024 22:32:05 -0800
Subject: [PATCH 2/2] Get Snowflake + Hamilton UI example ready

Note: this is a toy example. For real production needs, you'd need
to modify a few things:

1. not use SQLLITE, and instead postgresql, or implement django-snowflake connection.
2. likely not use the Hamilton code within the flask container, instead package up properly
and define a UDF or UDTF.
3. could use snowpark dataframes or have hamilton code do other things, e.g. LLM calls..
---
 docs/hamilton-ui/ui.rst                       |   6 +
 examples/snowflake/hamilton_ui/README.md      |  17 ++-
 .../hamilton_ui/pipeline_endpoint.py          |  24 +++-
 .../hamilton_ui/pipeline_endpoint_.py         | 128 ------------------
 .../snowflake/hamilton_ui/requirements.txt    |   2 +
 examples/snowflake/hamilton_ui/snowflake.sql  |   5 +-
 6 files changed, 42 insertions(+), 140 deletions(-)
 delete mode 100644 examples/snowflake/hamilton_ui/pipeline_endpoint_.py
 create mode 100644 examples/snowflake/hamilton_ui/requirements.txt

diff --git a/docs/hamilton-ui/ui.rst b/docs/hamilton-ui/ui.rst
index 2bb168fcb..c1db0bed0 100644
--- a/docs/hamilton-ui/ui.rst
+++ b/docs/hamilton-ui/ui.rst
@@ -123,6 +123,12 @@ More extensive self-hosting documentation is in the works, e.g. Snowflake, Datab
 chart contribution!
 
 
+Running on Snowflake
+---------------------
+You can run the Hamilton UI on Snowflake Container Services. For a detailed guide, see the blog post
+`Observability of Python code and application logic with Hamilton UI on Snowflake Container Services <https://medium.com/@pkantyka/observability-of-python-code-and-application-logic-with-hamilton-ui-on-snowflake-container-services-a26693b46635>`_ by
+`Greg Kantyka <https://medium.com/@pkantyka>`_ and the `Hamilton Snowflake Example <https://github.com/DAGWorks-Inc/hamilton/tree/main/examples/snowflake/hamilton_ui>`_.
+
 -----------
 Get started
 -----------
diff --git a/examples/snowflake/hamilton_ui/README.md b/examples/snowflake/hamilton_ui/README.md
index 86aff275d..9f5fc7174 100644
--- a/examples/snowflake/hamilton_ui/README.md
+++ b/examples/snowflake/hamilton_ui/README.md
@@ -1,9 +1,16 @@
 # Running the Hamilton & the Hamilton UI in Snowflake
 
-This example is code for the ["TODO" post]().
+This example is code for the ["Observability of Python code and application logic with Hamilton UI on Snowflake Container Services" post](https://medium.com/@pkantyka/observability-of-python-code-and-application-logic-with-hamilton-ui-on-snowflake-container-services-a26693b46635) by
+[Greg Kantyka](https://medium.com/@pkantyka).
 
-Here we show the code required to be packaged up for
-use on Snowflake.
+Here we show the code required to be packaged up for use on Snowflake:
 
-TODO:
- - cut & paste instructions from blog post?
+1. Docker file that runs the Hamilton UI and a flask endpoint to exercise Hamilton code
+2. my_functions.py - the Hamilton code that is exercised by the flask endpoint
+3. pipeline_endpoint.py - the flask endpoint that exercises the Hamilton code
+
+To run see:
+ - snowflake.sql that contains all the SQL to create the necessary objects in Snowflake and exercise things.
+
+For more details see ["Observability of Python code and application logic with Hamilton UI on Snowflake Container Services" post](https://medium.com/@pkantyka/observability-of-python-code-and-application-logic-with-hamilton-ui-on-snowflake-container-services-a26693b46635) by
+[Greg Kantyka](https://medium.com/@pkantyka).
diff --git a/examples/snowflake/hamilton_ui/pipeline_endpoint.py b/examples/snowflake/hamilton_ui/pipeline_endpoint.py
index ce56dc9f9..7a787c8d2 100644
--- a/examples/snowflake/hamilton_ui/pipeline_endpoint.py
+++ b/examples/snowflake/hamilton_ui/pipeline_endpoint.py
@@ -1,3 +1,15 @@
+"""
+This module:
+- Defines a flask app that listens for POST requests on /echo
+- the /echo command is invoked from a Snowflake SQL query
+- the /echo command calls a function get_response that is defined in this module
+- get_response uses Hamilton to execute a pipeline defined in my_functions.py
+- my_functions.py contains the functions that are used in the pipeline
+- the pipeline is executed with the input data from the Snowflake query
+- the output of the pipeline is returned to Snowflake
+- the Hamilton UI tracker is used to track the execution of the pipeline
+"""
+
 import logging
 import os
 import sys
@@ -13,7 +25,7 @@
 from hamilton import driver
 from hamilton_sdk import adapters
 
-# WRAPER CODE FOR SNOWFLAKE FUNCTION ######
+# WRAPPER CODE FOR SNOWFLAKE FUNCTION ######
 
 SERVICE_HOST = os.getenv("SERVER_HOST", "0.0.0.0")
 SERVICE_PORT = os.getenv("SERVER_PORT", 8080)
@@ -42,6 +54,7 @@ def readiness_probe():
 
 @app.post("/echo")
 def echo():
+    """This is the endpoint that Snowflake will call to run Hamilton code."""
     message = request.json
     logger.debug(f"Received request: {message}")
 
@@ -61,17 +74,18 @@ def echo():
     return response
 
 
-# END OF WRAPER CODE FOR SNOWFLAKE FUNCTION ######
+# END OF WRAPPER CODE FOR SNOWFLAKE FUNCTION ######
 
 
 def get_response(prj_id, spend, signups, output_columns):
+    """The function that is called from SQL on Snowflake."""
     tracker = adapters.HamiltonTracker(
         project_id=prj_id,
         username="admin",
         dag_name="MYDAG",
         tags={"environment": "R&D", "team": "MY_TEAM", "version": "Beta"},
     )
-    initial_columns = {  # load from actuals or wherever -- this is our initial data we use as input.
+    input_columns = {
         "signups": pd.Series(spend),
         "spend": pd.Series(signups),
     }
@@ -81,11 +95,11 @@ def get_response(prj_id, spend, signups, output_columns):
         .with_modules(
             my_functions
         )  # we need to tell hamilton where to load function definitions from
-        .with_adapters(tracker)  # we want a pandas dataframe as output
+        .with_adapters(tracker)  # we add the Hamilton UI tracker
         .build()
     )
 
-    df = dr.execute(output_columns, inputs=initial_columns)
+    df = dr.execute(output_columns, inputs=input_columns)
 
     serializable_df = {}
 
diff --git a/examples/snowflake/hamilton_ui/pipeline_endpoint_.py b/examples/snowflake/hamilton_ui/pipeline_endpoint_.py
deleted file mode 100644
index 21e5b2ef0..000000000
--- a/examples/snowflake/hamilton_ui/pipeline_endpoint_.py
+++ /dev/null
@@ -1,128 +0,0 @@
-import json
-import logging
-import os
-import sys
-
-from flask import Flask, make_response, request
-
-SERVICE_HOST = os.getenv("SERVER_HOST", "0.0.0.0")
-SERVICE_PORT = os.getenv("SERVER_PORT", 8080)
-CHARACTER_NAME = os.getenv("CHARACTER_NAME", "I")
-
-
-def get_logger(logger_name):
-    logger = logging.getLogger(logger_name)
-    logger.setLevel(logging.DEBUG)
-    handler = logging.StreamHandler(sys.stdout)
-    handler.setLevel(logging.DEBUG)
-    handler.setFormatter(logging.Formatter("%(name)s [%(asctime)s] [%(levelname)s] %(message)s"))
-    logger.addHandler(handler)
-    return logger
-
-
-logger = get_logger("echo-service")
-
-app = Flask(__name__)
-
-
-@app.get("/healthcheck")
-def readiness_probe():
-    return "OK"
-
-
-@app.post("/echo")
-def echo():
-    """
-    Main handler for input data sent by Snowflake.
-    """
-    message = request.json
-    logger.debug(f"Received request: {message}")
-
-    if message is None or not message["data"]:
-        logger.info("Received empty message")
-        return {}
-
-    # input format:
-    #   {"data": [
-    #     [row_index, column_1_value, column_2_value, ...],
-    #     ...
-    #   ]}
-    input_rows = message["data"]
-    logger.info(f"Received {len(input_rows)} rows")
-
-    # output format:
-    #   {"data": [
-    #     [row_index, column_1_value, column_2_value, ...}],
-    #     ...
-    #   ]}
-    output_rows = [[row[0], get_response(row[1], row[2])] for row in input_rows]
-    logger.info(f"Produced {len(output_rows)} rows")
-
-    response = make_response({"data": output_rows})
-    response.headers["Content-type"] = "application/json"
-    logger.debug(f"Sending response: {response.json}")
-    return response
-
-
-import pandas as pd
-
-# We add this to speed up running things if you have a lot in your python environment.
-from hamilton import registry
-
-registry.disable_autoload()
-import my_functions  # we import the module here!
-
-from hamilton import driver
-from hamilton_sdk import adapters
-
-
-def get_response(prj_id, output_columns):
-    tracker = adapters.HamiltonTracker(
-        project_id=prj_id,
-        username="admin",
-        dag_name="MYDAG",
-        tags={"environment": "DEV", "team": "MY_TEAM", "version": "X"},
-    )
-    # Instantiate a common spine for your pipeline
-    index = pd.date_range("2022-01-01", periods=6, freq="w")
-    initial_columns = {  # load from actuals or wherever -- this is our initial data we use as input.
-        # Note: these do not have to be all series, they could be scalar inputs.
-        "signups": pd.Series([1, 10, 50, 100, 200, 400], index=index),
-        "spend": pd.Series([10, 10, 20, 40, 40, 50], index=index),
-    }
-    dr = (
-        driver.Builder()
-        .with_config({})  # we don't have any configuration or invariant data for this example.
-        .with_modules(
-            my_functions
-        )  # we need to tell hamilton where to load function definitions from
-        .with_adapters(tracker)  # we want a pandas dataframe as output
-        .build()
-    )
-    # we need to specify what we want in the final dataframe (these could be function pointers).
-    output_columns = ["spend", "signups", "spend_std_dev", "spend_zero_mean_unit_variance"]
-    # let's create the dataframe!
-    df = dr.execute(output_columns, inputs=initial_columns)
-    # `pip install sf-hamilton[visualization]` earlier you can also do
-    # dr.visualize_execution(output_columns,'./my_dag.png', {})
-    print(df)
-
-    serializable_df = {}
-
-    for key, value in df.items():
-        if isinstance(value, pd.Series):
-            # Convert Series to dict (or .tolist() for just values)
-            serializable_df[key] = {str(k): v for k, v in value.to_dict().items()}
-        else:
-            # Pass other values as is
-            serializable_df[key] = value
-
-    # Serialize to JSON
-    print(serializable_df)
-    return serializable_df
-    return json.dumps(serializable_df, indent=4)
-    # return json.dumps(df, indent=4)
-
-
-if __name__ == "__main__":
-    app.run(host=SERVICE_HOST, port=SERVICE_PORT)
diff --git a/examples/snowflake/hamilton_ui/requirements.txt b/examples/snowflake/hamilton_ui/requirements.txt
new file mode 100644
index 000000000..7e573a31a
--- /dev/null
+++ b/examples/snowflake/hamilton_ui/requirements.txt
@@ -0,0 +1,2 @@
+flask
+sf-hamilton[ui,sdk]
diff --git a/examples/snowflake/hamilton_ui/snowflake.sql b/examples/snowflake/hamilton_ui/snowflake.sql
index bc702b169..7cad26748 100644
--- a/examples/snowflake/hamilton_ui/snowflake.sql
+++ b/examples/snowflake/hamilton_ui/snowflake.sql
@@ -1,4 +1,5 @@
-https://medium.com/@pkantyka/observability-of-python-code-and-application-logic-with-hamilton-ui-on-snowflake-container-services-a26693b46635
+-- For more details visit:
+-- https://medium.com/@pkantyka/observability-of-python-code-and-application-logic-with-hamilton-ui-on-snowflake-container-services-a26693b46635
 
 CREATE SERVICE public.hamilton_ui
 IN COMPUTE POOL TEST_POOL
@@ -90,4 +91,4 @@ FROM
   flattened f
   left join lateral flatten(metric_value) f2
 where
-  metric_key = 'spend_zero_mean_unit_variance';
+  metric_key = 'spend_zero_mean_unit_variance';
