diff --git a/src/snowflake/cli/api/commands/decorators.py b/src/snowflake/cli/api/commands/decorators.py index d2ed2015aa..959acc35ca 100644 --- a/src/snowflake/cli/api/commands/decorators.py +++ b/src/snowflake/cli/api/commands/decorators.py @@ -74,9 +74,7 @@ def global_options_with_connection(func: Callable): ) -def with_project_definition( - project_name: Optional[str] = None, is_optional: bool = False -): +def with_project_definition(is_optional: bool = False): def _decorator(func: Callable): return _options_decorator_factory( @@ -86,7 +84,7 @@ def _decorator(func: Callable): "project_definition", inspect.Parameter.KEYWORD_ONLY, annotation=Optional[str], - default=project_definition_option(project_name, is_optional), + default=project_definition_option(is_optional), ), inspect.Parameter( "env_overrides", diff --git a/src/snowflake/cli/api/commands/flags.py b/src/snowflake/cli/api/commands/flags.py index 5a6684f9b2..f7c8eb34c8 100644 --- a/src/snowflake/cli/api/commands/flags.py +++ b/src/snowflake/cli/api/commands/flags.py @@ -27,7 +27,7 @@ from snowflake.cli.api.cli_global_context import cli_context_manager from snowflake.cli.api.commands.typer_pre_execute import register_pre_execute_command from snowflake.cli.api.console import cli_console -from snowflake.cli.api.exceptions import MissingConfiguration, NoProjectDefinitionError +from snowflake.cli.api.exceptions import MissingConfiguration from snowflake.cli.api.output.formats import OutputFormat from snowflake.cli.api.project.definition_manager import DefinitionManager from snowflake.cli.api.rendering.jinja import CONTEXT_KEY @@ -516,7 +516,7 @@ def execution_identifier_argument(sf_object: str, example: str) -> typer.Argumen ) -def register_project_definition(project_name: Optional[str], is_optional: bool) -> None: +def register_project_definition(is_optional: bool) -> None: project_path = cli_context_manager.project_path_arg env_overrides_args = cli_context_manager.project_env_overrides_args @@ -530,42 +530,21 @@ def register_project_definition(project_name: Optional[str], is_optional: bool) "Cannot find project definition (snowflake.yml). Please provide a path to the project or run this command in a valid project directory." ) - if project_name is not None and not getattr(project_definition, project_name, None): - raise NoProjectDefinitionError( - project_type=project_name, project_file=project_path - ) - cli_context_manager.set_project_definition(project_definition) cli_context_manager.set_project_root(project_root) cli_context_manager.set_template_context(template_context) -def _get_project_long_name(project_short_name: Optional[str]) -> str: - if project_short_name is None: - return "Snowflake" - - if project_short_name == "native_app": - project_long_name = "Snowflake Native App" - elif project_short_name == "streamlit": - project_long_name = "Streamlit app" - else: - project_long_name = project_short_name.replace("_", " ").capitalize() - - return f"the {project_long_name}" - - -def project_definition_option(project_name: Optional[str], is_optional: bool): +def project_definition_option(is_optional: bool): def project_definition_callback(project_path: str) -> None: cli_context_manager.set_project_path_arg(project_path) - register_pre_execute_command( - lambda: register_project_definition(project_name, is_optional) - ) + register_pre_execute_command(lambda: register_project_definition(is_optional)) return typer.Option( None, "-p", "--project", - help=f"Path where {_get_project_long_name(project_name)} project resides. " + help=f"Path where Snowflake project resides. " f"Defaults to current working directory.", callback=_callback(lambda: project_definition_callback), show_default=False, diff --git a/src/snowflake/cli/api/project/project_verification.py b/src/snowflake/cli/api/project/project_verification.py new file mode 100644 index 0000000000..6aa4968204 --- /dev/null +++ b/src/snowflake/cli/api/project/project_verification.py @@ -0,0 +1,23 @@ +# 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 snowflake.cli.api.cli_global_context import cli_context +from snowflake.cli.api.exceptions import NoProjectDefinitionError + + +def assert_project_type(project_type: str): + if not getattr(cli_context.project_definition, project_type, None): + raise NoProjectDefinitionError( + project_type=project_type, project_file=cli_context.project_root + ) diff --git a/src/snowflake/cli/plugins/nativeapp/commands.py b/src/snowflake/cli/plugins/nativeapp/commands.py index a5894dae15..f0822f8d98 100644 --- a/src/snowflake/cli/plugins/nativeapp/commands.py +++ b/src/snowflake/cli/plugins/nativeapp/commands.py @@ -33,6 +33,7 @@ MessageResult, ObjectResult, ) +from snowflake.cli.api.project.project_verification import assert_project_type from snowflake.cli.api.secure_path import SecurePath from snowflake.cli.plugins.nativeapp.common_flags import ( ForceOption, @@ -145,13 +146,16 @@ def app_list_templates(**options) -> CommandResult: @app.command("bundle") -@with_project_definition("native_app") +@with_project_definition() def app_bundle( **options, ) -> CommandResult: """ Prepares a local folder with configured app artifacts. """ + + assert_project_type("native_app") + manager = NativeAppManager( project_definition=cli_context.project_definition.native_app, project_root=cli_context.project_root, @@ -161,7 +165,7 @@ def app_bundle( @app.command("run", requires_connection=True) -@with_project_definition("native_app") +@with_project_definition() def app_run( version: Optional[str] = typer.Option( None, @@ -191,6 +195,8 @@ def app_run( then creates or upgrades an application object from the application package. """ + assert_project_type("native_app") + is_interactive = False if force: policy = AllowAlwaysPolicy() @@ -221,7 +227,7 @@ def app_run( @app.command("open", requires_connection=True) -@with_project_definition("native_app") +@with_project_definition() def app_open( **options, ) -> CommandResult: @@ -229,6 +235,9 @@ def app_open( Opens the Snowflake Native App inside of your browser, once it has been installed in your account. """ + + assert_project_type("native_app") + manager = NativeAppManager( project_definition=cli_context.project_definition.native_app, project_root=cli_context.project_root, @@ -243,7 +252,7 @@ def app_open( @app.command("teardown", requires_connection=True) -@with_project_definition("native_app") +@with_project_definition() def app_teardown( force: Optional[bool] = ForceOption, cascade: Optional[bool] = typer.Option( @@ -257,6 +266,9 @@ def app_teardown( """ Attempts to drop both the application object and application package as defined in the project definition file. """ + + assert_project_type("native_app") + processor = NativeAppTeardownProcessor( project_definition=cli_context.project_definition.native_app, project_root=cli_context.project_root, @@ -266,7 +278,7 @@ def app_teardown( @app.command("deploy", requires_connection=True) -@with_project_definition("native_app") +@with_project_definition() def app_deploy( prune: Optional[bool] = typer.Option( default=None, @@ -296,6 +308,9 @@ def app_deploy( Creates an application package in your Snowflake account and syncs the local changes to the stage without creating or updating the application. Running this command with no arguments at all, as in `snow app deploy`, is a shorthand for `snow app deploy --prune --recursive`. """ + + assert_project_type("native_app") + has_paths = paths is not None and len(paths) > 0 if prune is None and recursive is None and not has_paths: prune = True @@ -329,11 +344,14 @@ def app_deploy( @app.command("validate", requires_connection=True) -@with_project_definition("native_app") +@with_project_definition() def app_validate(**options): """ Validates a deployed Snowflake Native App's setup script. """ + + assert_project_type("native_app") + manager = NativeAppManager( project_definition=cli_context.project_definition.native_app, project_root=cli_context.project_root, diff --git a/src/snowflake/cli/plugins/nativeapp/version/commands.py b/src/snowflake/cli/plugins/nativeapp/version/commands.py index ec7ecba3ad..57fee71022 100644 --- a/src/snowflake/cli/plugins/nativeapp/version/commands.py +++ b/src/snowflake/cli/plugins/nativeapp/version/commands.py @@ -25,6 +25,7 @@ ) from snowflake.cli.api.commands.snow_typer import SnowTyperFactory from snowflake.cli.api.output.types import CommandResult, MessageResult, QueryResult +from snowflake.cli.api.project.project_verification import assert_project_type from snowflake.cli.plugins.nativeapp.common_flags import ForceOption, InteractiveOption from snowflake.cli.plugins.nativeapp.policy import ( AllowAlwaysPolicy, @@ -46,7 +47,7 @@ @app.command(requires_connection=True) -@with_project_definition("native_app") +@with_project_definition() def create( version: Optional[str] = typer.Argument( None, @@ -71,6 +72,9 @@ def create( """ Adds a new patch to the provided version defined in your application package. If the version does not exist, creates a version with patch 0. """ + + assert_project_type("native_app") + if version is None and patch is not None: raise MissingParameter("Cannot provide a patch without version!") @@ -107,13 +111,16 @@ def create( @app.command("list", requires_connection=True) -@with_project_definition("native_app") +@with_project_definition() def version_list( **options, ) -> CommandResult: """ Lists all versions defined in an application package. """ + + assert_project_type("native_app") + processor = NativeAppRunProcessor( project_definition=cli_context.project_definition.native_app, project_root=cli_context.project_root, @@ -123,7 +130,7 @@ def version_list( @app.command(requires_connection=True) -@with_project_definition("native_app") +@with_project_definition() def drop( version: Optional[str] = typer.Argument( None, @@ -137,6 +144,9 @@ def drop( Drops a version defined in your application package. Versions can either be passed in as an argument to the command or read from the `manifest.yml` file. Dropping patches is not allowed. """ + + assert_project_type("native_app") + is_interactive = False if force: policy = AllowAlwaysPolicy() diff --git a/src/snowflake/cli/plugins/snowpark/commands.py b/src/snowflake/cli/plugins/snowpark/commands.py index 32b261fcb2..ee2eef7097 100644 --- a/src/snowflake/cli/plugins/snowpark/commands.py +++ b/src/snowflake/cli/plugins/snowpark/commands.py @@ -38,9 +38,7 @@ DEPLOYMENT_STAGE, ObjectType, ) -from snowflake.cli.api.exceptions import ( - SecretsWithoutExternalAccessIntegrationError, -) +from snowflake.cli.api.exceptions import SecretsWithoutExternalAccessIntegrationError from snowflake.cli.api.identifiers import FQN from snowflake.cli.api.output.types import ( CollectionResult, @@ -48,6 +46,7 @@ MessageResult, SingleQueryResult, ) +from snowflake.cli.api.project.project_verification import assert_project_type from snowflake.cli.api.project.schemas.snowpark.callable import ( FunctionSchema, ProcedureSchema, @@ -116,7 +115,7 @@ @app.command("deploy", requires_connection=True) -@with_project_definition("snowpark") +@with_project_definition() def deploy( replace: bool = ReplaceOption( help="Replaces procedure or function, even if no detected changes to metadata" @@ -128,6 +127,9 @@ def deploy( By default, if any of the objects exist already the commands will fail unless `--replace` flag is provided. All deployed objects use the same artifact which is deployed only once. """ + + assert_project_type("snowpark") + snowpark = cli_context.project_definition.snowpark paths = SnowparkPackagePaths.for_snowpark_project( project_root=SecurePath(cli_context.project_root), @@ -379,7 +381,7 @@ def _read_snowflake_requrements_file(file_path: SecurePath): @app.command("build", requires_connection=True) -@with_project_definition("snowpark") +@with_project_definition() def build( ignore_anaconda: bool = IgnoreAnacondaOption, allow_shared_libraries: bool = AllowSharedLibrariesOption, @@ -396,6 +398,9 @@ def build( Builds the Snowpark project as a `.zip` archive that can be used by `deploy` command. The archive is built using only the `src` directory specified in the project file. """ + + assert_project_type("snowpark") + if not deprecated_check_anaconda_for_pypi_deps: ignore_anaconda = True snowpark_paths = SnowparkPackagePaths.for_snowpark_project( diff --git a/src/snowflake/cli/plugins/streamlit/commands.py b/src/snowflake/cli/plugins/streamlit/commands.py index 7eb6f3d8f5..9d1e1aef02 100644 --- a/src/snowflake/cli/plugins/streamlit/commands.py +++ b/src/snowflake/cli/plugins/streamlit/commands.py @@ -35,6 +35,7 @@ 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.plugins.object.command_aliases import ( add_object_command_aliases, @@ -118,7 +119,7 @@ def _check_file_exists_if_not_default(ctx: click.Context, value): @app.command("deploy", requires_connection=True) -@with_project_definition("streamlit") +@with_project_definition() @with_experimental_behaviour() def streamlit_deploy( replace: bool = ReplaceOption( @@ -132,6 +133,9 @@ def streamlit_deploy( environment.yml and any other pages or folders, if present. If you don’t specify a stage name, the `streamlit` stage is used. If the specified stage does not exist, the command creates it. """ + + assert_project_type("streamlit") + streamlit: Streamlit = cli_context.project_definition.streamlit if not streamlit: return MessageResult("No streamlit were specified in project definition.") diff --git a/tests/__snapshots__/test_help_messages.ambr b/tests/__snapshots__/test_help_messages.ambr index 666bb4bb96..7118d13fc6 100644 --- a/tests/__snapshots__/test_help_messages.ambr +++ b/tests/__snapshots__/test_help_messages.ambr @@ -46,8 +46,8 @@ Prepares a local folder with configured app artifacts. +- Options --------------------------------------------------------------------+ - | --project -p TEXT Path where the Snowflake Native App project | - | resides. Defaults to current working directory. | + | --project -p TEXT Path where Snowflake project resides. Defaults to | + | current working directory. | | --env TEXT String in format of key=value. Overrides variables | | from env section used for templating. | | --help -h Show this message and exit. | @@ -106,9 +106,9 @@ | validation of a deployed Snowflake | | Native App's setup script SQL | | [default: validate] | - | --project -p TEXT Path where the Snowflake Native App | - | project resides. Defaults to | - | current working directory. | + | --project -p TEXT Path where Snowflake project | + | resides. Defaults to current | + | working directory. | | --env TEXT String in format of key=value. | | Overrides variables from env | | section used for templating. | @@ -267,8 +267,8 @@ installed in your account. +- Options --------------------------------------------------------------------+ - | --project -p TEXT Path where the Snowflake Native App project | - | resides. Defaults to current working directory. | + | --project -p TEXT Path where Snowflake project resides. Defaults to | + | current working directory. | | --env TEXT String in format of key=value. Overrides variables | | from env section used for templating. | | --help -h Show this message and exit. | @@ -414,8 +414,7 @@ | Native App's setup | | script SQL | | [default: validate] | - | --project -p TEXT Path where the | - | Snowflake Native App | + | --project -p TEXT Path where Snowflake | | project resides. | | Defaults to current | | working directory. | @@ -514,9 +513,9 @@ | to True in an interactive shell | | environment, and False | | otherwise. | - | --project -p TEXT Path where the Snowflake Native | - | App project resides. Defaults | - | to current working directory. | + | --project -p TEXT Path where Snowflake project | + | resides. Defaults to current | + | working directory. | | --env TEXT String in format of key=value. | | Overrides variables from env | | section used for templating. | @@ -588,8 +587,8 @@ Validates a deployed Snowflake Native App's setup script. +- Options --------------------------------------------------------------------+ - | --project -p TEXT Path where the Snowflake Native App project | - | resides. Defaults to current working directory. | + | --project -p TEXT Path where Snowflake project resides. Defaults to | + | current working directory. | | --env TEXT String in format of key=value. Overrides variables | | from env section used for templating. | | --help -h Show this message and exit. | @@ -704,10 +703,9 @@ | want perform potentially | | destructive actions. | | Defaults to unset. | - | --project -p TEXT Path where the Snowflake | - | Native App project | - | resides. Defaults to | - | current working | + | --project -p TEXT Path where Snowflake | + | project resides. Defaults | + | to current working | | directory. | | --env TEXT String in format of | | key=value. Overrides | @@ -807,9 +805,9 @@ | want perform potentially | | destructive actions. Defaults | | to unset. | - | --project -p TEXT Path where the Snowflake Native | - | App project resides. Defaults | - | to current working directory. | + | --project -p TEXT Path where Snowflake project | + | resides. Defaults to current | + | working directory. | | --env TEXT String in format of key=value. | | Overrides variables from env | | section used for templating. | @@ -881,8 +879,8 @@ Lists all versions defined in an application package. +- Options --------------------------------------------------------------------+ - | --project -p TEXT Path where the Snowflake Native App project | - | resides. Defaults to current working directory. | + | --project -p TEXT Path where Snowflake project resides. Defaults to | + | current working directory. | | --env TEXT String in format of key=value. Overrides variables | | from env section used for templating. | | --help -h Show this message and exit. | @@ -3160,7 +3158,7 @@ | --skip-version-check Skip comparing versions of | | dependencies between requirements | | and Anaconda. | - | --project -p TEXT Path where the Snowpark project | + | --project -p TEXT Path where Snowflake project | | resides. Defaults to current working | | directory. | | --env TEXT String in format of key=value. | @@ -3239,8 +3237,8 @@ +- Options --------------------------------------------------------------------+ | --replace Replaces procedure or function, even if no detected | | changes to metadata | - | --project -p TEXT Path where the Snowpark project resides. Defaults | - | to current working directory. | + | --project -p TEXT Path where Snowflake project resides. Defaults to | + | current working directory. | | --env TEXT String in format of key=value. Overrides variables | | from env section used for templating. | | --help -h Show this message and exit. | @@ -7538,8 +7536,8 @@ +- Options --------------------------------------------------------------------+ | --replace Replace the Streamlit app if it already exists. | | --open Whether to open the Streamlit app in a browser. | - | --project -p TEXT Path where the Streamlit app project resides. | - | Defaults to current working directory. | + | --project -p TEXT Path where Snowflake project resides. Defaults to | + | current working directory. | | --env TEXT String in format of key=value. Overrides variables | | from env section used for templating. | | --help -h Show this message and exit. |