Skip to content

Commit

Permalink
[SNOW-1039218] feat: add hidden app list-templates command
Browse files Browse the repository at this point in the history
  • Loading branch information
sfc-gh-mchok committed Mar 8, 2024
1 parent d53aacd commit 73d7c32
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 3 deletions.
60 changes: 57 additions & 3 deletions src/snowflake/cli/plugins/nativeapp/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,17 @@
with_project_definition,
)
from snowflake.cli.api.commands.snow_typer import SnowTyper
from snowflake.cli.api.output.types import CommandResult, MessageResult
from snowflake.cli.api.output.types import (
CollectionResult,
CommandResult,
MessageResult,
)
from snowflake.cli.api.secure_path import SecurePath
from snowflake.cli.plugins.nativeapp.common_flags import ForceOption, InteractiveOption
from snowflake.cli.plugins.nativeapp.init import nativeapp_init
from snowflake.cli.plugins.nativeapp.init import (
OFFICIAL_TEMPLATES_GITHUB_URL,
nativeapp_init,
)
from snowflake.cli.plugins.nativeapp.manager import NativeAppManager
from snowflake.cli.plugins.nativeapp.policy import (
AllowAlwaysPolicy,
Expand All @@ -20,7 +28,10 @@
from snowflake.cli.plugins.nativeapp.teardown_processor import (
NativeAppTeardownProcessor,
)
from snowflake.cli.plugins.nativeapp.utils import is_tty_interactive
from snowflake.cli.plugins.nativeapp.utils import (
get_first_paragraph_from_markdown_file,
is_tty_interactive,
)
from snowflake.cli.plugins.nativeapp.version.commands import app as versions_app

app = SnowTyper(
Expand Down Expand Up @@ -69,6 +80,49 @@ def app_init(
)


@app.command("list-templates", hidden=True)
def app_list_templates(**options) -> CommandResult:
"""
Prints information regarding templates that can be used with snow app init.
"""
with SecurePath.temporary_directory() as temp_path:
from git import Repo

repo = Repo.clone_from(
url=OFFICIAL_TEMPLATES_GITHUB_URL,
to_path=temp_path.path,
filter=["tree:0"],
depth=1,
)

# Close repo to avoid issues with permissions on Windows
repo.close()

# Mark a directory as a template if a project definition jinja template is inside
template_directories = [
entry.name
for entry in repo.head.commit.tree
if (temp_path / entry.name / "snowflake.yml.jinja").exists()
]

# get the template descriptions from the README.md in its directory
template_descriptions = [
get_first_paragraph_from_markdown_file(
(temp_path / directory / "README.md").path
)
for directory in template_directories
]

result = (
{"template": directory, "description": description}
for directory, description in zip(
template_directories, template_descriptions
)
)

return CollectionResult(result)


@app.command("bundle", hidden=True)
@with_project_definition("native_app")
def app_bundle(
Expand Down
14 changes: 14 additions & 0 deletions src/snowflake/cli/plugins/nativeapp/utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from pathlib import Path
from sys import stdin, stdout


Expand All @@ -7,3 +8,16 @@ def needs_confirmation(needs_confirm: bool, auto_yes: bool) -> bool:

def is_tty_interactive():
return stdin.isatty() and stdout.isatty()


def get_first_paragraph_from_markdown_file(file_path: Path) -> str:
with open(file_path, "r") as markdown_file:
paragraph_text = ""

for line in markdown_file:
stripped_line = line.strip()
if not stripped_line.startswith("#") and stripped_line:
paragraph_text = stripped_line
break

return paragraph_text
24 changes: 24 additions & 0 deletions tests/__snapshots__/test_help_messages.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,30 @@
╰──────────────────────────────────────────────────────────────────────────────╯


'''
# ---
# name: test_help_messages[app.list-templates]
'''

Usage: default app list-templates [OPTIONS]

Prints information regarding templates that can be used with snow app init.

╭─ Options ────────────────────────────────────────────────────────────────────╮
│ --help -h Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Global configuration ───────────────────────────────────────────────────────╮
│ --format [TABLE|JSON] Specifies the output format. │
│ [default: TABLE] │
│ --verbose -v Displays log entries for log levels `info` │
│ and higher. │
│ --debug Displays log entries for log levels `debug` │
│ and higher; debug logs contains additional │
│ information. │
│ --silent Turns off intermediate output to console. │
╰──────────────────────────────────────────────────────────────────────────────╯


'''
# ---
# name: test_help_messages[app.open]
Expand Down
25 changes: 25 additions & 0 deletions tests/nativeapp/__snapshots__/test_commands.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -358,3 +358,28 @@

'''
# ---
# name: test_list_templates_no_options_success
'''
+------------------------------------------------------------------------------+
| template | description |
|------------------+-----------------------------------------------------------|
| basic | 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. |
| streamlit-java | 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. |
| streamlit-python | 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. |
+------------------------------------------------------------------------------+

'''
# ---
8 changes: 8 additions & 0 deletions tests/nativeapp/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,11 @@ def test_init_no_template_failure(

assert result.exit_code == 1
assert result.output == snapshot


def test_list_templates_no_options_success(runner, temp_dir, snapshot):
args = ["app", "list-templates"]
result = runner.invoke(args)

assert result.exit_code == 0
assert result.output == snapshot
32 changes: 32 additions & 0 deletions tests/nativeapp/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import pytest
from snowflake.cli.api.secure_path import SecurePath
from snowflake.cli.plugins.nativeapp.utils import get_first_paragraph_from_markdown_file


@pytest.mark.parametrize(
"file_content, expected_paragraph",
[
(
"""
## 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).
""",
"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.",
)
],
)
def test_get_first_paragraph_from_markdown_file(file_content, expected_paragraph):
with SecurePath.temporary_directory() as temp_path:
temp_readme_path = (temp_path / "README.md").path

with open(temp_readme_path, "w+") as temp_readme_file:
temp_readme_file.write(file_content)

actual_paragraph = get_first_paragraph_from_markdown_file(temp_readme_path)

assert actual_paragraph == expected_paragraph

0 comments on commit 73d7c32

Please sign in to comment.