Skip to content

Commit cf1b713

Browse files
committed
SNOW-2306184: replace import level singleton with factory created
1 parent 41d9e8a commit cf1b713

File tree

10 files changed

+380
-139
lines changed

10 files changed

+380
-139
lines changed

src/snowflake/cli/_app/cli_app.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,14 @@
4040
get_new_version_msg,
4141
show_new_version_banner_callback,
4242
)
43-
from snowflake.cli.api.config import config_init, get_feature_flags_section
43+
from snowflake.cli.api.config import (
44+
config_init,
45+
get_config_manager,
46+
get_feature_flags_section,
47+
)
4448
from snowflake.cli.api.output.formats import OutputFormat
4549
from snowflake.cli.api.output.types import CollectionResult
4650
from snowflake.cli.api.secure_path import SecurePath
47-
from snowflake.connector.config_manager import CONFIG_MANAGER
4851

4952
log = logging.getLogger(__name__)
5053

@@ -160,7 +163,7 @@ def callback(value: bool):
160163
{"key": "version", "value": __about__.VERSION},
161164
{
162165
"key": "default_config_file_path",
163-
"value": str(CONFIG_MANAGER.file_path),
166+
"value": str(get_config_manager().file_path),
164167
},
165168
{"key": "python_version", "value": sys.version},
166169
{"key": "system_info", "value": platform.platform()},

src/snowflake/cli/_app/version_check.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
CLI_SECTION,
1111
IGNORE_NEW_VERSION_WARNING_KEY,
1212
get_config_bool_value,
13+
get_config_manager,
1314
)
1415
from snowflake.cli.api.secure_path import SecurePath
15-
from snowflake.connector.config_manager import CONFIG_MANAGER
1616

1717
REPOSITORY_URL_PIP = "https://pypi.org/pypi/snowflake-cli/json"
1818
REPOSITORY_URL_BREW = "https://formulae.brew.sh/api/formula/snowflake-cli.json"
@@ -69,12 +69,14 @@ class _VersionCache:
6969
_last_time = "last_time_check"
7070
_version = "version"
7171
_last_time_shown = "last_time_shown"
72-
_version_cache_file = SecurePath(
73-
CONFIG_MANAGER.file_path.parent / ".cli_version.cache"
74-
)
72+
73+
@property
74+
def _version_cache_file(self):
75+
"""Get version cache file path with lazy evaluation."""
76+
return SecurePath(get_config_manager().file_path.parent / ".cli_version.cache")
7577

7678
def __init__(self):
77-
self._cache_file = _VersionCache._version_cache_file
79+
self._cache_file = self._version_cache_file
7880

