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

# 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,
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