From 7b620d544b8e1c5b86fb4a741c8432f0b97ce7b1 Mon Sep 17 00:00:00 2001 From: Bruno Dufour Date: Thu, 26 Sep 2024 14:32:17 -0400 Subject: [PATCH] Added native app templates --- .github/CODEOWNERS | 2 + app_basic/.gitignore | 2 + app_basic/README.md | 17 +++ app_basic/app/README.md | 20 ++++ app_basic/app/manifest.yml | 9 ++ app_basic/app/setup_script.sql | 11 ++ app_basic/snowflake.yml | 29 +++++ app_basic/template.yml | 8 ++ app_spcs_basic/.gitignore | 2 + app_spcs_basic/README.md | 82 ++++++++++++++ app_spcs_basic/app/README.md | 3 + app_spcs_basic/app/manifest.yml | 33 ++++++ app_spcs_basic/app/service_spec.yml | 13 +++ app_spcs_basic/app/services.sql | 100 ++++++++++++++++++ app_spcs_basic/app/setup.sql | 2 + app_spcs_basic/build-and-push.sh | 19 ++++ app_spcs_basic/service/Dockerfile | 12 +++ app_spcs_basic/service/index.html | 10 ++ app_spcs_basic/service/main.py | 73 +++++++++++++ app_spcs_basic/snowflake.yml | 29 +++++ app_spcs_basic/template.yml | 8 ++ app_streamlit_java/.gitignore | 3 + app_streamlit_java/README.md | 64 +++++++++++ app_streamlit_java/app/README.md | 3 + app_streamlit_java/app/manifest.yml | 13 +++ app_streamlit_java/app/setup_script.sql | 32 ++++++ .../scripts/any-provider-setup.sql | 2 + app_streamlit_java/scripts/shared-content.sql | 12 +++ app_streamlit_java/snowflake.yml | 51 +++++++++ app_streamlit_java/src/module-add/.gitignore | 0 app_streamlit_java/src/module-add/pom.xml | 40 +++++++ .../src/main/java/com/snowflake/add/Add.java | 12 +++ .../test/java/com/snowflake/add/AddTest.java | 15 +++ .../src/module-ui/src/environment.yml | 6 ++ app_streamlit_java/src/module-ui/src/ui.py | 24 +++++ app_streamlit_java/template.yml | 8 ++ app_streamlit_js/.gitignore | 3 + app_streamlit_js/README.md | 47 ++++++++ app_streamlit_js/app/README.md | 3 + app_streamlit_js/app/manifest.yml | 13 +++ app_streamlit_js/app/setup_script.sql | 31 ++++++ .../scripts/any-provider-setup.sql | 2 + app_streamlit_js/scripts/shared-content.sql | 12 +++ app_streamlit_js/snowflake.yml | 47 ++++++++ .../src/module-ui/src/environment.yml | 6 ++ app_streamlit_js/src/module-ui/src/ui.py | 24 +++++ app_streamlit_js/template.yml | 8 ++ app_streamlit_python/.gitignore | 4 + app_streamlit_python/README.md | 90 ++++++++++++++++ app_streamlit_python/app/README.md | 3 + app_streamlit_python/app/manifest.yml | 13 +++ app_streamlit_python/app/setup_script.sql | 42 ++++++++ app_streamlit_python/local_test_env.yml | 12 +++ app_streamlit_python/pytest.ini | 2 + .../scripts/any-provider-setup.sql | 2 + .../scripts/shared-content.sql | 12 +++ app_streamlit_python/snowflake.yml | 51 +++++++++ .../src/module-add/src/main/python/add.py | 15 +++ .../module-add/src/test/python/test_add.py | 22 ++++ .../src/module-ui/src/environment.yml | 7 ++ app_streamlit_python/src/module-ui/src/ui.py | 50 +++++++++ .../src/module-ui/test/test_ui.py | 29 +++++ app_streamlit_python/template.yml | 9 ++ 63 files changed, 1328 insertions(+) create mode 100644 app_basic/.gitignore create mode 100644 app_basic/README.md create mode 100644 app_basic/app/README.md create mode 100644 app_basic/app/manifest.yml create mode 100644 app_basic/app/setup_script.sql create mode 100644 app_basic/snowflake.yml create mode 100644 app_basic/template.yml create mode 100644 app_spcs_basic/.gitignore create mode 100644 app_spcs_basic/README.md create mode 100644 app_spcs_basic/app/README.md create mode 100644 app_spcs_basic/app/manifest.yml create mode 100644 app_spcs_basic/app/service_spec.yml create mode 100644 app_spcs_basic/app/services.sql create mode 100644 app_spcs_basic/app/setup.sql create mode 100755 app_spcs_basic/build-and-push.sh create mode 100644 app_spcs_basic/service/Dockerfile create mode 100644 app_spcs_basic/service/index.html create mode 100644 app_spcs_basic/service/main.py create mode 100644 app_spcs_basic/snowflake.yml create mode 100644 app_spcs_basic/template.yml create mode 100644 app_streamlit_java/.gitignore create mode 100644 app_streamlit_java/README.md create mode 100644 app_streamlit_java/app/README.md create mode 100644 app_streamlit_java/app/manifest.yml create mode 100644 app_streamlit_java/app/setup_script.sql create mode 100644 app_streamlit_java/scripts/any-provider-setup.sql create mode 100644 app_streamlit_java/scripts/shared-content.sql create mode 100644 app_streamlit_java/snowflake.yml create mode 100644 app_streamlit_java/src/module-add/.gitignore create mode 100644 app_streamlit_java/src/module-add/pom.xml create mode 100644 app_streamlit_java/src/module-add/src/main/java/com/snowflake/add/Add.java create mode 100644 app_streamlit_java/src/module-add/src/test/java/com/snowflake/add/AddTest.java create mode 100644 app_streamlit_java/src/module-ui/src/environment.yml create mode 100644 app_streamlit_java/src/module-ui/src/ui.py create mode 100644 app_streamlit_java/template.yml create mode 100644 app_streamlit_js/.gitignore create mode 100644 app_streamlit_js/README.md create mode 100644 app_streamlit_js/app/README.md create mode 100644 app_streamlit_js/app/manifest.yml create mode 100644 app_streamlit_js/app/setup_script.sql create mode 100644 app_streamlit_js/scripts/any-provider-setup.sql create mode 100644 app_streamlit_js/scripts/shared-content.sql create mode 100644 app_streamlit_js/snowflake.yml create mode 100644 app_streamlit_js/src/module-ui/src/environment.yml create mode 100644 app_streamlit_js/src/module-ui/src/ui.py create mode 100644 app_streamlit_js/template.yml create mode 100644 app_streamlit_python/.gitignore create mode 100644 app_streamlit_python/README.md create mode 100644 app_streamlit_python/app/README.md create mode 100644 app_streamlit_python/app/manifest.yml create mode 100644 app_streamlit_python/app/setup_script.sql create mode 100644 app_streamlit_python/local_test_env.yml create mode 100644 app_streamlit_python/pytest.ini create mode 100644 app_streamlit_python/scripts/any-provider-setup.sql create mode 100644 app_streamlit_python/scripts/shared-content.sql create mode 100644 app_streamlit_python/snowflake.yml create mode 100644 app_streamlit_python/src/module-add/src/main/python/add.py create mode 100644 app_streamlit_python/src/module-add/src/test/python/test_add.py create mode 100644 app_streamlit_python/src/module-ui/src/environment.yml create mode 100644 app_streamlit_python/src/module-ui/src/ui.py create mode 100644 app_streamlit_python/src/module-ui/test/test_ui.py create mode 100644 app_streamlit_python/template.yml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 836e013..6fae42b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1,3 @@ * @snowflakedb/snowcli + +app_* @snowflakedb/nade diff --git a/app_basic/.gitignore b/app_basic/.gitignore new file mode 100644 index 0000000..9162cdf --- /dev/null +++ b/app_basic/.gitignore @@ -0,0 +1,2 @@ +snowflake.local.yml +output/** diff --git a/app_basic/README.md b/app_basic/README.md new file mode 100644 index 0000000..7715ed5 --- /dev/null +++ b/app_basic/README.md @@ -0,0 +1,17 @@ +## Introduction + +This is the basic project template for a Snowflake Native App project. It contains minimal code meant to help you set up your first application object in your account quickly. + +### Project Structure +| File Name | Purpose | +| --------- | ------- | +| README.md | The current file you are looking at, meant to guide you through a Snowflake Native App project. | +| app/setup_script.sql | Contains SQL statements that are run when an account installs or upgrades a Snowflake Native App. | +| app/manifest.yml | Defines properties required by the application package. Find more details at the [Manifest Documentation.](https://docs.snowflake.com/en/developer-guide/native-apps/creating-manifest) +| app/README.md | Exposed to the account installing the Snowflake Native App with details on what it does and how to use it. | +| snowflake.yml | Used by the Snowflake CLI tool to discover your project's code and interact with your Snowflake account with all relevant prvileges and grants. | + +### Adding a snowflake.local.yml file +Though your project directory already comes with a `snowflake.yml` file, an individual developer can choose to customize the behavior of the Snowflake CLI by providing local overrides to `snowflake.yml`, such as a new role to test out your own application package. This is where you can use `snowflake.local.yml`, which is not a version-controlled file. + +For more information, please refer to the Snowflake Documentation on installing and using Snowflake CLI to create a Snowflake Native App. diff --git a/app_basic/app/README.md b/app_basic/app/README.md new file mode 100644 index 0000000..7cc5726 --- /dev/null +++ b/app_basic/app/README.md @@ -0,0 +1,20 @@ +## Welcome to your First Snowflake Native App! + +In this Snowflake Native App, you will be able to explore some basic concepts such as application role, versioned schemas and creating procedures and functions within a setup script. + +For more information about a Snowflake Native App, please read the [official Snowflake documentation](https://docs.snowflake.com/en/developer-guide/native-apps/native-apps-about) which goes in depth about many additional functionalities of this framework. + +## Using the application after installation +To interact with the application after it has successfully installed in your account, switch to the application owner role first. + +### Calling a stored procedure + +``` +CALL ..; +``` + +### Calling a function + +``` +SELECT ..; +``` diff --git a/app_basic/app/manifest.yml b/app_basic/app/manifest.yml new file mode 100644 index 0000000..cc26726 --- /dev/null +++ b/app_basic/app/manifest.yml @@ -0,0 +1,9 @@ +# This is a manifest.yml file, a required component of creating a Snowflake Native App. +# This file defines properties required by the application package, including the location of the setup script and version definitions. +# Refer to https://docs.snowflake.com/en/developer-guide/native-apps/creating-manifest for a detailed understanding of this file. + +manifest_version: 1 + +artifacts: + setup_script: setup_script.sql + readme: README.md diff --git a/app_basic/app/setup_script.sql b/app_basic/app/setup_script.sql new file mode 100644 index 0000000..3d8d622 --- /dev/null +++ b/app_basic/app/setup_script.sql @@ -0,0 +1,11 @@ +-- This is the setup script that runs while installing a Snowflake Native App in a consumer account. +-- To write this script, you can familiarize yourself with some of the following concepts: +-- Application Roles +-- Versioned Schemas +-- UDFs/Procs +-- Extension Code +-- Refer to https://docs.snowflake.com/en/developer-guide/native-apps/creating-setup-script for a detailed understanding of this file. + +CREATE OR ALTER VERSIONED SCHEMA core; + +-- The rest of this script is left blank for purposes of your learning and exploration. diff --git a/app_basic/snowflake.yml b/app_basic/snowflake.yml new file mode 100644 index 0000000..4942ec9 --- /dev/null +++ b/app_basic/snowflake.yml @@ -0,0 +1,29 @@ +# This is a project definition file, a required component if you intend to use Snowflake CLI in a project directory such as this template. + +definition_version: 1 +native_app: + name: + source_stage: app_src.stage + artifacts: + - src: app/* + dest: ./ + +definition_version: 2 +entities: + pkg: + type: application package + identifier: <% fn.concat_ids('_pkg', ctx.env.suffix) %> + manifest: app/manifest.yml + artifacts: + - src: app/* + dest: ./ + + app: + type: application + from: + target: pkg + identifier: <% fn.concat_ids('', ctx.env.suffix) %> + +env: + suffix: <% fn.concat_ids('_', fn.sanitize_id(fn.get_username('unknown_user')) | lower) %> + diff --git a/app_basic/template.yml b/app_basic/template.yml new file mode 100644 index 0000000..9f3c9b5 --- /dev/null +++ b/app_basic/template.yml @@ -0,0 +1,8 @@ +minimum_cli_version: "2.8.0" +files_to_render: + - snowflake.yml +variables: + - name: project_name + prompt: "Project identifier" + default: my_native_app_project + type: string diff --git a/app_spcs_basic/.gitignore b/app_spcs_basic/.gitignore new file mode 100644 index 0000000..9162cdf --- /dev/null +++ b/app_spcs_basic/.gitignore @@ -0,0 +1,2 @@ +snowflake.local.yml +output/** diff --git a/app_spcs_basic/README.md b/app_spcs_basic/README.md new file mode 100644 index 0000000..2ebac1e --- /dev/null +++ b/app_spcs_basic/README.md @@ -0,0 +1,82 @@ +# Instructions + +## Prerequisites + +1. Install Docker + - [Windows](https://docs.docker.com/desktop/install/windows-install/) + - [Mac](https://docs.docker.com/desktop/install/mac-install/) + - [Linux](https://docs.docker.com/desktop/install/linux-install/) + + +## Set your SnowCLI connection (optional) + +We use your default connection to connect to Snowflake and deploy the images / app. Set your +default connection by modifying your `config.toml` file or by exporting the following environment variable: + +```sh +export SNOWFLAKE_DEFAULT_CONNECTION_NAME= +``` + +## Create image repository, build and push your local service image + +The [service/](service/) directory contains a [Dockerfile](service/Dockerfile) that builds a +simple Python server that responds to GET health checks, a GET for `/index.html`, as well as +POSTing to `/echo` in the Snowflake External Function payload format. You can build it and +push it to an image repository called `SPCS_NA.PUBLIC.IMAGES` in your account like so: + +```sh +./build-and-push.sh +``` + +This command will always use your default SnowCLI connection. + +## Deploy the application + +Deploy the app package and instance as such: + +```sh +snow app run +``` + +> Take note of the name of the application, which is based on the name you chose when you initialized this project. The application object and package are, by default, automatically suffixed by your (local) user name (i.e. `$USER`). + +## Setup the application + +When the application is opened for the first time, you will be prompted to grant the following account-level privileges to it: + +- CREATE COMPUTE POOL +- BIND SERVICE ENDPOINT + +Click on the `Grant` button to proceed. + +## Activate the application + +Once privileges are granted, a new `Activate` button should appear. Click the button and wait until the application is fully activated. +The `Activate` button invokes the `grant_callback` defined in the [manifest.yml](app/manifest.yml) file, which then creates the `COMPUTE POOL` and `SERVICE` needed to launch the application. + +## Launch the application + +Once all services and pools are created, you will be able to launch the app by clicking on the `Launch App` button. This will navigate to the URL provided by the `Service` and `Endpoint` defined in the `default_web_endpoint` in the [manifest.yml](app/manifest.yml). You will see the contents of [index.html](service/index.html) as served by the application container. + +## Test out the echo service + +```sh +snow sql -q "select .services.echo('Hello world!')" +``` + +You should see the same text back (Hello world!). + +## Clean up + +You can stop the service and drop the compute pool without dropping the application by running the following statement: + +```sh +snow sql -q "call .setup.drop_service_and_pool()" +``` + +Optionally, you can remove the app + package altogether afterwards: + +```sh +snow app teardown --cascade +``` +> Version `2.4.0+` of Snowflake CLI should be installed in order to execute `--cascade` command. diff --git a/app_spcs_basic/app/README.md b/app_spcs_basic/app/README.md new file mode 100644 index 0000000..6b67159 --- /dev/null +++ b/app_spcs_basic/app/README.md @@ -0,0 +1,3 @@ +# Snowflake Native App - SPCS + +This is a sample Snowflake Native App deployed using Snowpark Container Services. diff --git a/app_spcs_basic/app/manifest.yml b/app_spcs_basic/app/manifest.yml new file mode 100644 index 0000000..9f8e3bd --- /dev/null +++ b/app_spcs_basic/app/manifest.yml @@ -0,0 +1,33 @@ +# For more information on creating manifest, go to https://docs.snowflake.com/en/developer-guide/native-apps/creating-manifest +manifest_version: 1 + +version: + name: Dev + label: "Dev Version" + comment: "Default version used for development. Override for actual deployment." + +configuration: + log_level: INFO + trace_level: ALWAYS + grant_callback: setup.create_service + +artifacts: + readme: README.md + + default_web_endpoint: + service: services.spcs_na_service + endpoint: my-endpoint + + setup_script: setup.sql + + container_services: + images: + - /spcs_na_db/public/images/spcs_na_service:latest + +privileges: + - CREATE COMPUTE POOL: + required_at_setup: true + description: "Permission to create compute pools" + - BIND SERVICE ENDPOINT: + required_at_setup: true + description: "Required to create endpoints in services we can assign to functions" diff --git a/app_spcs_basic/app/service_spec.yml b/app_spcs_basic/app/service_spec.yml new file mode 100644 index 0000000..d92bb18 --- /dev/null +++ b/app_spcs_basic/app/service_spec.yml @@ -0,0 +1,13 @@ +spec: + container: + - name: my-service + image: /spcs_na_db/public/images/spcs_na_service:latest + env: + PORT: 8080 + readinessProbe: + port: 8080 + path: /healthcheck + endpoint: + - name: my-endpoint + port: 8080 + public: true diff --git a/app_spcs_basic/app/services.sql b/app_spcs_basic/app/services.sql new file mode 100644 index 0000000..ab20687 --- /dev/null +++ b/app_spcs_basic/app/services.sql @@ -0,0 +1,100 @@ +-- namespace under which our services and their functions will live +create schema if not exists services; + +-- namespace for service administration +create or alter versioned schema setup; + +-- creates a compute pool, service, and service function +create or replace procedure setup.create_service(privileges ARRAY) +returns varchar +language sql +execute as owner +as $$ + begin + let pool_name := (select current_database()) || '_app_pool'; + + create compute pool if not exists identifier(:pool_name) + MIN_NODES = 1 + MAX_NODES = 1 + INSTANCE_FAMILY = CPU_X64_XS; + + create service if not exists services.spcs_na_service + in compute pool identifier(:pool_name) + from spec='service_spec.yml'; + + grant usage on service services.spcs_na_service + to application role app_public; + + create or replace function services.echo(payload varchar) + returns varchar + service = services.spcs_na_service + endpoint = 'my-endpoint' + max_batch_rows = 50 + AS '/echo'; + + grant usage on function services.echo(varchar) + to application role app_public; + + return 'Done'; + end; +$$; +grant usage on procedure setup.create_service(ARRAY) + to application role app_public; + +create or replace procedure setup.suspend_service() +returns varchar +language sql +execute as owner +as $$ + begin + alter service services.spcs_na_service suspend; + return 'Done'; + end; +$$; +grant usage on procedure setup.suspend_service() + to application role app_public; + +create or replace procedure setup.resume_service() +returns varchar +language sql +execute as owner +as $$ + begin + alter service services.spcs_na_service resume; + return 'Done'; + end; +$$; +grant usage on procedure setup.resume_service() + to application role app_public; + +create or replace procedure setup.drop_service_and_pool() +returns varchar +language sql +execute as owner +as $$ + begin + let pool_name := (select current_database()) || '_app_pool'; + drop service if exists services.spcs_na_service; + drop compute pool if exists identifier(:pool_name); + return 'Done'; + end; +$$; +grant usage on procedure setup.drop_service_and_pool() + to application role app_public; + +create or replace procedure setup.service_status() +returns varchar +language sql +execute as owner +as $$ + declare + service_status varchar; + begin + call system$get_service_status('services.spcs_na_service') into :service_status; + return parse_json(:service_status)[0]['status']::varchar; + end; +$$; +grant usage on procedure setup.service_status() + to application role app_public; + +grant usage on schema setup to application role app_public; diff --git a/app_spcs_basic/app/setup.sql b/app_spcs_basic/app/setup.sql new file mode 100644 index 0000000..825b6c3 --- /dev/null +++ b/app_spcs_basic/app/setup.sql @@ -0,0 +1,2 @@ +create application role if not exists app_public; +execute immediate from './services.sql'; diff --git a/app_spcs_basic/build-and-push.sh b/app_spcs_basic/build-and-push.sh new file mode 100755 index 0000000..e041882 --- /dev/null +++ b/app_spcs_basic/build-and-push.sh @@ -0,0 +1,19 @@ +#!/bin/bash +DB_NAME="spcs_na_db" +SCHEMA_NAME="public" +IMAGE_REPO_NAME="images" +SERVICE_NAME="spcs_na_service" # service_spec.yml and manifest.yml needs to agree with this name as it is the service and image name. +DIR_NAME="./service" + +# make sure the target repository exists +snow sql -q "create database if not exists $DB_NAME" +snow sql -q "create schema if not exists $DB_NAME.$SCHEMA_NAME" +snow sql -q "create image repository if not exists $DB_NAME.$SCHEMA_NAME.$IMAGE_REPO_NAME" + +IMAGE_REPO_URL=$(snow spcs image-repository url $IMAGE_REPO_NAME --database $DB_NAME --schema $SCHEMA_NAME) +IMAGE_FQN="$IMAGE_REPO_URL/$SERVICE_NAME" + +# build and push the image (uses :latest implicitly) +docker buildx build --platform=linux/amd64 -t $IMAGE_FQN $DIR_NAME +snow spcs image-registry login +docker image push $IMAGE_FQN diff --git a/app_spcs_basic/service/Dockerfile b/app_spcs_basic/service/Dockerfile new file mode 100644 index 0000000..9281133 --- /dev/null +++ b/app_spcs_basic/service/Dockerfile @@ -0,0 +1,12 @@ +FROM python:3-alpine + +RUN mkdir /web +COPY index.html /web +COPY main.py /web + +# default port is 8080 +EXPOSE 8080 + +WORKDIR /web + +CMD ["python", "main.py"] diff --git a/app_spcs_basic/service/index.html b/app_spcs_basic/service/index.html new file mode 100644 index 0000000..64c6f85 --- /dev/null +++ b/app_spcs_basic/service/index.html @@ -0,0 +1,10 @@ + + + + Welcome to SPCS + NA + + +

Hello World!

+

We can show a container-based UI!

+ + diff --git a/app_spcs_basic/service/main.py b/app_spcs_basic/service/main.py new file mode 100644 index 0000000..3bf52c6 --- /dev/null +++ b/app_spcs_basic/service/main.py @@ -0,0 +1,73 @@ +from http.server import BaseHTTPRequestHandler, HTTPServer +import os +import json +import traceback +import sys + +PORT = int(os.environ.get("PORT", 8080)) +GET_PATHS = ('/', '/index.html', '/healthcheck') +POST_PATHS = ('/echo') + +class handler(BaseHTTPRequestHandler): + """ + Simple Python HTTP server that acts as both an ingress (GET) + and as a service function endpoint (POST) + """ + + def do_GET(self): + """ + Returns an HTML page bundled with the server + """ + if self.path not in GET_PATHS: + self.send_error(404) + return + + self.send_response(200) + self.send_header('Content-type', 'text/html') + self.end_headers() + + with open('index.html', 'rb') as f: + self.wfile.write(f.read()) + + def do_POST(self): + """ + POSTing to /echo in the external function format will echo the payload back to Snowflake. + See https://docs.snowflake.com/en/sql-reference/external-functions-data-format + """ + if self.path not in POST_PATHS: + self.send_error(404) + return + + self.send_response(200) + self.send_header('Content-type', 'application/json') + self.end_headers() + + try: + # post body looks something like... + # { + # "data": [ + # [0, 10, "Alex", "2014-01-01 16:00:00"], + # [1, 20, "Steve", "2015-01-01 16:00:00"], + # [2, 30, "Alice", "2016-01-01 16:00:00"], + # [3, 40, "Adrian", "2017-01-01 16:00:00"] + # ] + # } + content_len = int(self.headers.get('Content-Length')) + post_body = self.rfile.read(content_len) + parsed = json.loads(post_body) + + # in our case, we only have the one column, so we'll just have [rowid, message] + # and in fact, we're just echoing, so all we have to do is just return the same payload + # so you can see what's happening in logs, we'll echo all the rows we found + for [rowid, message] in parsed["data"]: + print(f"{rowid}: {message}") + + # echo back the exact same data, making sure it's JSON + self.wfile.write(json.dumps(parsed).encode('utf-8')) + except: + tb = traceback.format_exc() + print(tb, file=sys.stderr) + self.send_error(500, "Exception occurred", tb) + +with HTTPServer(('', PORT), handler) as server: + server.serve_forever() diff --git a/app_spcs_basic/snowflake.yml b/app_spcs_basic/snowflake.yml new file mode 100644 index 0000000..4942ec9 --- /dev/null +++ b/app_spcs_basic/snowflake.yml @@ -0,0 +1,29 @@ +# This is a project definition file, a required component if you intend to use Snowflake CLI in a project directory such as this template. + +definition_version: 1 +native_app: + name: + source_stage: app_src.stage + artifacts: + - src: app/* + dest: ./ + +definition_version: 2 +entities: + pkg: + type: application package + identifier: <% fn.concat_ids('_pkg', ctx.env.suffix) %> + manifest: app/manifest.yml + artifacts: + - src: app/* + dest: ./ + + app: + type: application + from: + target: pkg + identifier: <% fn.concat_ids('', ctx.env.suffix) %> + +env: + suffix: <% fn.concat_ids('_', fn.sanitize_id(fn.get_username('unknown_user')) | lower) %> + diff --git a/app_spcs_basic/template.yml b/app_spcs_basic/template.yml new file mode 100644 index 0000000..9f3c9b5 --- /dev/null +++ b/app_spcs_basic/template.yml @@ -0,0 +1,8 @@ +minimum_cli_version: "2.8.0" +files_to_render: + - snowflake.yml +variables: + - name: project_name + prompt: "Project identifier" + default: my_native_app_project + type: string diff --git a/app_streamlit_java/.gitignore b/app_streamlit_java/.gitignore new file mode 100644 index 0000000..86f603c --- /dev/null +++ b/app_streamlit_java/.gitignore @@ -0,0 +1,3 @@ +snowflake.local.yml +output/** +target diff --git a/app_streamlit_java/README.md b/app_streamlit_java/README.md new file mode 100644 index 0000000..24b423b --- /dev/null +++ b/app_streamlit_java/README.md @@ -0,0 +1,64 @@ +## Introduction + +This is an example template for a Snowflake Native App project which demonstrates the use of Java extension code and adding Streamlit code. This template is meant to guide developers towards a possible project structure on the basis of functionality, as well as to indicate the contents of some common and useful files. + + +# How to build/test this template +## Build the jar +You need to build the Java application (module-add) to get the .jar file, assuming JDK and Maven are installed and properly configured. +``` +cd src/module-add/ +mvn clean install +mvn package +``` + +Similarly, you can also use your own build steps for any other languages supported by Snowflake that you wish to write your code in, and any build automation tools you prefer. For more information on supported languages, visit [docs](https://docs.snowflake.com/en/developer-guide/stored-procedures-vs-udfs#label-sp-udf-languages). + +## Run the app +Create or update an application package in your Snowflake account, upload application artifacts to a stage in the application package, and create or update an application object in the same account based on the uploaded artifacts. +``` +snow app run +``` + +For more information, please refer to the Snowflake Documentation on installing and using Snowflake CLI to create a Snowflake Native App. + +# Directory Structure +## `/app` +This directory holds your Snowflake Native App files. + +### `/app/README.md` +Exposed to the account installing the Snowflake Native App with details on what it does and how to use it. + +### `/app/manifest.yml` +Defines properties required by the application package. Find more details at the [Manifest Documentation.](https://docs.snowflake.com/en/developer-guide/native-apps/creating-manifest) + +### `/app/setup_script.sql` +Contains SQL statements that are run when an account installs or upgrades a Snowflake Native App. + +## `/scripts` +You can add any additional scripts such as `.sql` and `.jinja` files here. One of the common use cases for such a script is to be able to add shared content from external databases to your application package, which makes it available to be used in the setup script that runs during Snowflake Native App installation. +_Note: See the note at the end of `snowflake.yml` if you decide to use the scripts. _ + + +## `/src` +This directory contains code organization by functionality, such as one distinct module for Streamlit related code, and another module for "number add" functionality, which is used an example in this template. +``` +/src + |-module-add + | |-main + | | |-java/com/snowflake/add + | | |-Add.java + | | + | |-test + | |-java/com/snowflake/add + | |-AddTest.java + |module-ui + | |-src + | |-ui.py + | |-environment.yml +``` + +## `snowflake.yml` +`snowflake.yml` is used by Snowflake CLI to discover your project's code and interact with snowflake with all relevant privileges and grants. + +For more information, please refer to the Snowflake Documentation on installing and using Snowflake CLI to create a Snowflake Native App. diff --git a/app_streamlit_java/app/README.md b/app_streamlit_java/app/README.md new file mode 100644 index 0000000..ae54793 --- /dev/null +++ b/app_streamlit_java/app/README.md @@ -0,0 +1,3 @@ +# Snowflake Native App - Add + +This is a sample Snowflake Native App that adds two numbers using Streamlit and Java. diff --git a/app_streamlit_java/app/manifest.yml b/app_streamlit_java/app/manifest.yml new file mode 100644 index 0000000..e8865f7 --- /dev/null +++ b/app_streamlit_java/app/manifest.yml @@ -0,0 +1,13 @@ +# For more information on creating manifest, go to https://docs.snowflake.com/en/developer-guide/native-apps/creating-manifest +manifest_version: 1 + +version: + name: Dev + label: "Dev Version" + comment: "Default version used for development. Override for actual deployment." + +artifacts: + setup_script: setup_script.sql + readme: README.md + default_streamlit: core.ui + extension_code: true diff --git a/app_streamlit_java/app/setup_script.sql b/app_streamlit_java/app/setup_script.sql new file mode 100644 index 0000000..b3d84c4 --- /dev/null +++ b/app_streamlit_java/app/setup_script.sql @@ -0,0 +1,32 @@ +-- This is the setup script that runs while installing a Snowflake Native App in a consumer account. +-- For more information on how to create setup file, visit https://docs.snowflake.com/en/developer-guide/native-apps/creating-setup-script + +-- A general guideline to building this script looks like: +-- 1. Create application roles +CREATE APPLICATION ROLE IF NOT EXISTS app_public; + +-- 2. Create a versioned schema to hold those UDFs/Stored Procedures +CREATE OR ALTER VERSIONED SCHEMA core; +GRANT USAGE ON SCHEMA core TO APPLICATION ROLE app_public; + +-- 3. Create UDFs and Stored Procedures using the java code you wrote in src/module-add, as shown below. +CREATE or REPLACE FUNCTION core.add(num1 NUMBER, num2 NUMBER) + RETURNS NUMBER + LANGUAGE JAVA + IMPORTS = ('/module-add/add-1.0-SNAPSHOT.jar') + HANDLER='com.snowflake.add.Add.two'; + +-- 4. Grant appropriate privileges over these objects to your application roles. +GRANT USAGE ON FUNCTION core.add(NUMBER, NUMBER) TO APPLICATION ROLE app_public; + +-- 5. Create a streamlit object using the code you wrote in you wrote in src/module-ui, as shown below. +-- The `from` value is derived from the stage path described in snowflake.yml +CREATE STREAMLIT core.ui + FROM '/streamlit/' + MAIN_FILE = 'ui.py'; + + +-- 6. Grant appropriate privileges over these objects to your application roles. +GRANT USAGE ON STREAMLIT core.ui TO APPLICATION ROLE app_public; + +-- A detailed explanation can be found at https://docs.snowflake.com/en/developer-guide/native-apps/adding-streamlit diff --git a/app_streamlit_java/scripts/any-provider-setup.sql b/app_streamlit_java/scripts/any-provider-setup.sql new file mode 100644 index 0000000..ba99387 --- /dev/null +++ b/app_streamlit_java/scripts/any-provider-setup.sql @@ -0,0 +1,2 @@ +-- Any script that should be run by the provider, such as creating and populating database objects that need to be shared with the application package. +-- This script must be idempotent. diff --git a/app_streamlit_java/scripts/shared-content.sql b/app_streamlit_java/scripts/shared-content.sql new file mode 100644 index 0000000..8491757 --- /dev/null +++ b/app_streamlit_java/scripts/shared-content.sql @@ -0,0 +1,12 @@ +-- An example script to share your data with consumer -- + +-- Scenario: You want to create an application package which is able to access some data that resides in a different database in your account. +-- Let this database be called past_add_transactions, with a schema called core, and a table in the schema called transactions. + +-- To share past_add_transactions.core.transactions with your application package, we recommend carrying out the following steps: +-- 1. Create a view over the table past_add_transactions.core.transactions that can be shared with the application package, such as past_add_transactions.core.transactions_v; +-- 2. Grant reference_usage on database past_add_transactions to share in application package; +-- 3. Grant usage on schema past_add_transactions.core to share in the application package; +-- 4. Grant select/any required privilege on the view past_add_transactions.core.transactions_v to share in application package; + +-- For more information, refer to https://docs.snowflake.com/en/developer-guide/native-apps/preparing-data-content diff --git a/app_streamlit_java/snowflake.yml b/app_streamlit_java/snowflake.yml new file mode 100644 index 0000000..7f44fc8 --- /dev/null +++ b/app_streamlit_java/snowflake.yml @@ -0,0 +1,51 @@ +# This is a project definition file, a required component if you intend to use Snowflake CLI in a project directory such as this template. + +definition_version: 1 +native_app: + name: + source_stage: app_src.stage + artifacts: + - src: app/* + dest: ./ + - src: src/module-add/target/add-1.0-SNAPSHOT.jar + dest: module-add/add-1.0-SNAPSHOT.jar + - src: src/module-ui/src/* + dest: streamlit/ + +definition_version: 2 +entities: + pkg: + type: application package + identifier: <% fn.concat_ids('_pkg', ctx.env.suffix) %> + manifest: app/manifest.yml + artifacts: + - src: app/* + dest: ./ + - src: src/module-add/target/add-1.0-SNAPSHOT.jar + dest: module-add/add-1.0-SNAPSHOT.jar + - src: src/module-ui/src/* + dest: streamlit/ + + app: + type: application + from: + target: pkg + identifier: <% fn.concat_ids('', ctx.env.suffix) %> + +env: + suffix: <% fn.concat_ids('_', fn.sanitize_id(fn.get_username('unknown_user')) | lower) %> + + + +# If you added any sql scripts under scripts/, you should add the following snippet after `artifacts` under `native_app`. +# package: +# scripts: +# - scripts/any-provider-setup.sql +# - scripts/shared-content.sql + +# If you added any sql scripts under scripts/, you should add the following snippet after `artifacts` under `entities.pkg`: +# meta: +# post_deploy: +# - sql_script: scripts/any-provider-setup.sql +# - sql_script: scripts/shared-content.sql + diff --git a/app_streamlit_java/src/module-add/.gitignore b/app_streamlit_java/src/module-add/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/app_streamlit_java/src/module-add/pom.xml b/app_streamlit_java/src/module-add/pom.xml new file mode 100644 index 0000000..c115652 --- /dev/null +++ b/app_streamlit_java/src/module-add/pom.xml @@ -0,0 +1,40 @@ + + + + 4.0.0 + + com.snowflake.add + add + 1.0-SNAPSHOT + + add + + + UTF-8 + 1.8 + 1.8 + 11 + + + + + junit + junit + 4.11 + test + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + + + + diff --git a/app_streamlit_java/src/module-add/src/main/java/com/snowflake/add/Add.java b/app_streamlit_java/src/module-add/src/main/java/com/snowflake/add/Add.java new file mode 100644 index 0000000..9dc2aaa --- /dev/null +++ b/app_streamlit_java/src/module-add/src/main/java/com/snowflake/add/Add.java @@ -0,0 +1,12 @@ +/** + * This file should contain the classes, and their methods, that you plan to use + * for UDFs and Stored Procedures. + * You may also use scala code in .scala files. + */ +package com.snowflake.add; + +public class Add { + public static int two(int num1, int num2) { + return num1 + num2; + } +} diff --git a/app_streamlit_java/src/module-add/src/test/java/com/snowflake/add/AddTest.java b/app_streamlit_java/src/module-add/src/test/java/com/snowflake/add/AddTest.java new file mode 100644 index 0000000..bba8d72 --- /dev/null +++ b/app_streamlit_java/src/module-add/src/test/java/com/snowflake/add/AddTest.java @@ -0,0 +1,15 @@ +/** + * This is a sample file that should contain any unit tests for your Java code. + */ +package com.snowflake.add; + +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class AddTest { + @Test + public void shouldAnswerWithTrue() { + assertTrue(Add.two(1, 2) == 3); + } +} diff --git a/app_streamlit_java/src/module-ui/src/environment.yml b/app_streamlit_java/src/module-ui/src/environment.yml new file mode 100644 index 0000000..0e200d7 --- /dev/null +++ b/app_streamlit_java/src/module-ui/src/environment.yml @@ -0,0 +1,6 @@ +# This file is used to install packages used by the Streamlit App. +# For more details, refer to https://docs.snowflake.com/en/developer-guide/streamlit/create-streamlit-sql#label-streamlit-install-packages-manual +channels: +- snowflake +dependencies: +- streamlit=1.26.0 diff --git a/app_streamlit_java/src/module-ui/src/ui.py b/app_streamlit_java/src/module-ui/src/ui.py new file mode 100644 index 0000000..9498228 --- /dev/null +++ b/app_streamlit_java/src/module-ui/src/ui.py @@ -0,0 +1,24 @@ +# Import python packages +import streamlit as st +from snowflake.snowpark.context import get_active_session + +# Write directly to the app +st.title("Hello Snowflake!") +st.write(""" + The sum of the two numbers is calculated by the Java Add.two() function + which is called from core.add() UDF defined in your setup_script.sql. +""") + +num1 = st.number_input('First number', value=1) +num2 = st.number_input('Second number', value=1) +# Get the current credentials +session = get_active_session() + +# Create an example data frame +data_frame = session.sql("SELECT core.add(%s, %s);" % (num1, num2)) + +# Execute the query and convert it into a Pandas data frame +queried_data = data_frame.to_pandas() + +# Display the Pandas data frame as a Streamlit data frame. +st.dataframe(queried_data, use_container_width=True) diff --git a/app_streamlit_java/template.yml b/app_streamlit_java/template.yml new file mode 100644 index 0000000..9f3c9b5 --- /dev/null +++ b/app_streamlit_java/template.yml @@ -0,0 +1,8 @@ +minimum_cli_version: "2.8.0" +files_to_render: + - snowflake.yml +variables: + - name: project_name + prompt: "Project identifier" + default: my_native_app_project + type: string diff --git a/app_streamlit_js/.gitignore b/app_streamlit_js/.gitignore new file mode 100644 index 0000000..86f603c --- /dev/null +++ b/app_streamlit_js/.gitignore @@ -0,0 +1,3 @@ +snowflake.local.yml +output/** +target diff --git a/app_streamlit_js/README.md b/app_streamlit_js/README.md new file mode 100644 index 0000000..9bb8b96 --- /dev/null +++ b/app_streamlit_js/README.md @@ -0,0 +1,47 @@ +## Introduction + +This is an example template for a Snowflake Native App project which demonstrates the use of JavaScript extension code and adding Streamlit code. This template is meant to guide developers towards a possible project structure on the basis of functionality, as well as to indicate the contents of some common and useful files. + + +# How to build/test this template + +## Run the app +Create or update an application package in your Snowflake account, upload application artifacts to a stage in the application package, and create or update an application object in the same account based on the uploaded artifacts. +``` +snow app run +``` + +For more information, please refer to the Snowflake Documentation on installing and using Snowflake CLI to create a Snowflake Native App. + +# Directory Structure +## `/app` +This directory holds your Snowflake Native App files. + +### `/app/README.md` +Exposed to the account installing the Snowflake Native App with details on what it does and how to use it. + +### `/app/manifest.yml` +Defines properties required by the application package. Find more details at the [Manifest Documentation.](https://docs.snowflake.com/en/developer-guide/native-apps/creating-manifest) + +### `/app/setup_script.sql` +Contains SQL statements that are run when an account installs or upgrades a Snowflake Native App. + +## `/scripts` +You can add any additional scripts such as `.sql` and `.jinja` files here. One of the common use cases for such a script is to be able to add shared content from external databases to your application package, which makes it available to be used in the setup script that runs during Snowflake Native App installation. +_Note: See the note at the end of `snowflake.yml` if you decide to use the scripts. _ + + +## `/src` +This directory contains code organization by functionality, such as one distinct module for Streamlit related code. The JavaScript code is defined in the UDF from the [setup_script.sql](app/setup_script.sql) file. +``` +/src + |module-ui + | |-src + | |-ui.py + | |-environment.yml +``` + +## `snowflake.yml` +`snowflake.yml` is used by Snowflake CLI to discover your project's code and interact with snowflake with all relevant privileges and grants. + +For more information, please refer to the Snowflake Documentation on installing and using Snowflake CLI to create a Snowflake Native App. diff --git a/app_streamlit_js/app/README.md b/app_streamlit_js/app/README.md new file mode 100644 index 0000000..606bfd7 --- /dev/null +++ b/app_streamlit_js/app/README.md @@ -0,0 +1,3 @@ +# Snowflake Native App - Add + +This is a sample Snowflake Native App that adds two numbers using Streamlit and JavaScript. diff --git a/app_streamlit_js/app/manifest.yml b/app_streamlit_js/app/manifest.yml new file mode 100644 index 0000000..e8865f7 --- /dev/null +++ b/app_streamlit_js/app/manifest.yml @@ -0,0 +1,13 @@ +# For more information on creating manifest, go to https://docs.snowflake.com/en/developer-guide/native-apps/creating-manifest +manifest_version: 1 + +version: + name: Dev + label: "Dev Version" + comment: "Default version used for development. Override for actual deployment." + +artifacts: + setup_script: setup_script.sql + readme: README.md + default_streamlit: core.ui + extension_code: true diff --git a/app_streamlit_js/app/setup_script.sql b/app_streamlit_js/app/setup_script.sql new file mode 100644 index 0000000..770f587 --- /dev/null +++ b/app_streamlit_js/app/setup_script.sql @@ -0,0 +1,31 @@ +-- This is the setup script that runs while installing a Snowflake Native App in a consumer account. +-- For more information on how to create setup file, visit https://docs.snowflake.com/en/developer-guide/native-apps/creating-setup-script + +-- A general guideline to building this script looks like: +-- 1. Create application roles +CREATE APPLICATION ROLE IF NOT EXISTS app_public; + +-- 2. Create a versioned schema to hold those UDFs/Stored Procedures +CREATE OR ALTER VERSIONED SCHEMA core; +GRANT USAGE ON SCHEMA core TO APPLICATION ROLE app_public; + +-- 3. Create UDFs and Stored Procedures using the JavaScript handler. +CREATE or REPLACE FUNCTION core.add(NUM1 DOUBLE, NUM2 DOUBLE) + RETURNS DOUBLE + LANGUAGE JAVASCRIPT + AS 'return NUM1 + NUM2;'; + +-- 4. Grant appropriate privileges over these objects to your application roles. +GRANT USAGE ON FUNCTION core.add(DOUBLE, DOUBLE) TO APPLICATION ROLE app_public; + +-- 5. Create a streamlit object using the code you wrote in you wrote in src/module-ui, as shown below. +-- The `from` value is derived from the stage path described in snowflake.yml +CREATE STREAMLIT core.ui + FROM '/streamlit/' + MAIN_FILE = 'ui.py'; + + +-- 6. Grant appropriate privileges over these objects to your application roles. +GRANT USAGE ON STREAMLIT core.ui TO APPLICATION ROLE app_public; + +-- A detailed explanation can be found at https://docs.snowflake.com/en/developer-guide/native-apps/adding-streamlit diff --git a/app_streamlit_js/scripts/any-provider-setup.sql b/app_streamlit_js/scripts/any-provider-setup.sql new file mode 100644 index 0000000..ba99387 --- /dev/null +++ b/app_streamlit_js/scripts/any-provider-setup.sql @@ -0,0 +1,2 @@ +-- Any script that should be run by the provider, such as creating and populating database objects that need to be shared with the application package. +-- This script must be idempotent. diff --git a/app_streamlit_js/scripts/shared-content.sql b/app_streamlit_js/scripts/shared-content.sql new file mode 100644 index 0000000..8491757 --- /dev/null +++ b/app_streamlit_js/scripts/shared-content.sql @@ -0,0 +1,12 @@ +-- An example script to share your data with consumer -- + +-- Scenario: You want to create an application package which is able to access some data that resides in a different database in your account. +-- Let this database be called past_add_transactions, with a schema called core, and a table in the schema called transactions. + +-- To share past_add_transactions.core.transactions with your application package, we recommend carrying out the following steps: +-- 1. Create a view over the table past_add_transactions.core.transactions that can be shared with the application package, such as past_add_transactions.core.transactions_v; +-- 2. Grant reference_usage on database past_add_transactions to share in application package; +-- 3. Grant usage on schema past_add_transactions.core to share in the application package; +-- 4. Grant select/any required privilege on the view past_add_transactions.core.transactions_v to share in application package; + +-- For more information, refer to https://docs.snowflake.com/en/developer-guide/native-apps/preparing-data-content diff --git a/app_streamlit_js/snowflake.yml b/app_streamlit_js/snowflake.yml new file mode 100644 index 0000000..e457818 --- /dev/null +++ b/app_streamlit_js/snowflake.yml @@ -0,0 +1,47 @@ +# This is a project definition file, a required component if you intend to use Snowflake CLI in a project directory such as this template. + +definition_version: 1 +native_app: + name: + source_stage: app_src.stage + artifacts: + - src: app/* + dest: ./ + - src: src/module-ui/src/* + dest: streamlit/ + +definition_version: 2 +entities: + pkg: + type: application package + identifier: <% fn.concat_ids('_pkg', ctx.env.suffix) %> + manifest: app/manifest.yml + artifacts: + - src: app/* + dest: ./ + - src: src/module-ui/src/* + dest: streamlit/ + + app: + type: application + from: + target: pkg + identifier: <% fn.concat_ids('', ctx.env.suffix) %> + +env: + suffix: <% fn.concat_ids('_', fn.sanitize_id(fn.get_username('unknown_user')) | lower) %> + + + +# If you added any sql scripts under scripts/, you should add the following snippet after `artifacts` under `native_app`. +# package: +# scripts: +# - scripts/any-provider-setup.sql +# - scripts/shared-content.sql + +# If you added any sql scripts under scripts/, you should add the following snippet after `artifacts` under `entities.pkg`: +# meta: +# post_deploy: +# - sql_script: scripts/any-provider-setup.sql +# - sql_script: scripts/shared-content.sql + diff --git a/app_streamlit_js/src/module-ui/src/environment.yml b/app_streamlit_js/src/module-ui/src/environment.yml new file mode 100644 index 0000000..0e200d7 --- /dev/null +++ b/app_streamlit_js/src/module-ui/src/environment.yml @@ -0,0 +1,6 @@ +# This file is used to install packages used by the Streamlit App. +# For more details, refer to https://docs.snowflake.com/en/developer-guide/streamlit/create-streamlit-sql#label-streamlit-install-packages-manual +channels: +- snowflake +dependencies: +- streamlit=1.26.0 diff --git a/app_streamlit_js/src/module-ui/src/ui.py b/app_streamlit_js/src/module-ui/src/ui.py new file mode 100644 index 0000000..4466e6f --- /dev/null +++ b/app_streamlit_js/src/module-ui/src/ui.py @@ -0,0 +1,24 @@ +# Import python packages +import streamlit as st +from snowflake.snowpark import Session + +# Write directly to the app +st.title("Hello Snowflake!") +st.write(""" + The sum of the two numbers is calculated by the JavaScript + core.add() UDF defined in your setup_script.sql. +""") + +num1 = st.number_input('First number', value=1) +num2 = st.number_input('Second number', value=1) +# Get the current credentials +session = Session.builder.getOrCreate() + +# Create an example data frame +data_frame = session.sql("SELECT core.add(%s, %s);" % (num1, num2)) + +# Execute the query and convert it into a Pandas data frame +queried_data = data_frame.to_pandas() + +# Display the Pandas data frame as a Streamlit data frame. +st.dataframe(queried_data, use_container_width=True) diff --git a/app_streamlit_js/template.yml b/app_streamlit_js/template.yml new file mode 100644 index 0000000..9f3c9b5 --- /dev/null +++ b/app_streamlit_js/template.yml @@ -0,0 +1,8 @@ +minimum_cli_version: "2.8.0" +files_to_render: + - snowflake.yml +variables: + - name: project_name + prompt: "Project identifier" + default: my_native_app_project + type: string diff --git a/app_streamlit_python/.gitignore b/app_streamlit_python/.gitignore new file mode 100644 index 0000000..d6aef68 --- /dev/null +++ b/app_streamlit_python/.gitignore @@ -0,0 +1,4 @@ +snowflake.local.yml +output/ +**/__pycache__/ +**/.pytest_cache/ diff --git a/app_streamlit_python/README.md b/app_streamlit_python/README.md new file mode 100644 index 0000000..11eedcb --- /dev/null +++ b/app_streamlit_python/README.md @@ -0,0 +1,90 @@ +## Introduction + +This is an example template for a Snowflake Native App project which demonstrates the use of Python extension code and adding Streamlit code. This template is meant to guide developers towards a possible project structure on the basis of functionality, as well as to indicate the contents of some common and useful files. + +Since this template contains Python files only, you do not need to perform any additional steps to build the source code. You can directly go to the next section. However, if there were any source code that needed to be built, you must manually perform the build steps here before proceeding to the next section. + +Similarly, you can also use your own build steps for any other languages supported by Snowflake that you wish to write your code in. For more information on supported languages, visit [docs](https://docs.snowflake.com/en/developer-guide/stored-procedures-vs-udfs#label-sp-udf-languages). + +## Run the app +Create or update an application package in your Snowflake account, upload application artifacts to a stage in the application package, and create or update an application object in the same account based on the uploaded artifacts. +``` +snow app run +``` + +For more information, please refer to the Snowflake Documentation on installing and using Snowflake CLI to create a Snowflake Native App. +# Directory Structure +## `/app` +This directory holds your Snowflake Native App files. + +### `/app/README.md` +Exposed to the account installing the application with details on what it does and how to use it. + +### `/app/manifest.yml` +Defines properties required by the application package. Find more details at the [Manifest Documentation.](https://docs.snowflake.com/en/developer-guide/native-apps/creating-manifest) + +### `/app/setup_script.sql` +Contains SQL statements that are run when a consumer installs or upgrades a Snowflake Native App in their account. + +## `/scripts` +You can add any additional scripts such as `.sql` and `.jinja` files here. One common use case for such a script is to add shared content from external databases to your application package. This allows you to refer to the external database in the setup script that runs when a Snowflake Native App is installed. +_Note: See the note at the end of `snowflake.yml` if you decide to use the scripts. _ + + +## `/src` +This directory contains code organization by functionality, such as one distinct module for Streamlit related code, and another module for "number add" functionality, which is used an example in this template. +``` +/src + |-module-add + | |-main + | | |-python + | | |-add.py + | | + | |-test + | |-python + | |-add_test.py + | + |-module-ui + | |-src + | |-ui.py + | |-environment.yml + | |-test + | |-test_ui.py +``` + +## `snowflake.yml.jinja` +Snowflake CLI uses the `snowflake.yml` file to discover your project's code and interact with Snowflake using all relevant privileges and grants. + +For more information, please refer to the Snowflake Documentation on installing and using Snowflake CLI to create a Snowflake Native App. + +## Unit tests +To set up and run unit tests, please follow the steps below. + +### Set up testing conda environment (First Time setup) + +Go to the project's root directory where you can find `local_test_env.yml` and run the following command once to set up a conda environment with the correct packages. Please note that the version of test packages may differ from the version of packages in Snowflake, so you will need to be careful with any differences in behavior. + +``` +conda env create --file local_test_env.yml +``` + +This will create a conda environment with the name `streamlit-python-testing`. + +### Run unit tests +To run unit tests, follow these steps: + +#### Activate conda environment +You will need to activate this conda environment once per command line session: +``` +conda activate streamlit-python-testing +``` +To deactivate and use your current command line session for other tasks, run the following: +``` +conda deactivate +``` +#### Run Pytest +To run the example tests provided, execute the following command from the project's root: +``` +pytest +``` +Note that there is a pytest.ini file specifying the location of the source code that we are testing. diff --git a/app_streamlit_python/app/README.md b/app_streamlit_python/app/README.md new file mode 100644 index 0000000..c80db31 --- /dev/null +++ b/app_streamlit_python/app/README.md @@ -0,0 +1,3 @@ +# Snowflake Native App - Add + +This is a sample Snowflake Native App that adds two numbers using Streamlit and Python. diff --git a/app_streamlit_python/app/manifest.yml b/app_streamlit_python/app/manifest.yml new file mode 100644 index 0000000..e8865f7 --- /dev/null +++ b/app_streamlit_python/app/manifest.yml @@ -0,0 +1,13 @@ +# For more information on creating manifest, go to https://docs.snowflake.com/en/developer-guide/native-apps/creating-manifest +manifest_version: 1 + +version: + name: Dev + label: "Dev Version" + comment: "Default version used for development. Override for actual deployment." + +artifacts: + setup_script: setup_script.sql + readme: README.md + default_streamlit: core.ui + extension_code: true diff --git a/app_streamlit_python/app/setup_script.sql b/app_streamlit_python/app/setup_script.sql new file mode 100644 index 0000000..93ad078 --- /dev/null +++ b/app_streamlit_python/app/setup_script.sql @@ -0,0 +1,42 @@ +-- This is the setup script that runs while installing a Snowflake Native App in a consumer account. +-- For more information on how to create setup file, visit https://docs.snowflake.com/en/developer-guide/native-apps/creating-setup-script + +-- A general guideline to building this script looks like: +-- 1. Create application roles +CREATE APPLICATION ROLE IF NOT EXISTS app_public; + +-- 2. Create a versioned schema to hold those UDFs/Stored Procedures +CREATE OR ALTER VERSIONED SCHEMA core; +GRANT USAGE ON SCHEMA core TO APPLICATION ROLE app_public; + +-- 3. Create UDFs and Stored Procedures using the python code you wrote in src/module-add, as shown below. +CREATE OR REPLACE FUNCTION core.add(x NUMBER, y NUMBER) + RETURNS NUMBER + LANGUAGE PYTHON + RUNTIME_VERSION=3.8 + PACKAGES=('snowflake-snowpark-python') + IMPORTS=('/module-add/add.py') + HANDLER='add.add_fn'; + +CREATE OR REPLACE PROCEDURE core.increment_by_one(x NUMBER) + RETURNS NUMBER + LANGUAGE PYTHON + RUNTIME_VERSION=3.8 + PACKAGES=('snowflake-snowpark-python') + IMPORTS=('/module-add/add.py') + HANDLER='add.increment_by_one_fn'; + +-- 4. Grant appropriate privileges over these objects to your application roles. +GRANT USAGE ON FUNCTION core.add(NUMBER, NUMBER) TO APPLICATION ROLE app_public; +GRANT USAGE ON PROCEDURE core.increment_by_one(NUMBER) TO APPLICATION ROLE app_public; + +-- 5. Create a streamlit object using the code you wrote in you wrote in src/module-ui, as shown below. +-- The `from` value is derived from the stage path described in snowflake.yml +CREATE STREAMLIT core.ui + FROM '/streamlit/' + MAIN_FILE = 'ui.py'; + +-- 6. Grant appropriate privileges over these objects to your application roles. +GRANT USAGE ON STREAMLIT core.ui TO APPLICATION ROLE app_public; + +-- A detailed explanation can be found at https://docs.snowflake.com/en/developer-guide/native-apps/adding-streamlit diff --git a/app_streamlit_python/local_test_env.yml b/app_streamlit_python/local_test_env.yml new file mode 100644 index 0000000..dec7358 --- /dev/null +++ b/app_streamlit_python/local_test_env.yml @@ -0,0 +1,12 @@ +# This file is used to install packages for local testing +name: streamlit-python-testing +channels: + - snowflake +dependencies: + - python=3.83.10 + - pip + - pip: + - pytest + - snowflake-cli-labs>=2.0.0,<3.0.0>=3.0.0 + - snowflake-snowpark-python>=1.15.0 + - streamlit>=1.28.0 # this version could be different than the streamlit version in Snowflake, and therefore, 100% compatibility is not guaranteed. diff --git a/app_streamlit_python/pytest.ini b/app_streamlit_python/pytest.ini new file mode 100644 index 0000000..ea614e6 --- /dev/null +++ b/app_streamlit_python/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +pythonpath=src/module-add/src/main/python src/module-ui/src diff --git a/app_streamlit_python/scripts/any-provider-setup.sql b/app_streamlit_python/scripts/any-provider-setup.sql new file mode 100644 index 0000000..27ee5d9 --- /dev/null +++ b/app_streamlit_python/scripts/any-provider-setup.sql @@ -0,0 +1,2 @@ +-- Any script that should be run as the provider, such as creating and populating database objects that need to be shared with the application package. +-- This script must be idempotent. diff --git a/app_streamlit_python/scripts/shared-content.sql b/app_streamlit_python/scripts/shared-content.sql new file mode 100644 index 0000000..8491757 --- /dev/null +++ b/app_streamlit_python/scripts/shared-content.sql @@ -0,0 +1,12 @@ +-- An example script to share your data with consumer -- + +-- Scenario: You want to create an application package which is able to access some data that resides in a different database in your account. +-- Let this database be called past_add_transactions, with a schema called core, and a table in the schema called transactions. + +-- To share past_add_transactions.core.transactions with your application package, we recommend carrying out the following steps: +-- 1. Create a view over the table past_add_transactions.core.transactions that can be shared with the application package, such as past_add_transactions.core.transactions_v; +-- 2. Grant reference_usage on database past_add_transactions to share in application package; +-- 3. Grant usage on schema past_add_transactions.core to share in the application package; +-- 4. Grant select/any required privilege on the view past_add_transactions.core.transactions_v to share in application package; + +-- For more information, refer to https://docs.snowflake.com/en/developer-guide/native-apps/preparing-data-content diff --git a/app_streamlit_python/snowflake.yml b/app_streamlit_python/snowflake.yml new file mode 100644 index 0000000..72110e4 --- /dev/null +++ b/app_streamlit_python/snowflake.yml @@ -0,0 +1,51 @@ +# This is a project definition file, a required component if you intend to use Snowflake CLI in a project directory such as this template. + +definition_version: 1 +native_app: + name: + source_stage: app_src.stage + artifacts: + - src: app/* + dest: ./ + - src: src/module-add/src/main/python/add.py + dest: module-add/add.py + - src: src/module-ui/src/* + dest: streamlit/ + +definition_version: 2 +entities: + pkg: + type: application package + identifier: <% fn.concat_ids('_pkg', ctx.env.suffix) %> + manifest: app/manifest.yml + artifacts: + - src: app/* + dest: ./ + - src: src/module-add/src/main/python/add.py + dest: module-add/add.py + - src: src/module-ui/src/* + dest: streamlit/ + + app: + type: application + from: + target: pkg + identifier: <% fn.concat_ids('', ctx.env.suffix) %> + +env: + suffix: <% fn.concat_ids('_', fn.sanitize_id(fn.get_username('unknown_user')) | lower) %> + + + +# If you added any sql scripts under scripts/, you should add the following snippet after `artifacts` under `native_app`. +# package: +# scripts: +# - scripts/any-provider-setup.sql +# - scripts/shared-content.sql + +# If you added any sql scripts under scripts/, you should add the following snippet after `artifacts` under `entities.pkg`: +# meta: +# post_deploy: +# - sql_script: scripts/any-provider-setup.sql +# - sql_script: scripts/shared-content.sql + diff --git a/app_streamlit_python/src/module-add/src/main/python/add.py b/app_streamlit_python/src/module-add/src/main/python/add.py new file mode 100644 index 0000000..5a4ee68 --- /dev/null +++ b/app_streamlit_python/src/module-add/src/main/python/add.py @@ -0,0 +1,15 @@ +# This is where you can create python functions, which can further +# be used to create Snowpark UDFs and Stored Procedures in your setup_script.sql file. + +from snowflake.snowpark import Session +from snowflake.snowpark.functions import lit + +# UDF example: +def add_fn(x: int, y: int) -> int: + return x + y + + +# Stored Procedure example: +def increment_by_one_fn(session: Session, x: int) -> int: + df = session.create_dataframe([[]]).select((lit(1) + lit(x)).as_('RESULT')) + return df.collect()[0]['RESULT'] diff --git a/app_streamlit_python/src/module-add/src/test/python/test_add.py b/app_streamlit_python/src/module-add/src/test/python/test_add.py new file mode 100644 index 0000000..2d1086e --- /dev/null +++ b/app_streamlit_python/src/module-add/src/test/python/test_add.py @@ -0,0 +1,22 @@ +# This is a sample file that can contain any unit tests for your python code. +# You can use your own testing frameworks as part of the tests too. +import pytest +from add import add_fn, increment_by_one_fn +from unittest.mock import MagicMock +from snowflake.snowpark import Session +from snowflake.snowpark.functions import lit +from functools import partial + +@pytest.fixture() +def session(): + session = Session.builder.config('local_testing', True).create() + yield session + session.close() + +# Unit tests for UDF +def test_add_fn(): + assert add_fn(1, 4) == 5 + +# Unit tests for Stored Procedure +def test_increment_by_one_fn(session): + assert increment_by_one_fn(session, 3) == 4 diff --git a/app_streamlit_python/src/module-ui/src/environment.yml b/app_streamlit_python/src/module-ui/src/environment.yml new file mode 100644 index 0000000..b79c26d --- /dev/null +++ b/app_streamlit_python/src/module-ui/src/environment.yml @@ -0,0 +1,7 @@ +# This file is used to install packages used by the Streamlit App. +# For more details, refer to https://docs.snowflake.com/en/developer-guide/streamlit/create-streamlit-sql#label-streamlit-install-packages-manual + +channels: +- snowflake +dependencies: +- streamlit=1.26.0 diff --git a/app_streamlit_python/src/module-ui/src/ui.py b/app_streamlit_python/src/module-ui/src/ui.py new file mode 100644 index 0000000..b56d22c --- /dev/null +++ b/app_streamlit_python/src/module-ui/src/ui.py @@ -0,0 +1,50 @@ +def run_streamlit(): + # Import python packages + # Streamlit app testing framework requires imports to reside here + # Streamlit app testing documentation: https://docs.streamlit.io/library/api-reference/app-testing + import pandas as pd + import streamlit as st + from snowflake.snowpark.functions import call_udf, col + from snowflake.snowpark import Session + + st.title('Hello Snowflake!') + + + st.header('UDF Example') + + st.write( + """The sum of the two numbers is calculated by the Python add_fn() function + which is called from core.add() UDF defined in your setup_script.sql. + """) + + # Get the current credentials + session = Session.builder.getOrCreate() + + num1 = st.number_input('First number', key='numToAdd1', value=1) + num2 = st.number_input('Second number', key='numToAdd2', value=1) + + # Create an example data frame + data_frame = session.create_dataframe([[num1, num2]], schema=['num1', 'num2']) + data_frame = data_frame.select(call_udf('core.add', col('num1'), col('num2'))) + + # Execute the query and convert it into a Pandas data frame + queried_data = data_frame.to_pandas() + + # Display the Pandas data frame as a Streamlit data frame. + st.dataframe(queried_data, use_container_width=True) + + + st.header('Stored Procedure Example') + + st.write( + """Incrementing a number by one is calculated by the Python increment_by_one_fn() function + which implements the core.increment_by_one() Stored Procedure defined in your setup_script.sql. + """) + + num_to_increment = st.number_input('Number to increment', key='numToIncrement', value=1) + result = session.call('core.increment_by_one', num_to_increment) + + st.dataframe(pd.DataFrame([[result]]), use_container_width=True) + +if __name__ == '__main__': + run_streamlit() diff --git a/app_streamlit_python/src/module-ui/test/test_ui.py b/app_streamlit_python/src/module-ui/test/test_ui.py new file mode 100644 index 0000000..82cc609 --- /dev/null +++ b/app_streamlit_python/src/module-ui/test/test_ui.py @@ -0,0 +1,29 @@ +from pytest import fixture +from snowflake.snowpark import Session +from snowflake.snowpark.functions import lit +from streamlit.testing.v1 import AppTest +import add as add +import ui as ui + +@fixture +def session(): + session = Session.builder.config('local_testing', True).create() + yield session + session.close() + +@fixture(autouse=True) +def set_up(session: Session): + session.sproc.register(add.increment_by_one_fn, name='core.increment_by_one') + session.udf.register(add.add_fn, name='core.add') + +def test_streamlit(): + at = AppTest.from_function(ui.run_streamlit) + at.run() + + at.number_input('numToAdd1').set_value(6).run() + at.number_input('numToAdd2').set_value(3).run() + assert (at.dataframe[0].value[:].values == [[9]]).all() + + at.number_input('numToIncrement').set_value(4).run() + assert (at.dataframe[1].value[:].values == [[5]]).all() + assert not at.exception diff --git a/app_streamlit_python/template.yml b/app_streamlit_python/template.yml new file mode 100644 index 0000000..c63f093 --- /dev/null +++ b/app_streamlit_python/template.yml @@ -0,0 +1,9 @@ +minimum_cli_version: "2.8.0" +files_to_render: + - snowflake.yml + - local_test_env.yml +variables: + - name: project_name + prompt: "Project identifier" + default: my_native_app_project + type: string