7981
def _save_latest_version(self, version: str):
8082
data = {

src/snowflake/cli/_plugins/sql/repl.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,27 @@
1313
from snowflake.cli._plugins.sql.manager import SqlManager
1414
from snowflake.cli._plugins.sql.repl_commands import detect_command
1515
from snowflake.cli.api.cli_global_context import get_cli_context_manager
16+
from snowflake.cli.api.config import get_config_manager
1617
from snowflake.cli.api.console import cli_console
1718
from snowflake.cli.api.output.types import MultipleResults, QueryResult
1819
from snowflake.cli.api.rendering.sql_templates import SQLTemplateSyntaxConfig
1920
from snowflake.cli.api.secure_path import SecurePath
20-
from snowflake.connector.config_manager import CONFIG_MANAGER
2121
from snowflake.connector.cursor import SnowflakeCursor
2222

2323
log = getLogger(__name__)
2424

25-
HISTORY_FILE = SecurePath(
26-
CONFIG_MANAGER.file_path.parent / "repl_history"
27-
).path.expanduser()
25+
26+
def _get_history_file():
27+
"""Get history file path with lazy evaluation to avoid circular imports."""
28+
return SecurePath(
29+
get_config_manager().file_path.parent / "repl_history"
30+
).path.expanduser()
31+
32+
33+
HISTORY_FILE = None # Will be set lazily
2834
EXIT_KEYWORDS = ("exit", "quit")
2935

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

3238

3339
@contextmanager
@@ -65,7 +71,7 @@ def __init__(
6571
self._data = data or {}
6672
self._retain_comments = retain_comments
6773
self._template_syntax_config = template_syntax_config
68-
self._history = FileHistory(HISTORY_FILE)
74+
self._history = FileHistory(_get_history_file())
6975
self._lexer = PygmentsLexer(CliLexer)
7076
self._completer = cli_completer
7177
self._repl_key_bindings = self._setup_key_bindings()

src/snowflake/cli/api/cli_global_context.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from snowflake.cli._plugins.sql.repl import Repl
3333
from snowflake.cli.api.project.definition_manager import DefinitionManager
3434
from snowflake.cli.api.project.schemas.project_definition import ProjectDefinition
35+
from snowflake.connector.config_manager import ConfigManager
3536

3637
_CONNECTION_CACHE = OpenConnectionCache()
3738

@@ -66,13 +67,21 @@ class _CliGlobalContextManager:
6667
_definition_manager: DefinitionManager | None = None
6768
enhanced_exit_codes: bool = False
6869

70+
# Configuration management
71+
_config_manager: ConfigManager | None = None
72+
config_file_override: Path | None = None
73+
connections_file_override: Path | None = None
74+
6975
# which properties invalidate our current DefinitionManager?
7076
DEFINITION_MANAGER_DEPENDENCIES = [
7177
"project_path_arg",
7278
"project_is_optional",
7379
"project_env_overrides_args",
7480
]
7581

82+
# Dependencies that invalidate config manager
83+
CONFIG_MANAGER_DEPENDENCIES = ["config_file_override", "connections_file_override"]
84+
7685
def reset(self):
7786
self.__init__()
7887

@@ -88,6 +97,9 @@ def __setattr__(self, prop, val):
8897
if prop in self.DEFINITION_MANAGER_DEPENDENCIES:
8998
self._clear_definition_manager()
9099

100+
if prop in self.CONFIG_MANAGER_DEPENDENCIES:
101+
self._clear_config_manager()
102+
91103
super().__setattr__(prop, val)
92104

93105
@property
@@ -144,6 +156,75 @@ def _clear_definition_manager(self):
144156
"""
145157
self._definition_manager = None
146158

159+
@property
160+
def config_manager(self) -> ConfigManager:
161+
"""
162+
Get or create the configuration manager instance.
163+
Follows the same lazy initialization pattern as DefinitionManager.
164+
"""
165+
if self._config_manager is None:
166+
self._config_manager = self._create_config_manager()
167+
return self._config_manager
168+
169+
def _create_config_manager(self) -> ConfigManager:
170+
"""
171+
Factory method to create ConfigManager instance with CLI-specific options.
172+
Replicates the behavior of the imported CONFIG_MANAGER singleton.
173+
"""
174+
import tomlkit
175+
from snowflake.cli.api.config import get_connections_file
176+
from snowflake.connector.config_manager import (
177+
ConfigManager,
178+
ConfigSlice,
179+
ConfigSliceOptions,
180+
)
181+
from snowflake.connector.constants import CONFIG_FILE
182+
183+
# Get current connections file path (handles test env changes)
184+
connections_file = get_connections_file()
185+
186+
# Create ConfigSlice for connections.toml (same as singleton CONFIG_MANAGER)
187+
connections_slice = ConfigSlice(
188+
path=connections_file,
189+
options=ConfigSliceOptions(check_permissions=True, only_in_slice=False),
190+
section="connections",
191+
)
192+
193+
# Create manager instance with connections slice
194+
manager = ConfigManager(
195+
name="CONFIG_MANAGER",
196+
file_path=self.config_file_override or CONFIG_FILE,
197+
_slices=[connections_slice],
198+
)
199+
200+
# Add connector's default options (replicating connector's singleton setup)
201+
manager.add_option(
202+
name="connections",
203+
parse_str=tomlkit.parse,
204+
default=dict(),
205+
)
206+
207+
manager.add_option(
208+
name="default_connection_name", parse_str=str, default="default"
209+
)
210+
211+
# Add CLI-specific options (current lines 66-70 in config.py)
212+
from snowflake.cli.api.config import CLI_SECTION
213+
214+
manager.add_option(
215+
name=CLI_SECTION,
216+
parse_str=tomlkit.parse,
217+
default=dict(),
218+
)
219+
220+
return manager
221+
222+
def _clear_config_manager(self):
223+
"""
224+
Force re-creation of config manager when dependencies change.
225+
"""
226+
self._config_manager = None
227+
147228

148229
class _CliGlobalContextAccess:
149230
def __init__(self, manager: _CliGlobalContextManager):
@@ -216,6 +297,21 @@ def repl(self) -> Repl | None:
216297
"""Get the current REPL instance if running in REPL mode."""
217298
return self._manager.repl_instance
218299

300+
@property
301+
def config_manager(self) -> ConfigManager:
302+
"""Get the current configuration manager."""
303+
return self._manager.config_manager
304+
305+
@property
306+
def config_file_override(self) -> Path | None:
307+
"""Get the current config file override path."""
308+
return self._manager.config_file_override
309+
310+
@config_file_override.setter
311+
def config_file_override(self, value: Path | None) -> None:
312+
"""Set the config file override path."""
313+
self._manager.config_file_override = value
314+
219315

220316
_CLI_CONTEXT_MANAGER: ContextVar[_CliGlobalContextManager | None] = ContextVar(
221317
"cli_context", default=None

0 commit comments

Comments
 (0)