Skip to content
9 changes: 6 additions & 3 deletions src/snowflake/cli/_app/cli_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,14 @@
get_new_version_msg,
show_new_version_banner_callback,
)
from snowflake.cli.api.config import config_init, get_feature_flags_section
from snowflake.cli.api.config import (
config_init,
get_config_manager,
get_feature_flags_section,
)
from snowflake.cli.api.output.formats import OutputFormat
from snowflake.cli.api.output.types import CollectionResult
from snowflake.cli.api.secure_path import SecurePath
from snowflake.connector.config_manager import CONFIG_MANAGER

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -160,7 +163,7 @@ def callback(value: bool):
{"key": "version", "value": __about__.VERSION},
{
"key": "default_config_file_path",
"value": str(CONFIG_MANAGER.file_path),
"value": str(get_config_manager().file_path),
},
{"key": "python_version", "value": sys.version},
{"key": "system_info", "value": platform.platform()},
Expand Down
7 changes: 6 additions & 1 deletion src/snowflake/cli/_app/loggers.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import logging
import logging.config
from dataclasses import asdict, dataclass, field
from pathlib import Path
from typing import Any, Dict, List

import typer
Expand Down Expand Up @@ -136,7 +137,11 @@ def _check_log_level(self, config):

@property
def filename(self):
return self.path.path / _DEFAULT_LOG_FILENAME
from snowflake.cli.api.utils.path_utils import path_resolver
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: local import


# Ensure Windows short paths are resolved to prevent cleanup issues
resolved_path = path_resolver(str(self.path.path))
return Path(resolved_path) / _DEFAULT_LOG_FILENAME


def create_initial_loggers():
Expand Down
12 changes: 7 additions & 5 deletions src/snowflake/cli/_app/version_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
CLI_SECTION,
IGNORE_NEW_VERSION_WARNING_KEY,
get_config_bool_value,
get_config_manager,
)
from snowflake.cli.api.secure_path import SecurePath
from snowflake.connector.config_manager import CONFIG_MANAGER

REPOSITORY_URL_PIP = "https://pypi.org/pypi/snowflake-cli/json"
REPOSITORY_URL_BREW = "https://formulae.brew.sh/api/formula/snowflake-cli.json"
Expand Down Expand Up @@ -69,12 +69,14 @@ class _VersionCache:
_last_time = "last_time_check"
_version = "version"
_last_time_shown = "last_time_shown"
_version_cache_file = SecurePath(
CONFIG_MANAGER.file_path.parent / ".cli_version.cache"
)

@property
def _version_cache_file(self):
"""Get version cache file path with lazy evaluation."""
return SecurePath(get_config_manager().file_path.parent / ".cli_version.cache")

def __init__(self):
self._cache_file = _VersionCache._version_cache_file
self._cache_file = self._version_cache_file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The property _version_cache_file is now a method rather than a class variable, but it's still being evaluated immediately during initialization when assigned to self._cache_file. This defeats the purpose of lazy evaluation.

Consider modifying this approach to truly implement lazy loading:

def __init__(self):
    self._cache_file = None  # Initialize as None

@property
def _cache_file(self):
    if self.__cache_file is None:
        self.__cache_file = self._version_cache_file
    return self.__cache_file

@_cache_file.setter
def _cache_file(self, value):
    self.__cache_file = value

This way, the property is only evaluated when first accessed, not during initialization.

Spotted by Diamond

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.


