From 15745f60fb5fe702c4b6c57656f00a1efa410ae9 Mon Sep 17 00:00:00 2001 From: Patryk Czajka Date: Tue, 3 Sep 2024 10:50:30 +0200 Subject: [PATCH 1/8] update streamlit --- .pre-commit-config.yaml | 2 -- example_streamlit/snowflake.yml | 18 +++++++++++++++++- example_streamlit/template.yml | 1 + 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 65f6d2d..4b06049 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,8 +4,6 @@ repos: hooks: - id: trailing-whitespace - id: end-of-file-fixer - - id: check-yaml - exclude: .github/repo_meta.yaml - repo: https://github.com/codespell-project/codespell rev: v2.2.4 hooks: diff --git a/example_streamlit/snowflake.yml b/example_streamlit/snowflake.yml index 3f4d447..adbe846 100644 --- a/example_streamlit/snowflake.yml +++ b/example_streamlit/snowflake.yml @@ -1,4 +1,4 @@ -definition_version: "1.1" +definition_version: "1.1" streamlit: name: stage: @@ -8,3 +8,19 @@ streamlit: pages_dir: pages/ additional_source_files: - common/hello.py +definition_version: '2' +entities: + : + type: streamlit + identifier: + name: + main_file: streamlit_app.py + pages_dir: pages + query_warehouse: + stage: + artifacts: + - streamlit_app.py + - environment.yml + - pages + - common/hello.py + diff --git a/example_streamlit/template.yml b/example_streamlit/template.yml index 3d0c1ed..4aefdd7 100644 --- a/example_streamlit/template.yml +++ b/example_streamlit/template.yml @@ -1,3 +1,4 @@ +minimum_cli_version: "2.8.0" files_to_render: - snowflake.yml variables: From 5f8667ab1f3d81a2ab9a4b37b3f26b231ddc9b08 Mon Sep 17 00:00:00 2001 From: Patryk Czajka Date: Tue, 3 Sep 2024 11:43:01 +0200 Subject: [PATCH 2/8] update snowpark --- example_snowpark/snowflake.yml | 50 +++++++++++++++++++++++++++++++++- example_snowpark/template.yml | 1 + 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/example_snowpark/snowflake.yml b/example_snowpark/snowflake.yml index 69acc9a..d5d5bba 100644 --- a/example_snowpark/snowflake.yml +++ b/example_snowpark/snowflake.yml @@ -1,4 +1,4 @@ -definition_version: "1.1" +definition_version: "1.1" snowpark: project_name: "" stage_name: "" @@ -21,3 +21,51 @@ snowpark: handler: "procedures.test_procedure" signature: "" returns: string +definition_version: '2' + +mixins: + snowpark_shared: + artifacts: + - dest: + src: app/ + stage: + +entities: + + hello_function: + type: function + identifier: + name: hello_function + handler: functions.hello_function + signature: + - name: name + type: string + returns: string + meta: + use_mixins: + - snowpark_shared + + hello_procedure: + type: procedure + identifier: + name: hello_procedure + handler: procedures.hello_procedure + signature: + - name: name + type: string + returns: string + meta: + use_mixins: + - snowpark_shared + + test_procedure: + type: procedure + identifier: + name: test_procedure + handler: procedures.test_procedure + signature: '' + returns: string + meta: + use_mixins: + - snowpark_shared + diff --git a/example_snowpark/template.yml b/example_snowpark/template.yml index a94f277..81248e5 100644 --- a/example_snowpark/template.yml +++ b/example_snowpark/template.yml @@ -1,3 +1,4 @@ +minimum_cli_version: "2.8.0" files_to_render: - snowflake.yml variables: From 87b5779ea1e1c6c219c5199beeebeaa3eea785a4 Mon Sep 17 00:00:00 2001 From: Patryk Czajka Date: Tue, 3 Sep 2024 13:15:44 +0200 Subject: [PATCH 3/8] update tests --- .tests/test_examples.py | 72 ++++++++++++++++++++++++++++++++ .tests/test_render_templates.py | 48 +++++++++++++++++---- .tests/test_snowpark_examples.py | 32 -------------- 3 files changed, 113 insertions(+), 39 deletions(-) create mode 100644 .tests/test_examples.py delete mode 100644 .tests/test_snowpark_examples.py diff --git a/.tests/test_examples.py b/.tests/test_examples.py new file mode 100644 index 0000000..2552969 --- /dev/null +++ b/.tests/test_examples.py @@ -0,0 +1,72 @@ +import os +import subprocess +from pathlib import Path +from tempfile import TemporaryDirectory +from snowflake.cli.__about__ import VERSION +import pytest +from contextlib import contextmanager + +if VERSION < "2.8.0": + pytest.skip("This test requires CLI >= 2.8.0", allow_module_level=True) + + +@pytest.fixture() +def initialize_project(): + @contextmanager + def _initialize_project(template_name): + with TemporaryDirectory() as tmpdir: + project_dir = Path(tmpdir) / "project" + output = subprocess.check_output( + [ + "snow", + "init", + str(project_dir), + "--template", + template_name, + "--no-interactive", + ], + encoding="utf-8", + ) + assert "Initialized the new project in" in output + + old_cwd = os.getcwd() + os.chdir(project_dir) + yield project_dir + os.chdir(old_cwd) + + return _initialize_project + + +def test_snowpark_examples_functions_work_locally(initialize_project): + with initialize_project("example_snowpark") as snowpark_project: + output = subprocess.check_output( + ["python", str(snowpark_project / "app" / "functions.py"), "FooBar"], + encoding="utf-8", + ) + assert output.strip() == "Hello FooBar!" + + output = subprocess.check_output( + ["python", str(snowpark_project / "app" / "procedures.py"), "BazBar"], + encoding="utf-8", + ) + assert output.strip() == "Hello BazBar!" + + +def test_example_snowpark_yml_is_correct(initialize_project): + with initialize_project("example_snowpark") as snowpark_project: + output = subprocess.check_output( + ["snow", "snowpark", "build"], + encoding="utf-8", + ) + assert "Build done." in output + + +def test_example_streamlit_yml_is_correct(initialize_project): + with initialize_project("example_streamlit") as streamlit_project: + result = subprocess.run( + ["snow", "streamlit", "deploy"], + capture_output=True, + text=True, + encoding="utf-8", + ) + assert not "During evaluation of " in result.stdout + result.stderr diff --git a/.tests/test_render_templates.py b/.tests/test_render_templates.py index 7df3a66..d8459a7 100644 --- a/.tests/test_render_templates.py +++ b/.tests/test_render_templates.py @@ -1,15 +1,15 @@ -import pytest import subprocess -import yaml - from pathlib import Path from tempfile import TemporaryDirectory from typing import List +import pytest +import yaml from snowflake.cli.api.project.schemas.template import Template - +from snowflake.cli.__about__ import VERSION _REPO_ROOT = Path(__file__).parent.parent +from packaging.version import parse def _find_all_templates(): @@ -26,8 +26,7 @@ def _read_template_metadata(template_root: Path) -> Template: return Template(template_root, **yaml_contents) -def _gen_input_values(template_root: Path) -> List[str]: - metadata = _read_template_metadata(template_root) +def _gen_input_values(metadata: Template) -> List[str]: result = [] for variable in metadata.variables: value = { @@ -42,6 +41,12 @@ def _gen_input_values(template_root: Path) -> List[str]: @pytest.mark.parametrize("template_root", _find_all_templates()) def test_render_template(template_root): template_root = _REPO_ROOT / template_root + metadata = _read_template_metadata(template_root) + if metadata.minimum_cli_version and ( + parse(metadata.minimum_cli_version) > parse(VERSION) + ): + pytest.skip(f"Test requires CLI version >= {metadata.minimum_cli_version}") + with TemporaryDirectory() as tmpdir: project_path = Path(tmpdir) / "project" snow = subprocess.Popen( @@ -56,9 +61,38 @@ def test_render_template(template_root): stdout=subprocess.PIPE, ) try: - process_input = "\n".join(_gen_input_values(template_root)) + process_input = "\n".join(_gen_input_values(metadata)) # reasonable 60s timeout snow.communicate(input=process_input.encode(), timeout=60) except subprocess.TimeoutExpired: raise AssertionError("Timeout expired") assert snow.returncode == 0, f"Rendering finished with {snow.returncode}" + + +@pytest.mark.parametrize("template_root", _find_all_templates()) +def test_too_low_version_error(template_root): + template_root = _REPO_ROOT / template_root + metadata = _read_template_metadata(template_root) + if (not metadata.minimum_cli_version) or ( + parse(metadata.minimum_cli_version) <= parse(VERSION) + ): + pytest.skip("CLI version requirements fulfilled") + + with TemporaryDirectory() as tmpdir: + project_path = Path(tmpdir) / "project" + result = subprocess.run( + ["snow", "init", str(project_path), "--template-source", template_root], + capture_output=True, + text=True, + encoding="utf-8", + ) + assert result.returncode == 1 + assert result.stdout == "" + assert ( + f"Snowflake CLI version ({VERSION}) is too low - minimum version required by" + in result.stderr + ) + assert ( + f"template is {metadata.minimum_cli_version}. Please upgrade before continuing." + in result.stderr + ) diff --git a/.tests/test_snowpark_examples.py b/.tests/test_snowpark_examples.py deleted file mode 100644 index 0964b6d..0000000 --- a/.tests/test_snowpark_examples.py +++ /dev/null @@ -1,32 +0,0 @@ -import subprocess -from pathlib import Path -from tempfile import TemporaryDirectory - - -def test_snowpark_examples_functions_work_locally(): - with TemporaryDirectory() as tmpdir: - project_dir = Path(tmpdir) / "snowpark" - output = subprocess.check_output( - [ - "snow", - "init", - str(project_dir), - "--template", - "example_snowpark", - "--no-interactive", - ], - encoding="utf-8", - ) - assert "Initialized the new project in" in output - - output = subprocess.check_output( - ["python", str(project_dir / "app" / "functions.py"), "FooBar"], - encoding="utf-8", - ) - assert output.strip() == "Hello FooBar!" - - output = subprocess.check_output( - ["python", str(project_dir / "app" / "procedures.py"), "BazBar"], - encoding="utf-8", - ) - assert output.strip() == "Hello BazBar!" From 2525bbf3f2dfb1d59c2fcab417c7dfce32a53e33 Mon Sep 17 00:00:00 2001 From: Patryk Czajka Date: Tue, 3 Sep 2024 13:20:47 +0200 Subject: [PATCH 4/8] update workflows --- .github/workflows/tests.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index bd8644b..894fe8e 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -16,6 +16,7 @@ jobs: strategy: matrix: os: [ ubuntu-latest, windows-latest ] + cli_version: [2.7.0, 2.8.0, latest] steps: - uses: actions/checkout@v4 with: @@ -24,7 +25,11 @@ jobs: uses: actions/setup-python@v5 with: python-version: '3.11' + - name: Install dependencies + run: python -m pip install pytest snowflake-snowpark-python - name: Install Snowflake CLI - run: python -m pip install pytest snowflake-snowpark-python git+https://github.com/snowflakedb/snowflake-cli.git + uses: Snowflake-Labs/snowflake-cli-action@v1 + with: + cli-version: ${{ matrix.cli_version }} - name: Run tests run: python -m pytest .tests/ -vv From 9f0d3cb39567dcd0f1fd15107f881cea669fb1bb Mon Sep 17 00:00:00 2001 From: Patryk Czajka Date: Tue, 3 Sep 2024 13:23:42 +0200 Subject: [PATCH 5/8] update workflow --- .github/workflows/tests.yaml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 894fe8e..bce9883 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -25,11 +25,7 @@ jobs: uses: actions/setup-python@v5 with: python-version: '3.11' - - name: Install dependencies - run: python -m pip install pytest snowflake-snowpark-python - name: Install Snowflake CLI - uses: Snowflake-Labs/snowflake-cli-action@v1 - with: - cli-version: ${{ matrix.cli_version }} + run: python -m pip install pytest snowflake-snowpark-python git+https://github.com/snowflakedb/snowflake-cli.git==${{ matrix.cli_version }} - name: Run tests run: python -m pytest .tests/ -vv From 551268be87209be893d71a3b7947d5a7a02a3484 Mon Sep 17 00:00:00 2001 From: Patryk Czajka Date: Tue, 3 Sep 2024 13:27:43 +0200 Subject: [PATCH 6/8] fix workflow --- .github/workflows/tests.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index bce9883..8e996e2 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -16,7 +16,7 @@ jobs: strategy: matrix: os: [ ubuntu-latest, windows-latest ] - cli_version: [2.7.0, 2.8.0, latest] + cli_version: ['==2.7.0', '==2.8.0', ''] # check 2.X and latest release steps: - uses: actions/checkout@v4 with: @@ -26,6 +26,6 @@ jobs: with: python-version: '3.11' - name: Install Snowflake CLI - run: python -m pip install pytest snowflake-snowpark-python git+https://github.com/snowflakedb/snowflake-cli.git==${{ matrix.cli_version }} + run: python -m pip install pytest snowflake-snowpark-python snowflake-cli-labs${{ matrix.cli_version }} - name: Run tests run: python -m pytest .tests/ -vv From d4da38e267bf52ae863c8532f7b7b27590beac18 Mon Sep 17 00:00:00 2001 From: Bruno Dufour Date: Thu, 26 Sep 2024 14:32:17 -0400 Subject: [PATCH 7/8] Added native app templates --- .github/CODEOWNERS | 2 + app_basic/.gitignore | 2 + app_basic/README.md | 18 ++++ 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 | 9 ++ 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 | 70 ++++++++++++ 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 | 9 ++ app_streamlit_js/.gitignore | 3 + app_streamlit_js/README.md | 53 ++++++++++ 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 | 9 ++ app_streamlit_python/.gitignore | 4 + app_streamlit_python/README.md | 96 +++++++++++++++++ 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 | 10 ++ 63 files changed, 1351 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..6895d0c --- /dev/null +++ b/app_basic/README.md @@ -0,0 +1,18 @@ +## 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..5abcf06 --- /dev/null +++ b/app_basic/template.yml @@ -0,0 +1,9 @@ +minimum_cli_version: "2.8.0" +files_to_render: + - snowflake.yml + - README.md +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..6aec6d8 --- /dev/null +++ b/app_streamlit_java/README.md @@ -0,0 +1,70 @@ +## 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. + +### 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_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..5abcf06 --- /dev/null +++ b/app_streamlit_java/template.yml @@ -0,0 +1,9 @@ +minimum_cli_version: "2.8.0" +files_to_render: + - snowflake.yml + - README.md +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..6ce0b6b --- /dev/null +++ b/app_streamlit_js/README.md @@ -0,0 +1,53 @@ +## 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. + +### 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_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..5abcf06 --- /dev/null +++ b/app_streamlit_js/template.yml @@ -0,0 +1,9 @@ +minimum_cli_version: "2.8.0" +files_to_render: + - snowflake.yml + - README.md +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..c215907 --- /dev/null +++ b/app_streamlit_python/README.md @@ -0,0 +1,96 @@ +## 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` +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. + +### 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. + + +## 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..be4f904 --- /dev/null +++ b/app_streamlit_python/template.yml @@ -0,0 +1,10 @@ +minimum_cli_version: "2.8.0" +files_to_render: + - snowflake.yml + - local_test_env.yml + - README.md +variables: + - name: project_name + prompt: "Project identifier" + default: my_native_app_project + type: string From 402c193f4aac125af1925c3fdadf9b0b5acd62b3 Mon Sep 17 00:00:00 2001 From: Bruno Dufour Date: Tue, 1 Oct 2024 11:29:36 -0400 Subject: [PATCH 8/8] Fixed validation warnings; ensure unique project names --- app_spcs_basic/template.yml | 2 +- app_streamlit_java/app/setup_script.sql | 2 +- app_streamlit_java/template.yml | 2 +- app_streamlit_js/app/setup_script.sql | 2 +- app_streamlit_js/template.yml | 2 +- app_streamlit_python/app/setup_script.sql | 2 +- app_streamlit_python/template.yml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app_spcs_basic/template.yml b/app_spcs_basic/template.yml index 9f3c9b5..6cc998d 100644 --- a/app_spcs_basic/template.yml +++ b/app_spcs_basic/template.yml @@ -4,5 +4,5 @@ files_to_render: variables: - name: project_name prompt: "Project identifier" - default: my_native_app_project + default: my_app_spcs_basic type: string diff --git a/app_streamlit_java/app/setup_script.sql b/app_streamlit_java/app/setup_script.sql index b3d84c4..523f750 100644 --- a/app_streamlit_java/app/setup_script.sql +++ b/app_streamlit_java/app/setup_script.sql @@ -21,7 +21,7 @@ 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 +CREATE OR REPLACE STREAMLIT core.ui FROM '/streamlit/' MAIN_FILE = 'ui.py'; diff --git a/app_streamlit_java/template.yml b/app_streamlit_java/template.yml index 5abcf06..9f3e7c0 100644 --- a/app_streamlit_java/template.yml +++ b/app_streamlit_java/template.yml @@ -5,5 +5,5 @@ files_to_render: variables: - name: project_name prompt: "Project identifier" - default: my_native_app_project + default: my_app_streamlit_java type: string diff --git a/app_streamlit_js/app/setup_script.sql b/app_streamlit_js/app/setup_script.sql index 770f587..63d94ed 100644 --- a/app_streamlit_js/app/setup_script.sql +++ b/app_streamlit_js/app/setup_script.sql @@ -20,7 +20,7 @@ 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 +CREATE OR REPLACE STREAMLIT core.ui FROM '/streamlit/' MAIN_FILE = 'ui.py'; diff --git a/app_streamlit_js/template.yml b/app_streamlit_js/template.yml index 5abcf06..ff9bba2 100644 --- a/app_streamlit_js/template.yml +++ b/app_streamlit_js/template.yml @@ -5,5 +5,5 @@ files_to_render: variables: - name: project_name prompt: "Project identifier" - default: my_native_app_project + default: my_app_streamlit_js type: string diff --git a/app_streamlit_python/app/setup_script.sql b/app_streamlit_python/app/setup_script.sql index 93ad078..bbb2ad1 100644 --- a/app_streamlit_python/app/setup_script.sql +++ b/app_streamlit_python/app/setup_script.sql @@ -32,7 +32,7 @@ GRANT USAGE ON PROCEDURE core.increment_by_one(NUMBER) TO APPLICATION ROLE app_p -- 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 +CREATE OR REPLACE STREAMLIT core.ui FROM '/streamlit/' MAIN_FILE = 'ui.py'; diff --git a/app_streamlit_python/template.yml b/app_streamlit_python/template.yml index be4f904..691456f 100644 --- a/app_streamlit_python/template.yml +++ b/app_streamlit_python/template.yml @@ -6,5 +6,5 @@ files_to_render: variables: - name: project_name prompt: "Project identifier" - default: my_native_app_project + default: my_app_streamlit_python type: string