Skip to content

Commit

Permalink
Adjust streamlit to PuPr syntax (#448)
Browse files Browse the repository at this point in the history
Closes:  #442
  • Loading branch information
sfc-gh-turbaszek authored Oct 10, 2023
1 parent fb5442f commit 3b59d1c
Show file tree
Hide file tree
Showing 15 changed files with 341 additions and 363 deletions.
4 changes: 4 additions & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,15 @@
* Update function or procedure will upload function/procedure code to new path on stage. Previous code will remain under old path on stage.
* Snowpark command `compute-pool` and its alias `cp` were replaced by `pool` command.
* `snow snowpark registry` was replaced with `snow registry` command.
* Removed `snow streamlit create` command. Streamlit can be deployd using `snow streamlit deploy`

## New additions
* `--temporary-connection` flag, that allows you to connect, without anything declared in config file
* `snow streamlit init` command that creates a new streamlit project.
* `snow streamlit deploy` support pages and environment.yml files.

## Fixes and improvements
* Adjust streamlit commands to PuPr syntax
* Too long texts in table cells are now wrapped instead of cropped
* Split global options into separate section in `help`
* Avoiding unnecessary replace in function/procedure update
Expand Down
19 changes: 19 additions & 0 deletions src/snowcli/cli/common/sql_execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from textwrap import dedent

from click import ClickException
from snowflake.connector.errors import ProgrammingError
from snowcli.cli.common.snow_cli_global_context import snow_cli_global_context_manager
from snowflake.connector.cursor import DictCursor
Expand Down Expand Up @@ -89,3 +90,21 @@ def check_schema_exists(self, database: str, schema: str) -> None:
Use `snow connection list` to list existing connections
"""
) from e

def to_fully_qualified_name(self, name: str):
current_parts = name.split(".")
if len(current_parts) == 3:
# already fully qualified name
return name.upper()

if not self._conn.database:
raise ClickException(
"Default database not specified in connection details."
)

if len(current_parts) == 2:
# we assume this is schema.object
return f"{self._conn.database}.{name}".upper()

schema = self._conn.schema or "public"
return f"{self._conn.database}.{schema}.{name}".upper()
2 changes: 1 addition & 1 deletion src/snowcli/cli/stage/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class StageManager(SqlExecutionMixin):
@staticmethod
def get_standard_stage_name(name: str) -> str:
# Handle embedded stages
if name.startswith("snow://"):
if name.startswith("snow://") or name.startswith("@"):
return name

return f"@{name}"
Expand Down
152 changes: 80 additions & 72 deletions src/snowcli/cli/streamlit/commands.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
import logging

import typer
from pathlib import Path
from typing import Optional

from snowcli.cli.common.decorators import global_options_with_connection
from click import ClickException

from snowcli.cli.common.decorators import global_options_with_connection, global_options
from snowcli.cli.common.flags import DEFAULT_CONTEXT_SETTINGS
from snowcli.cli.streamlit.manager import StreamlitManager
from snowcli.output.decorators import with_output
from snowcli.cli.snowpark_shared import (
CheckAnacondaForPyPiDependencies,
PackageNativeLibrariesOption,
PyPiDownloadOption,
)

from snowcli.output.types import (
CommandResult,
QueryResult,
CollectionResult,
SingleQueryResult,
MessageResult,
MultipleResults,
)
from snowcli.utils import create_project_template

app = typer.Typer(
context_settings=DEFAULT_CONTEXT_SETTINGS,
Expand All @@ -29,6 +28,29 @@
log = logging.getLogger(__name__)


StageNameOption: str = typer.Option(
"streamlit",
"--stage",
help="Stage name where Streamlit files will be uploaded.",
)


@app.command("init")
@with_output
@global_options
def streamlit_init(
project_name: str = typer.Argument(
"example_streamlit", help="Name of the Streamlit project you want to create."
),
**options,
) -> CommandResult:
"""
Initializes this directory with a sample set of files for creating a Streamlit dashboard.
"""
create_project_template("default_streamlit", project_directory=project_name)
return MessageResult(f"Initialized the new project in {project_name}/")


@app.command("list")
@with_output
@global_options_with_connection
Expand Down Expand Up @@ -59,40 +81,6 @@ def streamlit_describe(
return result


@app.command("create")
@with_output
@global_options_with_connection
def streamlit_create(
name: str = typer.Argument(..., help="Name of streamlit to create."),
file: Path = typer.Option(
"streamlit_app.py",
exists=True,
readable=True,
file_okay=True,
help="Path to the Streamlit Python application (`streamlit_app.py`) file.",
),
from_stage: Optional[str] = typer.Option(
None,
help="Stage name from which to copy a Streamlit file.",
),
use_packaging_workaround: bool = typer.Option(
False,
help="Whether to package all code and dependencies into a zip file. Valid values: `true`, `false` (default). You should use this only for a temporary workaround until native support is available.",
),
**options,
) -> CommandResult:
"""
Creates a new Streamlit application object in Snowflake. The streamlit is created in database and schema configured in the connection.
"""
cursor = StreamlitManager().create(
streamlit_name=name,
file=file,
from_stage=from_stage,
use_packaging_workaround=use_packaging_workaround,
)
return SingleQueryResult(cursor)


@app.command("share")
@with_output
@global_options_with_connection
Expand Down Expand Up @@ -128,51 +116,71 @@ def streamlit_drop(
@with_output
@global_options_with_connection
def streamlit_deploy(
name: str = typer.Argument(..., help="Name of streamlit to deploy."),
streamlit_name: str = typer.Argument(..., help="Name of Streamlit to deploy."),
file: Path = typer.Option(
"streamlit_app.py",
exists=True,
readable=True,
file_okay=True,
help="Path of the Streamlit app file.",
),
open_: bool = typer.Option(
False,
"--open",
"-o",
help="Whether to open Streamlit in a browser. Valid values: `true`, `false`. Default: `false`.",
stage: Optional[str] = StageNameOption,
environment_file: Path = typer.Option(
None,
"--env-file",
help="Environment file to use.",
file_okay=True,
dir_okay=False,
),
use_packaging_workaround: bool = typer.Option(
False,
help="Whether to package all code and dependencies into a zip file. Valid values: `true`, `false` (default). You should use this only for a temporary workaround until native support is available.",
pages_dir: Path = typer.Option(
None,
"--pages-dir",
help="Directory with Streamlit pages",
file_okay=False,
dir_okay=True,
),
query_warehouse: Optional[str] = typer.Option(
None, "--query-warehouse", help="Query warehouse for this Streamlit."
),
packaging_workaround_includes_content: bool = typer.Option(
replace: Optional[bool] = typer.Option(
False,
help="Whether to package all code and dependencies into a zip file. Valid values: `true`, `false`. Default: `false`.",
"--replace",
help="Replace the Streamlit if it already exists.",
is_flag=True,
),
pypi_download: str = PyPiDownloadOption,
check_anaconda_for_pypi_deps: bool = CheckAnacondaForPyPiDependencies,
package_native_libraries: str = PackageNativeLibrariesOption,
excluded_anaconda_deps: str = typer.Option(
None,
help="List of comma-separated package names from `environment.yml` to exclude in the deployed app, particularly when Streamlit fails to import an Anaconda package at runtime. Be aware that excluding files might the risk of runtime errors).",
open_: bool = typer.Option(
False, "--open", help="Whether to open Streamlit in a browser.", is_flag=True
),
**options,
) -> CommandResult:
"""
Creates a Streamlit app package for deployment.
Uploads local files to specified stage and creates a Streamlit dashboard using the files. You must specify the
main python file. By default, the command will upload environment.yml and pages/ folder if present. If you
don't provide any stage name then 'streamlit' stage will be used. If provided stage will be created if it does
not exist.
You can modify the behaviour using flags. For details check help information.
"""
result = StreamlitManager().deploy(
streamlit_name=name,
file=file,
open_in_browser=open_,
use_packaging_workaround=use_packaging_workaround,
packaging_workaround_includes_content=packaging_workaround_includes_content,
pypi_download=pypi_download,
check_anaconda_for_pypi_deps=check_anaconda_for_pypi_deps,
package_native_libraries=package_native_libraries,
excluded_anaconda_deps=excluded_anaconda_deps,
if environment_file and not environment_file.exists():
raise ClickException(f"Provided file {environment_file} does not exist")
else:
environment_file = Path("environment.yml")

if pages_dir and not pages_dir.exists():
raise ClickException(f"Provided file {pages_dir} does not exist")
else:
pages_dir = Path("pages")

url = StreamlitManager().deploy(
streamlit_name=streamlit_name,
environment_file=environment_file,
pages_dir=pages_dir,
stage_name=stage,
main_file=file,
replace=replace,
warehouse=query_warehouse,
)
if result is not None:
return MessageResult(result)
return MessageResult("Done")

if open_:
typer.launch(url)

return MessageResult(f"Streamlit successfully deployed and available under {url}")
Loading

0 comments on commit 3b59d1c

Please sign in to comment.