diff --git a/src/snowflake/cli/api/project/schemas/entities/entities.py b/src/snowflake/cli/api/project/schemas/entities/entities.py index d6cfb8505..22b600c56 100644 --- a/src/snowflake/cli/api/project/schemas/entities/entities.py +++ b/src/snowflake/cli/api/project/schemas/entities/entities.py @@ -22,8 +22,9 @@ from snowflake.cli.api.project.schemas.entities.application_package_entity import ( ApplicationPackageEntity, ) +from snowflake.cli.api.project.schemas.entities.streamlit_entity import StreamlitEntity -Entity = Union[ApplicationEntity, ApplicationPackageEntity] +Entity = Union[ApplicationEntity, ApplicationPackageEntity, StreamlitEntity] ALL_ENTITIES = [*get_args(Entity)] diff --git a/src/snowflake/cli/api/project/schemas/entities/streamlit_entity.py b/src/snowflake/cli/api/project/schemas/entities/streamlit_entity.py new file mode 100644 index 000000000..6176b99e3 --- /dev/null +++ b/src/snowflake/cli/api/project/schemas/entities/streamlit_entity.py @@ -0,0 +1,68 @@ +# Copyright (c) 2024 Snowflake Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import annotations + +from pathlib import Path +from typing import List, Literal, Optional + +from pydantic import Field, model_validator +from snowflake.cli.api.project.schemas.entities.common import EntityBase +from snowflake.cli.api.project.schemas.identifier_model import ObjectIdentifierModel + + +class StreamlitEntity(EntityBase, ObjectIdentifierModel(object_name="Streamlit")): # type: ignore + type: Literal["streamlit"] # noqa: A003 + title: Optional[str] = Field( + title="Human-readable title for the Streamlit dashboard", default=None + ) + query_warehouse: str = Field( + title="Snowflake warehouse to host the app", default=None + ) + main_file: Optional[str] = Field( + title="Entrypoint file of the Streamlit app", default="streamlit_app.py" + ) + pages_dir: Optional[str] = Field(title="Streamlit pages", default=None) + stage: Optional[str] = Field( + title="Stage in which the app’s artifacts will be stored", default="streamlit" + ) + # Possibly can be PathMapping + artifacts: Optional[List[Path]] = Field( + title="List of files which should be deployed. Each file needs to exist locally. " + "Main file needs to be included in the artifacts.", + default=None, + ) + + @model_validator(mode="after") + def main_file_must_be_in_artifacts(self): + if not self.artifacts: + return self + + if Path(self.main_file) not in self.artifacts: + raise ValueError( + f"Specified main file {self.main_file} is not included in artifacts." + ) + return self + + @model_validator(mode="after") + def artifacts_must_exists(self): + if not self.artifacts: + return self + + for artefact in self.artifacts: + if not artefact.exists(): + raise ValueError( + f"Specified artefact {artefact} does not exist locally." + ) + + return self diff --git a/src/snowflake/cli/api/project/schemas/project_definition.py b/src/snowflake/cli/api/project/schemas/project_definition.py index 03385b79c..9445734e9 100644 --- a/src/snowflake/cli/api/project/schemas/project_definition.py +++ b/src/snowflake/cli/api/project/schemas/project_definition.py @@ -189,6 +189,9 @@ def _convert_env( return env return ProjectEnvironment(default_env=(env or {}), override_env={}) + def get_entities_by_type(self, entity_type: str): + return {i: e for i, e in self.entities.items() if e.get_type() == entity_type} + def build_project_definition(**data): """ diff --git a/src/snowflake/cli/plugins/streamlit/commands.py b/src/snowflake/cli/plugins/streamlit/commands.py index 9d1e1aef0..26ae65cdf 100644 --- a/src/snowflake/cli/plugins/streamlit/commands.py +++ b/src/snowflake/cli/plugins/streamlit/commands.py @@ -16,6 +16,7 @@ import logging from pathlib import Path +from typing import Dict import click import typer @@ -29,14 +30,18 @@ from snowflake.cli.api.commands.project_initialisation import add_init_command from snowflake.cli.api.commands.snow_typer import SnowTyperFactory from snowflake.cli.api.constants import ObjectType +from snowflake.cli.api.exceptions import NoProjectDefinitionError from snowflake.cli.api.identifiers import FQN from snowflake.cli.api.output.types import ( CommandResult, MessageResult, SingleQueryResult, ) -from snowflake.cli.api.project.project_verification import assert_project_type -from snowflake.cli.api.project.schemas.streamlit.streamlit import Streamlit +from snowflake.cli.api.project.schemas.entities.streamlit_entity import StreamlitEntity +from snowflake.cli.api.project.schemas.project_definition import ( + ProjectDefinition, + ProjectDefinitionV2, +) from snowflake.cli.plugins.object.command_aliases import ( add_object_command_aliases, scope_option, @@ -134,37 +139,37 @@ def streamlit_deploy( stage is used. If the specified stage does not exist, the command creates it. """ - assert_project_type("streamlit") + pd = cli_context.project_definition + if not pd.meets_version_requirement("2"): + pd = _migrate_v1_streamlit_to_v2(pd) - streamlit: Streamlit = cli_context.project_definition.streamlit - if not streamlit: - return MessageResult("No streamlit were specified in project definition.") + streamlits: Dict[str, StreamlitEntity] = pd.get_entities_by_type( + entity_type="streamlit" + ) - environment_file = streamlit.env_file - if environment_file and not Path(environment_file).exists(): - raise ClickException(f"Provided file {environment_file} does not exist") - elif environment_file is None: - environment_file = "environment.yml" + if not streamlits: + raise NoProjectDefinitionError( + project_type="streamlit", project_file=cli_context.project_root + ) - pages_dir = streamlit.pages_dir - if pages_dir and not Path(pages_dir).exists(): - raise ClickException(f"Provided file {pages_dir} does not exist") - elif pages_dir is None: - pages_dir = "pages" + # TODO: fix in follow-up + if len(list(streamlits)) > 1: + raise ClickException( + "Currently only single streamlit entity per project is supported." + ) + # Get first streamlit + streamlit: StreamlitEntity = streamlits[list(streamlits)[0]] streamlit_id = FQN.from_identifier_model(streamlit).using_context() url = StreamlitManager().deploy( streamlit_id=streamlit_id, - environment_file=Path(environment_file), - pages_dir=Path(pages_dir), + artifacts=streamlit.artifacts, stage_name=streamlit.stage, - main_file=Path(streamlit.main_file), + main_file=streamlit.main_file, replace=replace, query_warehouse=streamlit.query_warehouse, - additional_source_files=streamlit.additional_source_files, title=streamlit.title, - **options, ) if open_: @@ -173,6 +178,58 @@ def streamlit_deploy( return MessageResult(f"Streamlit successfully deployed and available under {url}") +def _migrate_v1_streamlit_to_v2(pd: ProjectDefinition): + if not pd.streamlit: + raise NoProjectDefinitionError( + project_type="streamlit", project_file=cli_context.project_root + ) + + default_env_file = "environment.yml" + default_pages_dir = "pages" + + # Process env file + environment_file = pd.streamlit.env_file + if environment_file and not Path(environment_file).exists(): + raise ClickException(f"Provided file {environment_file} does not exist") + elif environment_file is None and Path(default_env_file).exists(): + environment_file = default_env_file + # Process pages dir + pages_dir = pd.streamlit.pages_dir + if pages_dir and not Path(pages_dir).exists(): + raise ClickException(f"Provided file {pages_dir} does not exist") + elif pages_dir is None and Path(default_pages_dir).exists(): + pages_dir = default_pages_dir + + # Build V2 definition + artefacts = [ + pd.streamlit.main_file, + environment_file, + pages_dir, + ] + artefacts = [a for a in artefacts if a is not None] + if pd.streamlit.additional_source_files: + artefacts.extend(pd.streamlit.additional_source_files) + + data = { + "definition_version": "2", + "entities": { + "streamlit_app": { + "type": "streamlit", + "name": pd.streamlit.name, + "schema": pd.streamlit.schema_name, + "database": pd.streamlit.database, + "title": pd.streamlit.title, + "query_warehouse": pd.streamlit.query_warehouse, + "main_file": str(pd.streamlit.main_file), + "pages_dir": str(pd.streamlit.pages_dir), + "stage": pd.streamlit.stage, + "artifacts": artefacts, + } + }, + } + return ProjectDefinitionV2(**data) + + @app.command("get-url", requires_connection=True) def get_url( name: FQN = StreamlitNameArgument, diff --git a/src/snowflake/cli/plugins/streamlit/manager.py b/src/snowflake/cli/plugins/streamlit/manager.py index 100ab7c96..ff2482779 100644 --- a/src/snowflake/cli/plugins/streamlit/manager.py +++ b/src/snowflake/cli/plugins/streamlit/manager.py @@ -15,7 +15,6 @@ from __future__ import annotations import logging -import os from pathlib import Path from typing import List, Optional @@ -45,33 +44,25 @@ def share(self, streamlit_name: FQN, to_role: str) -> SnowflakeCursor: def _put_streamlit_files( self, root_location: str, - main_file: Path, - environment_file: Optional[Path], - pages_dir: Optional[Path], - additional_source_files: Optional[List[Path]], + artifacts: Optional[List[Path]] = None, ): + if not artifacts: + return stage_manager = StageManager() - - stage_manager.put(main_file, root_location, 4, True) - - if environment_file and environment_file.exists(): - stage_manager.put(environment_file, root_location, 4, True) - - if pages_dir and pages_dir.exists(): - stage_manager.put(pages_dir / "*.py", f"{root_location}/pages", 4, True) - - if additional_source_files: - for file in additional_source_files: - if os.sep in str(file): - destination = f"{root_location}/{str(file.parent)}" - else: - destination = root_location - stage_manager.put(file, destination, 4, True) + for file in artifacts: + if file.is_dir(): + stage_manager.put( + f"{file.joinpath('*')}", f"{root_location}/{file}", 4, True + ) + elif len(file.parts) > 1: + stage_manager.put(file, f"{root_location}/{file.parent}", 4, True) + else: + stage_manager.put(file, root_location, 4, True) def _create_streamlit( self, streamlit_id: FQN, - main_file: Path, + main_file: str, replace: Optional[bool] = None, experimental: Optional[bool] = None, query_warehouse: Optional[str] = None, @@ -95,7 +86,7 @@ def _create_streamlit( if from_stage_name: query.append(f"ROOT_LOCATION = '{from_stage_name}'") - query.append(f"MAIN_FILE = '{main_file.name}'") + query.append(f"MAIN_FILE = '{main_file}'") if query_warehouse: query.append(f"QUERY_WAREHOUSE = {query_warehouse}") @@ -107,15 +98,12 @@ def _create_streamlit( def deploy( self, streamlit_id: FQN, - main_file: Path, - environment_file: Optional[Path] = None, - pages_dir: Optional[Path] = None, + main_file: str, + artifacts: Optional[List[Path]] = None, stage_name: Optional[str] = None, query_warehouse: Optional[str] = None, replace: Optional[bool] = False, - additional_source_files: Optional[List[Path]] = None, title: Optional[str] = None, - **options, ): # for backwards compatibility - quoted stage path might be case-sensitive # https://docs.snowflake.com/en/sql-reference/identifiers-syntax#double-quoted-identifiers @@ -156,10 +144,7 @@ def deploy( self._put_streamlit_files( root_location, - main_file, - environment_file, - pages_dir, - additional_source_files, + artifacts, ) else: """ @@ -178,13 +163,7 @@ def deploy( f"{stage_name}/{streamlit_name_for_root_location}" ) - self._put_streamlit_files( - root_location, - main_file, - environment_file, - pages_dir, - additional_source_files, - ) + self._put_streamlit_files(root_location, artifacts) self._create_streamlit( streamlit_id, diff --git a/src/snowflake/cli/templates/default_streamlit/snowflake.yml b/src/snowflake/cli/templates/default_streamlit/snowflake.yml index 8292a2ded..51f35ab59 100644 --- a/src/snowflake/cli/templates/default_streamlit/snowflake.yml +++ b/src/snowflake/cli/templates/default_streamlit/snowflake.yml @@ -1,10 +1,15 @@ -definition_version: "1.1" -streamlit: - name: streamlit_app - stage: my_streamlit_stage - query_warehouse: my_streamlit_warehouse - main_file: streamlit_app.py - env_file: environment.yml - pages_dir: pages/ - additional_source_files: - - common/hello.py +definition_version: "2" +entities: + my_streamlit: + type: "streamlit" + name: "my_dashboard" + title: "My Streamlit Dashboard" + stage: "my_streamlit_stage" + query_warehouse: 'my_streamlit_warehouse' + main_file: streamlit_app.py + pages_dir: pages/ + artifacts: + - streamlit_app.py + - environment.yml + - common/hello.py + - pages/ diff --git a/tests/app/test_telemetry.py b/tests/app/test_telemetry.py index 26965a232..034b34095 100644 --- a/tests/app/test_telemetry.py +++ b/tests/app/test_telemetry.py @@ -107,6 +107,9 @@ def test_executing_command_sends_telemetry_result_data( @mock.patch("snowflake.connector.connect") @mock.patch("snowflake.cli.plugins.streamlit.commands.StreamlitManager") +@mock.patch.dict( + os.environ, {"SNOWFLAKE_CLI_FEATURES_ENABLE_PROJECT_DEFINITION_V2": "true"} +) def test_executing_command_sends_project_definition_in_telemetry_data( _, mock_conn, project_directory, runner ): diff --git a/tests/streamlit/__snapshots__/test_commands.ambr b/tests/streamlit/__snapshots__/test_commands.ambr new file mode 100644 index 000000000..38fe822b2 --- /dev/null +++ b/tests/streamlit/__snapshots__/test_commands.ambr @@ -0,0 +1,31 @@ +# serializer version: 1 +# name: test_artifacts_must_exists + ''' + +- Error ----------------------------------------------------------------------+ + | During evaluation of DefinitionV20 in project definition following errors | + | were encountered: | + | For field entities.my_streamlit.streamlit you provided '{'artifacts': | + | ['streamlit_app.py', 'foo_bar.py', 'pages/', 'environment.yml'], | + | 'main_file': 'streamlit_app.py', 'name': 'test_streamlit_deploy_snowcli', | + | 'query_warehouse': 'xsmall', 'stage': 'streamlit', 'title': 'My Fancy | + | Streamlit', 'type': 'streamlit'}'. This caused: Value error, Specified | + | artefact foo_bar.py does not exist locally. | + +------------------------------------------------------------------------------+ + + ''' +# --- +# name: test_main_file_must_be_in_artifacts + ''' + +- Error ----------------------------------------------------------------------+ + | During evaluation of DefinitionV20 in project definition following errors | + | were encountered: | + | For field entities.my_streamlit.streamlit you provided '{'artifacts': | + | ['streamlit_app.py', 'utils/utils.py', 'pages/', 'environment.yml'], | + | 'main_file': 'foo_bar.py', 'name': 'test_streamlit_deploy_snowcli', | + | 'query_warehouse': 'xsmall', 'stage': 'streamlit', 'title': 'My Fancy | + | Streamlit', 'type': 'streamlit'}'. This caused: Value error, Specified main | + | file foo_bar.py is not included in artifacts. | + +------------------------------------------------------------------------------+ + + ''' +# --- diff --git a/tests/streamlit/conftest.py b/tests/streamlit/conftest.py new file mode 100644 index 000000000..96e16f923 --- /dev/null +++ b/tests/streamlit/conftest.py @@ -0,0 +1,20 @@ +# Copyright (c) 2024 Snowflake Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + + +@pytest.fixture(autouse=True) +def global_setup(monkeypatch): + monkeypatch.setenv("SNOWFLAKE_CLI_FEATURES_ENABLE_PROJECT_DEFINITION_V2", "true") diff --git a/tests/streamlit/test_commands.py b/tests/streamlit/test_commands.py index f81c05024..cc59c9f14 100644 --- a/tests/streamlit/test_commands.py +++ b/tests/streamlit/test_commands.py @@ -11,7 +11,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - import shutil from pathlib import Path from textwrap import dedent @@ -206,6 +205,40 @@ def test_deploy_only_streamlit_file_replace( mock_typer.launch.assert_not_called() +def test_main_file_must_be_in_artifacts( + runner, mock_ctx, project_directory, alter_snowflake_yml, snapshot +): + with project_directory("example_streamlit_v2") as pdir: + alter_snowflake_yml( + pdir / "snowflake.yml", + parameter_path="entities.my_streamlit.main_file", + value="foo_bar.py", + ) + + result = runner.invoke( + ["streamlit", "deploy"], + ) + assert result.exit_code == 1 + assert result.output == snapshot + + +def test_artifacts_must_exists( + runner, mock_ctx, project_directory, alter_snowflake_yml, snapshot +): + with project_directory("example_streamlit_v2") as pdir: + alter_snowflake_yml( + pdir / "snowflake.yml", + parameter_path="entities.my_streamlit.artifacts.1", + value="foo_bar.py", + ) + + result = runner.invoke( + ["streamlit", "deploy"], + ) + assert result.exit_code == 1 + assert result.output == snapshot + + @mock.patch("snowflake.cli.plugins.streamlit.commands.typer") @mock.patch("snowflake.connector.connect") def test_deploy_launch_browser( @@ -299,7 +332,7 @@ def test_deploy_streamlit_and_pages_files( assert ctx.get_queries() == [ "create stage if not exists MockDatabase.MockSchema.streamlit", _put_query("streamlit_app.py", root_path), - _put_query("pages/*.py", f"{root_path}/pages"), + _put_query("pages/*", f"{root_path}/pages"), dedent( f""" CREATE STREAMLIT IDENTIFIER('MockDatabase.MockSchema.{STREAMLIT_NAME}') @@ -340,7 +373,7 @@ def test_deploy_all_streamlit_files( "create stage if not exists MockDatabase.MockSchema.streamlit", _put_query("streamlit_app.py", root_path), _put_query("environment.yml", root_path), - _put_query("pages/*.py", f"{root_path}/pages"), + _put_query("pages/*", f"{root_path}/pages"), _put_query("utils/utils.py", f"{root_path}/utils"), _put_query("extra_file.py", root_path), dedent( @@ -385,7 +418,7 @@ def test_deploy_put_files_on_stage( "create stage if not exists MockDatabase.MockSchema.streamlit_stage", _put_query("streamlit_app.py", root_path), _put_query("environment.yml", root_path), - _put_query("pages/*.py", f"{root_path}/pages"), + _put_query("pages/*", f"{root_path}/pages"), dedent( f""" CREATE STREAMLIT IDENTIFIER('MockDatabase.MockSchema.{STREAMLIT_NAME}') @@ -426,7 +459,7 @@ def test_deploy_all_streamlit_files_not_defaults( "create stage if not exists MockDatabase.MockSchema.streamlit_stage", _put_query("main.py", root_path), _put_query("streamlit_environment.yml", root_path), - _put_query("streamlit_pages/*.py", f"{root_path}/pages"), + _put_query("streamlit_pages/*", f"{root_path}/streamlit_pages"), dedent( f""" CREATE STREAMLIT IDENTIFIER('MockDatabase.MockSchema.{STREAMLIT_NAME}') @@ -476,7 +509,7 @@ def test_deploy_streamlit_main_and_pages_files_experimental( f"ALTER streamlit MockDatabase.MockSchema.{STREAMLIT_NAME} CHECKOUT", _put_query("streamlit_app.py", root_path), _put_query("environment.yml", f"{root_path}"), - _put_query("pages/*.py", f"{root_path}/pages"), + _put_query("pages/*", f"{root_path}/pages"), f"select system$get_snowsight_host()", REGIONLESS_QUERY, f"select current_account_name()", @@ -541,7 +574,7 @@ def test_deploy_streamlit_main_and_pages_files_experimental_double_deploy( ).strip(), _put_query("streamlit_app.py", root_path), _put_query("environment.yml", f"{root_path}"), - _put_query("pages/*.py", f"{root_path}/pages"), + _put_query("pages/*", f"{root_path}/pages"), f"select system$get_snowsight_host()", REGIONLESS_QUERY, f"select current_account_name()", @@ -582,7 +615,7 @@ def test_deploy_streamlit_main_and_pages_files_experimental_no_stage( f"ALTER streamlit MockDatabase.MockSchema.{STREAMLIT_NAME} CHECKOUT", _put_query("streamlit_app.py", root_path), _put_query("environment.yml", f"{root_path}"), - _put_query("pages/*.py", f"{root_path}/pages"), + _put_query("pages/*", f"{root_path}/pages"), f"select system$get_snowsight_host()", REGIONLESS_QUERY, f"select current_account_name()", @@ -624,7 +657,7 @@ def test_deploy_streamlit_main_and_pages_files_experimental_replace( f"ALTER streamlit MockDatabase.MockSchema.{STREAMLIT_NAME} CHECKOUT", _put_query("streamlit_app.py", root_path), _put_query("environment.yml", f"{root_path}"), - _put_query("pages/*.py", f"{root_path}/pages"), + _put_query("pages/*", f"{root_path}/pages"), f"select system$get_snowsight_host()", REGIONLESS_QUERY, f"select current_account_name()", diff --git a/tests_integration/test_data/projects/streamlit/environment.yml b/tests/test_data/projects/example_streamlit_v2/environment.yml similarity index 100% rename from tests_integration/test_data/projects/streamlit/environment.yml rename to tests/test_data/projects/example_streamlit_v2/environment.yml diff --git a/tests_integration/test_data/projects/streamlit/pages/my_page.py b/tests/test_data/projects/example_streamlit_v2/pages/my_page.py similarity index 100% rename from tests_integration/test_data/projects/streamlit/pages/my_page.py rename to tests/test_data/projects/example_streamlit_v2/pages/my_page.py diff --git a/tests/test_data/projects/example_streamlit_v2/snowflake.yml b/tests/test_data/projects/example_streamlit_v2/snowflake.yml new file mode 100644 index 000000000..61e71e0b3 --- /dev/null +++ b/tests/test_data/projects/example_streamlit_v2/snowflake.yml @@ -0,0 +1,14 @@ +definition_version: 2 +entities: + my_streamlit: + type: "streamlit" + name: test_streamlit_deploy_snowcli + title: "My Fancy Streamlit" + stage: streamlit + query_warehouse: xsmall + main_file: streamlit_app.py + artifacts: + - streamlit_app.py + - utils/utils.py + - pages/ + - environment.yml diff --git a/tests/test_data/projects/example_streamlit_v2/streamlit_app.py b/tests/test_data/projects/example_streamlit_v2/streamlit_app.py new file mode 100644 index 000000000..b699538d7 --- /dev/null +++ b/tests/test_data/projects/example_streamlit_v2/streamlit_app.py @@ -0,0 +1,3 @@ +import streamlit as st + +st.title("Example streamlit app") diff --git a/tests/test_data/projects/streamlit_full_definition/extra_file.py b/tests/test_data/projects/streamlit_full_definition/extra_file.py new file mode 100644 index 000000000..c84a9b135 --- /dev/null +++ b/tests/test_data/projects/streamlit_full_definition/extra_file.py @@ -0,0 +1 @@ +foo = 42 diff --git a/tests_integration/conftest.py b/tests_integration/conftest.py index 3177e4c0d..86581ff60 100644 --- a/tests_integration/conftest.py +++ b/tests_integration/conftest.py @@ -179,3 +179,8 @@ def reset_global_context_after_each_test(request): @pytest.fixture(autouse=True) def isolate_snowflake_home(snowflake_home): yield snowflake_home + + +@pytest.fixture(autouse=True) +def env_setup(monkeypatch): + monkeypatch.setenv("SNOWFLAKE_CLI_FEATURES_ENABLE_PROJECT_DEFINITION_V2", "true") diff --git a/tests_integration/test_data/projects/streamlit_v1/environment.yml b/tests_integration/test_data/projects/streamlit_v1/environment.yml new file mode 100644 index 000000000..ac8feac3e --- /dev/null +++ b/tests_integration/test_data/projects/streamlit_v1/environment.yml @@ -0,0 +1,5 @@ +name: sf_env +channels: + - snowflake +dependencies: + - pandas diff --git a/tests_integration/test_data/projects/streamlit_v1/pages/my_page.py b/tests_integration/test_data/projects/streamlit_v1/pages/my_page.py new file mode 100644 index 000000000..bc3ecbccb --- /dev/null +++ b/tests_integration/test_data/projects/streamlit_v1/pages/my_page.py @@ -0,0 +1,3 @@ +import streamlit as st + +st.title("Example page") diff --git a/tests_integration/test_data/projects/streamlit/snowflake.yml b/tests_integration/test_data/projects/streamlit_v1/snowflake.yml similarity index 100% rename from tests_integration/test_data/projects/streamlit/snowflake.yml rename to tests_integration/test_data/projects/streamlit_v1/snowflake.yml diff --git a/tests_integration/test_data/projects/streamlit/streamlit_app.py b/tests_integration/test_data/projects/streamlit_v1/streamlit_app.py similarity index 100% rename from tests_integration/test_data/projects/streamlit/streamlit_app.py rename to tests_integration/test_data/projects/streamlit_v1/streamlit_app.py diff --git a/tests_integration/test_data/projects/streamlit/utils/utils.py b/tests_integration/test_data/projects/streamlit_v1/utils/utils.py similarity index 100% rename from tests_integration/test_data/projects/streamlit/utils/utils.py rename to tests_integration/test_data/projects/streamlit_v1/utils/utils.py diff --git a/tests_integration/test_data/projects/streamlit_v2/environment.yml b/tests_integration/test_data/projects/streamlit_v2/environment.yml new file mode 100644 index 000000000..ac8feac3e --- /dev/null +++ b/tests_integration/test_data/projects/streamlit_v2/environment.yml @@ -0,0 +1,5 @@ +name: sf_env +channels: + - snowflake +dependencies: + - pandas diff --git a/tests_integration/test_data/projects/streamlit_v2/pages/my_page.py b/tests_integration/test_data/projects/streamlit_v2/pages/my_page.py new file mode 100644 index 000000000..bc3ecbccb --- /dev/null +++ b/tests_integration/test_data/projects/streamlit_v2/pages/my_page.py @@ -0,0 +1,3 @@ +import streamlit as st + +st.title("Example page") diff --git a/tests_integration/test_data/projects/streamlit_v2/snowflake.yml b/tests_integration/test_data/projects/streamlit_v2/snowflake.yml new file mode 100644 index 000000000..61e71e0b3 --- /dev/null +++ b/tests_integration/test_data/projects/streamlit_v2/snowflake.yml @@ -0,0 +1,14 @@ +definition_version: 2 +entities: + my_streamlit: + type: "streamlit" + name: test_streamlit_deploy_snowcli + title: "My Fancy Streamlit" + stage: streamlit + query_warehouse: xsmall + main_file: streamlit_app.py + artifacts: + - streamlit_app.py + - utils/utils.py + - pages/ + - environment.yml diff --git a/tests_integration/test_data/projects/streamlit_v2/streamlit_app.py b/tests_integration/test_data/projects/streamlit_v2/streamlit_app.py new file mode 100644 index 000000000..be783d837 --- /dev/null +++ b/tests_integration/test_data/projects/streamlit_v2/streamlit_app.py @@ -0,0 +1,4 @@ +import streamlit as st +from utils.utils import hello + +st.title(f"Example streamlit app. {hello()}") diff --git a/tests_integration/test_data/projects/streamlit_v2/utils/utils.py b/tests_integration/test_data/projects/streamlit_v2/utils/utils.py new file mode 100644 index 000000000..c6a2bb574 --- /dev/null +++ b/tests_integration/test_data/projects/streamlit_v2/utils/utils.py @@ -0,0 +1,2 @@ +def hello(): + return "Hello!" diff --git a/tests_integration/test_streamlit.py b/tests_integration/test_streamlit.py index 971871b21..a6b22b6bf 100644 --- a/tests_integration/test_streamlit.py +++ b/tests_integration/test_streamlit.py @@ -26,16 +26,18 @@ @pytest.mark.integration +@pytest.mark.parametrize("pdf_version", ["1", "2"]) def test_streamlit_deploy( runner, snowflake_session, test_database, _new_streamlit_role, project_directory, + pdf_version, ): streamlit_name = "test_streamlit_deploy_snowcli" - with project_directory("streamlit"): + with project_directory(f"streamlit_v{pdf_version}"): result = runner.invoke_with_connection_json(["streamlit", "deploy"]) assert result.exit_code == 0 @@ -93,16 +95,18 @@ def test_streamlit_deploy( @pytest.mark.skip( reason="only works in accounts with experimental checkout behavior enabled" ) +@pytest.mark.parametrize("pdf_version", ["1", "2"]) def test_streamlit_deploy_experimental_twice( runner, snowflake_session, test_database, _new_streamlit_role, project_directory, + pdf_version, ): streamlit_name = "test_streamlit_deploy_snowcli" - with project_directory("streamlit"): + with project_directory(f"streamlit_v{pdf_version}"): result = runner.invoke_with_connection_json( ["streamlit", "deploy", "--experimental"] ) @@ -163,8 +167,16 @@ def test_streamlit_deploy_experimental_twice( @pytest.mark.integration -def test_fully_qualified_name( - alter_snowflake_yml, test_database, project_directory, runner, snapshot +@pytest.mark.parametrize( + "pdf_version, param_path", [("1", "streamlit"), ("2", "entities.my_streamlit")] +) +def test_fully_qualified_name_v1( + alter_snowflake_yml, + test_database, + project_directory, + runner, + pdf_version, + param_path, ): default_schema = "PUBLIC" different_schema = "TOTALLY_DIFFERENT_SCHEMA" @@ -177,14 +189,14 @@ def test_fully_qualified_name( ) # test fully qualified name as name - with project_directory("streamlit") as tmp_dir: + with project_directory(f"streamlit_v{pdf_version}") as tmp_dir: streamlit_name = "streamlit_fqn" snowflake_yml = tmp_dir / "snowflake.yml" # FQN with "default" values alter_snowflake_yml( snowflake_yml, - parameter_path="streamlit.name", + parameter_path=f"{param_path}.name", value=f"{database}.{default_schema}.{streamlit_name}", ) result = runner.invoke_with_connection_json(["streamlit", "deploy"]) @@ -197,7 +209,7 @@ def test_fully_qualified_name( # FQN with different schema - should not conflict alter_snowflake_yml( snowflake_yml, - parameter_path="streamlit.name", + parameter_path=f"{param_path}.name", value=f"{database}.{different_schema}.{streamlit_name}", ) result = runner.invoke_with_connection_json(["streamlit", "deploy"]) @@ -210,7 +222,7 @@ def test_fully_qualified_name( # FQN with just schema provided - should require update alter_snowflake_yml( snowflake_yml, - parameter_path="streamlit.name", + parameter_path=f"{param_path}.name", value=f"{different_schema}.{streamlit_name}", ) result = runner.invoke_with_connection( @@ -220,12 +232,12 @@ def test_fully_qualified_name( # Same if name is not fqn but schema is specified alter_snowflake_yml( snowflake_yml, - parameter_path="streamlit.name", + parameter_path=f"{param_path}.name", value=streamlit_name, ) alter_snowflake_yml( snowflake_yml, - parameter_path="streamlit.schema", + parameter_path=f"{param_path}.schema", value=different_schema, ) result = runner.invoke_with_connection(