Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add app templates #10

Merged
merged 9 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
* @snowflakedb/snowcli

app_* @snowflakedb/nade
3 changes: 2 additions & 1 deletion .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ jobs:
strategy:
matrix:
os: [ ubuntu-latest, windows-latest ]
cli_version: ['==2.7.0', '==2.8.0', ''] # check 2.X and latest release
steps:
- uses: actions/checkout@v4
with:
Expand All @@ -25,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
run: python -m pip install pytest snowflake-snowpark-python snowflake-cli-labs${{ matrix.cli_version }}
- name: Run tests
run: python -m pytest .tests/ -vv
2 changes: 0 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
72 changes: 72 additions & 0 deletions .tests/test_examples.py
Original file line number Diff line number Diff line change
@@ -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
48 changes: 41 additions & 7 deletions .tests/test_render_templates.py
Original file line number Diff line number Diff line change
@@ -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():
Expand All @@ -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 = {
Expand All @@ -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(
Expand All @@ -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
)
32 changes: 0 additions & 32 deletions .tests/test_snowpark_examples.py

This file was deleted.

2 changes: 2 additions & 0 deletions app_basic/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
snowflake.local.yml
output/**
18 changes: 18 additions & 0 deletions app_basic/README.md
Original file line number Diff line number Diff line change
@@ -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. |
<!! if snowflake_cli_version < "3.0.0" !!>
### 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.
<!! endif !!>
20 changes: 20 additions & 0 deletions app_basic/app/README.md
Original file line number Diff line number Diff line change
@@ -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 <your_application_name>.<schema_name>.<stored_procedure_name_with_args>;
```

### Calling a function

```
SELECT <your_application_name>.<schema_name>.<udf_with_args>;
```
9 changes: 9 additions & 0 deletions app_basic/app/manifest.yml
Original file line number Diff line number Diff line change
@@ -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
11 changes: 11 additions & 0 deletions app_basic/app/setup_script.sql
Original file line number Diff line number Diff line change
@@ -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.
29 changes: 29 additions & 0 deletions app_basic/snowflake.yml
Original file line number Diff line number Diff line change
@@ -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.
<!! if snowflake_cli_version < "3.0.0" !!>
definition_version: 1
native_app:
name: <! project_name | to_snowflake_identifier !>
source_stage: app_src.stage
artifacts:
- src: app/*
dest: ./
<!! else !!>
definition_version: 2
entities:
pkg:
type: application package
identifier: <% fn.concat_ids('<! project_name | to_snowflake_identifier !>_pkg', ctx.env.suffix) %>
manifest: app/manifest.yml
artifacts:
- src: app/*
dest: ./

app:
type: application
from:
target: pkg
identifier: <% fn.concat_ids('<! project_name | to_snowflake_identifier !>', ctx.env.suffix) %>

env:
suffix: <% fn.concat_ids('_', fn.sanitize_id(fn.get_username('unknown_user')) | lower) %>
<!! endif !!>
9 changes: 9 additions & 0 deletions app_basic/template.yml
Original file line number Diff line number Diff line change
@@ -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
Comment on lines +6 to +8
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any strong opinion on using "project" for this variable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

snow init still says it "creates a project directory", so I figured that was the right term.

type: string
2 changes: 2 additions & 0 deletions app_spcs_basic/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
snowflake.local.yml
output/**
Loading