def _save_latest_version(self, version: str):
data = {
Expand Down
18 changes: 12 additions & 6 deletions src/snowflake/cli/_plugins/sql/repl.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,27 @@
from snowflake.cli._plugins.sql.manager import SqlManager
from snowflake.cli._plugins.sql.repl_commands import detect_command
from snowflake.cli.api.cli_global_context import get_cli_context_manager
from snowflake.cli.api.config import get_config_manager
from snowflake.cli.api.console import cli_console
from snowflake.cli.api.output.types import MultipleResults, QueryResult
from snowflake.cli.api.rendering.sql_templates import SQLTemplateSyntaxConfig
from snowflake.cli.api.secure_path import SecurePath
from snowflake.connector.config_manager import CONFIG_MANAGER
from snowflake.connector.cursor import SnowflakeCursor

log = getLogger(__name__)

HISTORY_FILE = SecurePath(
CONFIG_MANAGER.file_path.parent / "repl_history"
).path.expanduser()

def _get_history_file():
"""Get history file path with lazy evaluation to avoid circular imports."""
return SecurePath(
get_config_manager().file_path.parent / "repl_history"
).path.expanduser()


HISTORY_FILE = None # Will be set lazily
EXIT_KEYWORDS = ("exit", "quit")

log.debug("setting history file to: %s", HISTORY_FILE.as_posix())
# History file path will be set when REPL is initialized


@contextmanager
Expand Down Expand Up @@ -65,7 +71,7 @@ def __init__(
self._data = data or {}
self._retain_comments = retain_comments
self._template_syntax_config = template_syntax_config
self._history = FileHistory(HISTORY_FILE)
self._history = FileHistory(_get_history_file())
self._lexer = PygmentsLexer(CliLexer)
self._completer = cli_completer
self._repl_key_bindings = self._setup_key_bindings()
Expand Down
88 changes: 88 additions & 0 deletions src/snowflake/cli/api/cli_global_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,19 @@
from pathlib import Path
from typing import TYPE_CHECKING, Iterator

import tomlkit
from snowflake.cli.api.connections import ConnectionContext, OpenConnectionCache
from snowflake.cli.api.exceptions import MissingConfigurationError
from snowflake.cli.api.metrics import CLIMetrics
from snowflake.cli.api.output.formats import OutputFormat
from snowflake.cli.api.rendering.jinja import CONTEXT_KEY
from snowflake.connector import SnowflakeConnection
from snowflake.connector.config_manager import (
ConfigManager,
ConfigSlice,
ConfigSliceOptions,
)
from snowflake.connector.constants import CONFIG_FILE

if TYPE_CHECKING:
from snowflake.cli._plugins.sql.repl import Repl
Expand Down Expand Up @@ -66,13 +73,19 @@ class _CliGlobalContextManager:
_definition_manager: DefinitionManager | None = None
enhanced_exit_codes: bool = False

_config_manager: ConfigManager | None = None
config_file_override: Path | None = None
connections_file_override: Path | None = None

# which properties invalidate our current DefinitionManager?
DEFINITION_MANAGER_DEPENDENCIES = [
"project_path_arg",
"project_is_optional",
"project_env_overrides_args",
]

CONFIG_MANAGER_DEPENDENCIES = ["config_file_override", "connections_file_override"]

def reset(self):
self.__init__()

Expand All @@ -88,6 +101,9 @@ def __setattr__(self, prop, val):
if prop in self.DEFINITION_MANAGER_DEPENDENCIES:
self._clear_definition_manager()

if prop in self.CONFIG_MANAGER_DEPENDENCIES:
self._clear_config_manager()

super().__setattr__(prop, val)

@property
Expand Down Expand Up @@ -144,6 +160,63 @@ def _clear_definition_manager(self):
"""
self._definition_manager = None

@property
def config_manager(self) -> ConfigManager:
"""
Get or create the configuration manager instance.
Follows the same lazy initialization pattern as DefinitionManager.
"""
if self._config_manager is None:
self._config_manager = self._create_config_manager()
return self._config_manager

def _create_config_manager(self) -> ConfigManager:
"""
Factory method to create ConfigManager instance with CLI-specific options.
Replicates the behavior of the imported CONFIG_MANAGER singleton.
"""
from snowflake.cli.api.config import get_connections_file

connections_file = get_connections_file()

connections_slice = ConfigSlice(
path=connections_file,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

path=self.connections_file_override or connections_file

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually is that override even needed? We don't seem to be using it anywhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With connector based singleton this was created by default. As we are dropping the connector version of singleton we need to have our own support for connections.toml

options=ConfigSliceOptions(check_permissions=True, only_in_slice=False),
section="connections",
)

manager = ConfigManager(
name="CONFIG_MANAGER",
file_path=self.config_file_override or CONFIG_FILE,
_slices=[connections_slice],
)

manager.add_option(
name="connections",
parse_str=tomlkit.parse,
default=dict(),
)

manager.add_option(
name="default_connection_name", parse_str=str, default="default"
)

from snowflake.cli.api.config import CLI_SECTION

manager.add_option(
name=CLI_SECTION,
parse_str=tomlkit.parse,
default=dict(),
)

return manager

def _clear_config_manager(self):
"""
Force re-creation of config manager when dependencies change.
"""
self._config_manager = None


class _CliGlobalContextAccess:
def __init__(self, manager: _CliGlobalContextManager):
Expand Down Expand Up @@ -216,6 +289,21 @@ def repl(self) -> Repl | None:
"""Get the current REPL instance if running in REPL mode."""
return self._manager.repl_instance

@property
def config_manager(self) -> ConfigManager:
"""Get the current configuration manager."""
return self._manager.config_manager

@property
def config_file_override(self) -> Path | None:
"""Get the current config file override path."""
return self._manager.config_file_override

@config_file_override.setter
def config_file_override(self, value: Path | None) -> None:
"""Set the config file override path."""
self._manager.config_file_override = value


_CLI_CONTEXT_MANAGER: ContextVar[_CliGlobalContextManager | None] = ContextVar(
"cli_context", default=None
Expand Down
Loading
Loading