diff --git a/src/snowflake/cli/app/__main__.py b/src/snowflake/cli/app/__main__.py index 68aadbe2f4..c187979075 100644 --- a/src/snowflake/cli/app/__main__.py +++ b/src/snowflake/cli/app/__main__.py @@ -2,10 +2,11 @@ import sys -from snowflake.cli.app.cli_app import app +from snowflake.cli.app.cli_app import app_factory def main(*args): + app = app_factory() app(*args) diff --git a/src/snowflake/cli/app/cli_app.py b/src/snowflake/cli/app/cli_app.py index cca66eb61c..4697ac59f9 100644 --- a/src/snowflake/cli/app/cli_app.py +++ b/src/snowflake/cli/app/cli_app.py @@ -31,7 +31,6 @@ from snowflake.cli.app.printing import print_result from snowflake.connector.config_manager import CONFIG_MANAGER -app: SnowCliMainTyper = SnowCliMainTyper() log = logging.getLogger(__name__) _api = Api(plugin_config_provider=PluginConfigProviderImpl()) @@ -126,93 +125,98 @@ def _info_callback(value: bool): _exit_with_cleanup() -@app.callback() -def default( - version: bool = typer.Option( - None, - "--version", - help="Shows version of the Snowflake CLI", - callback=_version_callback, - is_eager=True, - ), - docs: bool = typer.Option( - None, - "--docs", - hidden=True, - help="Generates Snowflake CLI documentation", - callback=_docs_callback, - is_eager=True, - ), - structure: bool = typer.Option( - None, - "--structure", - hidden=True, - help="Prints Snowflake CLI structure of commands", - callback=_commands_structure_callback, - is_eager=True, - ), - info: bool = typer.Option( - None, - "--info", - help="Shows information about the Snowflake CLI", - callback=_info_callback, - ), - configuration_file: Path = typer.Option( - None, - "--config-file", - help="Specifies Snowflake CLI configuration file that should be used", - exists=True, - dir_okay=False, - is_eager=True, - callback=_config_init_callback, - ), - pycharm_debug_library_path: str = typer.Option( - None, - "--pycharm-debug-library-path", - hidden=True, - ), - pycharm_debug_server_host: str = typer.Option( - "localhost", - "--pycharm-debug-server-host", - hidden=True, - ), - pycharm_debug_server_port: int = typer.Option( - 12345, - "--pycharm-debug-server-port", - hidden=True, - ), - disable_external_command_plugins: bool = typer.Option( - None, - "--disable-external-command-plugins", - help="Disable external command plugins", - callback=_disable_external_command_plugins_callback, - is_eager=True, - hidden=True, - ), - # THIS OPTION SHOULD BE THE LAST OPTION IN THE LIST! - # --- - # This is a hidden artificial option used only to guarantee execution of commands registration - # and make this guaranty not dependent on other callbacks. - # Commands registration is invoked as soon as all callbacks - # decorated with "_commands_registration.before" are executed - # but if there are no such callbacks (at the result of possible future changes) - # then we need to invoke commands registration manually. - # - # This option is also responsible for resetting registration state for test purposes. - commands_registration: bool = typer.Option( - True, - "--commands-registration", - help="Commands registration", - hidden=True, - is_eager=True, - callback=_commands_registration_callback, - ), -) -> None: - """ - Snowflake CLI tool for developers. - """ - setup_pycharm_remote_debugger_if_provided( - pycharm_debug_library_path=pycharm_debug_library_path, - pycharm_debug_server_host=pycharm_debug_server_host, - pycharm_debug_server_port=pycharm_debug_server_port, - ) +def app_factory() -> SnowCliMainTyper: + app = SnowCliMainTyper() + + @app.callback() + def default( + version: bool = typer.Option( + None, + "--version", + help="Shows version of the Snowflake CLI", + callback=_version_callback, + is_eager=True, + ), + docs: bool = typer.Option( + None, + "--docs", + hidden=True, + help="Generates Snowflake CLI documentation", + callback=_docs_callback, + is_eager=True, + ), + structure: bool = typer.Option( + None, + "--structure", + hidden=True, + help="Prints Snowflake CLI structure of commands", + callback=_commands_structure_callback, + is_eager=True, + ), + info: bool = typer.Option( + None, + "--info", + help="Shows information about the Snowflake CLI", + callback=_info_callback, + ), + configuration_file: Path = typer.Option( + None, + "--config-file", + help="Specifies Snowflake CLI configuration file that should be used", + exists=True, + dir_okay=False, + is_eager=True, + callback=_config_init_callback, + ), + pycharm_debug_library_path: str = typer.Option( + None, + "--pycharm-debug-library-path", + hidden=True, + ), + pycharm_debug_server_host: str = typer.Option( + "localhost", + "--pycharm-debug-server-host", + hidden=True, + ), + pycharm_debug_server_port: int = typer.Option( + 12345, + "--pycharm-debug-server-port", + hidden=True, + ), + disable_external_command_plugins: bool = typer.Option( + None, + "--disable-external-command-plugins", + help="Disable external command plugins", + callback=_disable_external_command_plugins_callback, + is_eager=True, + hidden=True, + ), + # THIS OPTION SHOULD BE THE LAST OPTION IN THE LIST! + # --- + # This is a hidden artificial option used only to guarantee execution of commands registration + # and make this guaranty not dependent on other callbacks. + # Commands registration is invoked as soon as all callbacks + # decorated with "_commands_registration.before" are executed + # but if there are no such callbacks (at the result of possible future changes) + # then we need to invoke commands registration manually. + # + # This option is also responsible for resetting registration state for test purposes. + commands_registration: bool = typer.Option( + True, + "--commands-registration", + help="Commands registration", + hidden=True, + is_eager=True, + callback=_commands_registration_callback, + ), + ) -> None: + """ + Snowflake CLI tool for developers. + """ + setup_pycharm_remote_debugger_if_provided( + pycharm_debug_library_path=pycharm_debug_library_path, + pycharm_debug_server_host=pycharm_debug_server_host, + pycharm_debug_server_port=pycharm_debug_server_port, + ) + + return app diff --git a/tests/conftest.py b/tests/conftest.py index 1b07455cdd..c19e1610b9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,7 +11,6 @@ from snowflake.cli.api.console import cli_console from snowflake.cli.api.output.types import QueryResult from snowflake.cli.app import loggers -from snowflake.cli.app.cli_app import app pytest_plugins = ["tests.testing_utils.fixtures", "tests.project.fixtures"] @@ -72,7 +71,9 @@ def make_mock_cursor(mock_cursor): @pytest.fixture(name="faker_app") -def make_faker_app(_create_mock_cursor): +def make_faker_app(runner, _create_mock_cursor): + app = runner.app + @app.command("Faker") @with_output @global_options diff --git a/tests/testing_utils/fixtures.py b/tests/testing_utils/fixtures.py index fe643b5b59..5c30a0bb23 100644 --- a/tests/testing_utils/fixtures.py +++ b/tests/testing_utils/fixtures.py @@ -13,6 +13,7 @@ import pytest import strictyaml from snowflake.cli.api.project.definition import merge_left +from snowflake.cli.app.cli_app import app_factory from snowflake.connector.cursor import SnowflakeCursor from snowflake.connector.errors import ProgrammingError from strictyaml import as_document @@ -80,7 +81,7 @@ def dot_packages_directory(temp_dir): @pytest.fixture() def mock_ctx(mock_cursor): - return lambda cursor=mock_cursor(["row"], []): MockConnectionCtx(cursor) + yield lambda cursor=mock_cursor(["row"], []): MockConnectionCtx(cursor) class MockConnectionCtx(mock.MagicMock): @@ -200,9 +201,8 @@ def package_file(): @pytest.fixture(scope="function") def runner(test_snowcli_config): - from snowflake.cli.app.cli_app import app - - return SnowCLIRunner(app, test_snowcli_config) + app = app_factory() + yield SnowCLIRunner(app, test_snowcli_config) @pytest.fixture diff --git a/tests_integration/conftest.py b/tests_integration/conftest.py index 404b4eba87..633c7d4046 100644 --- a/tests_integration/conftest.py +++ b/tests_integration/conftest.py @@ -14,7 +14,7 @@ import strictyaml from snowflake.cli.api.cli_global_context import cli_context_manager from snowflake.cli.api.project.definition import merge_left -from snowflake.cli.app.cli_app import app +from snowflake.cli.app.cli_app import app_factory from strictyaml import as_document from typer import Typer from typer.testing import CliRunner @@ -113,7 +113,8 @@ def invoke_with_connection( @pytest.fixture def runner(test_snowcli_config_provider): - return SnowCLIRunner(app, test_snowcli_config_provider) + app = app_factory() + yield SnowCLIRunner(app, test_snowcli_config_provider) class QueryResultJsonEncoderError(RuntimeError): diff --git a/tests_integration/test_external_plugins.py b/tests_integration/test_external_plugins.py index c7112a36c0..cdbc88d83b 100644 --- a/tests_integration/test_external_plugins.py +++ b/tests_integration/test_external_plugins.py @@ -93,7 +93,10 @@ def test_loading_of_installed_plugins_if_all_plugins_enabled( @pytest.mark.integration def test_loading_of_installed_plugins_if_only_one_plugin_is_enabled( - runner, install_plugins, caplog, reset_command_registration_state + runner, + install_plugins, + caplog, + reset_command_registration_state, ): runner.use_config("config_with_enabled_only_one_external_plugin.toml") @@ -111,8 +114,18 @@ def test_loading_of_installed_plugins_if_only_one_plugin_is_enabled( @pytest.mark.integration +@pytest.mark.parametrize( + "config_value", + ( + pytest.param("1", id="integer as value"), + pytest.param('"True"', id="string as value"), + ), +) def test_enabled_value_must_be_boolean( - runner, snowflake_home, reset_command_registration_state + config_value, + runner, + snowflake_home, + reset_command_registration_state, ): def _use_config_with_value(value): config = Path(snowflake_home) / "config.toml" @@ -123,19 +136,18 @@ def _use_config_with_value(value): ) runner.use_config(config) - for value in ["1", '"True"']: - _use_config_with_value(value) - result = runner.invoke_with_config(["--help"]) - output = result.output.splitlines() - assert all( - [ - "Error" in output[0], - 'Invalid plugin configuration. [multilingual-hello]: "enabled" must be a' - in output[1], - "boolean" in output[2], - ] - ) - reset_command_registration_state() + _use_config_with_value(config_value) + result = runner.invoke_with_config(("--help,")) + + first, second, third, *_ = result.output.splitlines() + assert "Error" in first, first + assert ( + 'Invalid plugin configuration. [multilingual-hello]: "enabled" must be a' + in second + ), second + assert "boolean" in third, third + + reset_command_registration_state() def _assert_that_no_error_logs(caplog):