Skip to content

Commit

Permalink
mraba/app-factory: create app with factory (#860)
Browse files Browse the repository at this point in the history
* mraba/app-factory: create app with factory
  • Loading branch information
sfc-gh-mraba committed Mar 4, 2024
1 parent 14f6c3b commit 24800a1
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 115 deletions.
3 changes: 2 additions & 1 deletion src/snowflake/cli/app/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)


Expand Down
186 changes: 95 additions & 91 deletions src/snowflake/cli/app/cli_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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
5 changes: 3 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]

Expand Down Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions tests/testing_utils/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions tests_integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down
42 changes: 27 additions & 15 deletions tests_integration/test_external_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand All @@ -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"
Expand All @@ -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):
Expand Down

0 comments on commit 24800a1

Please sign in to comment.