snowflake-cli-labs 3.0.0rc1__py3-none-any.whl → 3.0.0rc2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- snowflake/cli/__about__.py +1 -1
- snowflake/cli/_app/cli_app.py +10 -1
- snowflake/cli/_app/snow_connector.py +76 -29
- snowflake/cli/_app/telemetry.py +8 -4
- snowflake/cli/_app/version_check.py +74 -0
- snowflake/cli/_plugins/git/commands.py +55 -14
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +2 -5
- snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +49 -31
- snowflake/cli/_plugins/nativeapp/manager.py +46 -87
- snowflake/cli/_plugins/nativeapp/run_processor.py +56 -260
- snowflake/cli/_plugins/nativeapp/same_account_install_method.py +74 -0
- snowflake/cli/_plugins/nativeapp/teardown_processor.py +9 -152
- snowflake/cli/_plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +91 -17
- snowflake/cli/_plugins/snowpark/commands.py +1 -1
- snowflake/cli/_plugins/snowpark/models.py +2 -1
- snowflake/cli/_plugins/streamlit/commands.py +1 -1
- snowflake/cli/_plugins/streamlit/manager.py +9 -0
- snowflake/cli/_plugins/workspace/action_context.py +2 -1
- snowflake/cli/_plugins/workspace/commands.py +48 -16
- snowflake/cli/_plugins/workspace/manager.py +1 -0
- snowflake/cli/api/cli_global_context.py +136 -313
- snowflake/cli/api/commands/flags.py +76 -91
- snowflake/cli/api/commands/snow_typer.py +6 -4
- snowflake/cli/api/config.py +1 -1
- snowflake/cli/api/connections.py +214 -0
- snowflake/cli/api/console/abc.py +4 -2
- snowflake/cli/api/entities/application_entity.py +687 -2
- snowflake/cli/api/entities/application_package_entity.py +151 -46
- snowflake/cli/api/entities/common.py +1 -0
- snowflake/cli/api/entities/utils.py +41 -17
- snowflake/cli/api/identifiers.py +3 -0
- snowflake/cli/api/project/definition.py +11 -0
- snowflake/cli/api/project/definition_conversion.py +171 -13
- snowflake/cli/api/project/schemas/entities/common.py +0 -12
- snowflake/cli/api/project/schemas/identifier_model.py +2 -2
- snowflake/cli/api/project/schemas/project_definition.py +101 -39
- snowflake/cli/api/rendering/project_definition_templates.py +4 -0
- snowflake/cli/api/rendering/sql_templates.py +7 -0
- snowflake/cli/api/utils/definition_rendering.py +3 -1
- {snowflake_cli_labs-3.0.0rc1.dist-info → snowflake_cli_labs-3.0.0rc2.dist-info}/METADATA +6 -6
- {snowflake_cli_labs-3.0.0rc1.dist-info → snowflake_cli_labs-3.0.0rc2.dist-info}/RECORD +44 -42
- snowflake/cli/api/commands/typer_pre_execute.py +0 -26
- {snowflake_cli_labs-3.0.0rc1.dist-info → snowflake_cli_labs-3.0.0rc2.dist-info}/WHEEL +0 -0
- {snowflake_cli_labs-3.0.0rc1.dist-info → snowflake_cli_labs-3.0.0rc2.dist-info}/entry_points.txt +0 -0
- {snowflake_cli_labs-3.0.0rc1.dist-info → snowflake_cli_labs-3.0.0rc2.dist-info}/licenses/LICENSE +0 -0
|
@@ -14,337 +14,126 @@
|
|
|
14
14
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
|
-
import
|
|
18
|
-
import
|
|
17
|
+
from contextlib import contextmanager
|
|
18
|
+
from contextvars import ContextVar
|
|
19
|
+
from dataclasses import dataclass, field, replace
|
|
19
20
|
from pathlib import Path
|
|
20
|
-
from typing import TYPE_CHECKING,
|
|
21
|
+
from typing import TYPE_CHECKING, Iterator
|
|
21
22
|
|
|
22
|
-
from snowflake.cli.api.
|
|
23
|
+
from snowflake.cli.api.connections import ConnectionContext, OpenConnectionCache
|
|
24
|
+
from snowflake.cli.api.exceptions import MissingConfiguration
|
|
23
25
|
from snowflake.cli.api.output.formats import OutputFormat
|
|
26
|
+
from snowflake.cli.api.rendering.jinja import CONTEXT_KEY
|
|
24
27
|
from snowflake.connector import SnowflakeConnection
|
|
25
|
-
from snowflake.connector.compat import IS_WINDOWS
|
|
26
28
|
|
|
27
29
|
if TYPE_CHECKING:
|
|
30
|
+
from snowflake.cli.api.project.definition_manager import DefinitionManager
|
|
28
31
|
from snowflake.cli.api.project.schemas.project_definition import ProjectDefinition
|
|
29
32
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class _ConnectionContext:
|
|
34
|
-
def __init__(self):
|
|
35
|
-
self._cached_connection: Optional[SnowflakeConnection] = None
|
|
36
|
-
|
|
37
|
-
self._connection_name: Optional[str] = None
|
|
38
|
-
self._account: Optional[str] = None
|
|
39
|
-
self._database: Optional[str] = None
|
|
40
|
-
self._role: Optional[str] = None
|
|
41
|
-
self._schema: Optional[str] = None
|
|
42
|
-
self._user: Optional[str] = None
|
|
43
|
-
self._password: Optional[str] = None
|
|
44
|
-
self._authenticator: Optional[str] = None
|
|
45
|
-
self._private_key_file: Optional[str] = None
|
|
46
|
-
self._warehouse: Optional[str] = None
|
|
47
|
-
self._mfa_passcode: Optional[str] = None
|
|
48
|
-
self._enable_diag: Optional[bool] = False
|
|
49
|
-
self._diag_log_path: Optional[Path] = None
|
|
50
|
-
self._diag_allowlist_path: Optional[Path] = None
|
|
51
|
-
self._temporary_connection: bool = False
|
|
52
|
-
self._session_token: Optional[str] = None
|
|
53
|
-
self._master_token: Optional[str] = None
|
|
54
|
-
self._token_file_path: Optional[Path] = None
|
|
55
|
-
|
|
56
|
-
def __setattr__(self, key, value):
|
|
57
|
-
"""
|
|
58
|
-
We invalidate connection cache every time connection attributes change.
|
|
59
|
-
"""
|
|
60
|
-
super().__setattr__(key, value)
|
|
61
|
-
if key != "_cached_connection":
|
|
62
|
-
self._cached_connection = None
|
|
63
|
-
|
|
64
|
-
@property
|
|
65
|
-
def connection_name(self) -> Optional[str]:
|
|
66
|
-
return self._connection_name
|
|
67
|
-
|
|
68
|
-
def set_connection_name(self, value: Optional[str]):
|
|
69
|
-
self._connection_name = value
|
|
70
|
-
|
|
71
|
-
@property
|
|
72
|
-
def account(self) -> Optional[str]:
|
|
73
|
-
return self._account
|
|
74
|
-
|
|
75
|
-
def set_account(self, value: Optional[str]):
|
|
76
|
-
self._account = value
|
|
77
|
-
|
|
78
|
-
@property
|
|
79
|
-
def database(self) -> Optional[str]:
|
|
80
|
-
return self._database
|
|
81
|
-
|
|
82
|
-
def set_database(self, value: Optional[str]):
|
|
83
|
-
self._database = value
|
|
84
|
-
|
|
85
|
-
@property
|
|
86
|
-
def role(self) -> Optional[str]:
|
|
87
|
-
return self._role
|
|
88
|
-
|
|
89
|
-
def set_role(self, value: Optional[str]):
|
|
90
|
-
self._role = value
|
|
91
|
-
|
|
92
|
-
@property
|
|
93
|
-
def schema(self) -> Optional[str]:
|
|
94
|
-
return self._schema
|
|
95
|
-
|
|
96
|
-
def set_schema(self, value: Optional[str]):
|
|
97
|
-
if (
|
|
98
|
-
value
|
|
99
|
-
and not (value.startswith('"') and value.endswith('"'))
|
|
100
|
-
# if schema is fully qualified name (db.schema)
|
|
101
|
-
and schema_pattern.match(value)
|
|
102
|
-
):
|
|
103
|
-
raise InvalidSchemaError(value)
|
|
104
|
-
self._schema = value
|
|
105
|
-
|
|
106
|
-
@property
|
|
107
|
-
def user(self) -> Optional[str]:
|
|
108
|
-
return self._user
|
|
109
|
-
|
|
110
|
-
def set_user(self, value: Optional[str]):
|
|
111
|
-
self._user = value
|
|
112
|
-
|
|
113
|
-
@property
|
|
114
|
-
def password(self) -> Optional[str]:
|
|
115
|
-
return self._password
|
|
116
|
-
|
|
117
|
-
def set_password(self, value: Optional[str]):
|
|
118
|
-
self._password = value
|
|
119
|
-
|
|
120
|
-
@property
|
|
121
|
-
def authenticator(self) -> Optional[str]:
|
|
122
|
-
return self._authenticator
|
|
123
|
-
|
|
124
|
-
def set_authenticator(self, value: Optional[str]):
|
|
125
|
-
self._authenticator = value
|
|
126
|
-
|
|
127
|
-
@property
|
|
128
|
-
def private_key_file(self) -> Optional[str]:
|
|
129
|
-
return self._private_key_file
|
|
130
|
-
|
|
131
|
-
def set_private_key_file(self, value: Optional[str]):
|
|
132
|
-
self._private_key_file = value
|
|
133
|
-
|
|
134
|
-
@property
|
|
135
|
-
def warehouse(self) -> Optional[str]:
|
|
136
|
-
return self._warehouse
|
|
137
|
-
|
|
138
|
-
def set_warehouse(self, value: Optional[str]):
|
|
139
|
-
self._warehouse = value
|
|
140
|
-
|
|
141
|
-
@property
|
|
142
|
-
def mfa_passcode(self) -> Optional[str]:
|
|
143
|
-
return self._mfa_passcode
|
|
144
|
-
|
|
145
|
-
def set_mfa_passcode(self, value: Optional[str]):
|
|
146
|
-
self._mfa_passcode = value
|
|
147
|
-
|
|
148
|
-
@property
|
|
149
|
-
def enable_diag(self) -> Optional[bool]:
|
|
150
|
-
return self._enable_diag
|
|
151
|
-
|
|
152
|
-
def set_enable_diag(self, value: Optional[bool]):
|
|
153
|
-
self._enable_diag = value
|
|
154
|
-
|
|
155
|
-
@property
|
|
156
|
-
def diag_log_path(self) -> Optional[Path]:
|
|
157
|
-
return self._diag_log_path
|
|
158
|
-
|
|
159
|
-
def set_diag_log_path(self, value: Optional[Path]):
|
|
160
|
-
self._diag_log_path = value
|
|
161
|
-
|
|
162
|
-
@property
|
|
163
|
-
def diag_allowlist_path(self) -> Optional[Path]:
|
|
164
|
-
return self._diag_allowlist_path
|
|
165
|
-
|
|
166
|
-
def set_diag_allowlist_path(self, value: Optional[Path]):
|
|
167
|
-
self._diag_allowlist_path = value
|
|
168
|
-
|
|
169
|
-
@property
|
|
170
|
-
def temporary_connection(self) -> bool:
|
|
171
|
-
return self._temporary_connection
|
|
172
|
-
|
|
173
|
-
def set_temporary_connection(self, value: bool):
|
|
174
|
-
self._temporary_connection = value
|
|
175
|
-
|
|
176
|
-
@property
|
|
177
|
-
def session_token(self) -> Optional[str]:
|
|
178
|
-
return self._session_token
|
|
179
|
-
|
|
180
|
-
def set_session_token(self, value: Optional[str]):
|
|
181
|
-
self._session_token = value
|
|
182
|
-
|
|
183
|
-
@property
|
|
184
|
-
def master_token(self) -> Optional[str]:
|
|
185
|
-
return self._master_token
|
|
186
|
-
|
|
187
|
-
def set_master_token(self, value: Optional[str]):
|
|
188
|
-
self._master_token = value
|
|
189
|
-
|
|
190
|
-
@property
|
|
191
|
-
def token_file_path(self) -> Optional[Path]:
|
|
192
|
-
return self._token_file_path
|
|
193
|
-
|
|
194
|
-
def set_token_file_path(self, value: Optional[Path]):
|
|
195
|
-
self._token_file_path = value
|
|
196
|
-
|
|
197
|
-
@property
|
|
198
|
-
def connection(self) -> SnowflakeConnection:
|
|
199
|
-
if not self._cached_connection:
|
|
200
|
-
self._cached_connection = self._build_connection()
|
|
201
|
-
return self._cached_connection
|
|
202
|
-
|
|
203
|
-
def _collect_not_empty_connection_attributes(self):
|
|
204
|
-
return {
|
|
205
|
-
"account": self.account,
|
|
206
|
-
"user": self.user,
|
|
207
|
-
"password": self.password,
|
|
208
|
-
"authenticator": self.authenticator,
|
|
209
|
-
"private_key_file": self.private_key_file,
|
|
210
|
-
"database": self.database,
|
|
211
|
-
"schema": self.schema,
|
|
212
|
-
"role": self.role,
|
|
213
|
-
"warehouse": self.warehouse,
|
|
214
|
-
"session_token": self.session_token,
|
|
215
|
-
"master_token": self.master_token,
|
|
216
|
-
"token_file_path": self.token_file_path,
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
def _build_connection(self):
|
|
220
|
-
from snowflake.cli._app.snow_connector import connect_to_snowflake
|
|
221
|
-
|
|
222
|
-
# Ignore warnings about bad owner or permissions on Windows
|
|
223
|
-
# Telemetry omit our warning filter from config.py
|
|
224
|
-
if IS_WINDOWS:
|
|
225
|
-
warnings.filterwarnings(
|
|
226
|
-
action="ignore",
|
|
227
|
-
message="Bad owner or permissions.*",
|
|
228
|
-
module="snowflake.connector.config_manager",
|
|
229
|
-
)
|
|
230
|
-
|
|
231
|
-
return connect_to_snowflake(
|
|
232
|
-
temporary_connection=self.temporary_connection,
|
|
233
|
-
mfa_passcode=self._mfa_passcode,
|
|
234
|
-
enable_diag=self._enable_diag,
|
|
235
|
-
diag_log_path=self._diag_log_path,
|
|
236
|
-
diag_allowlist_path=self._diag_allowlist_path,
|
|
237
|
-
connection_name=self.connection_name,
|
|
238
|
-
**self._collect_not_empty_connection_attributes(),
|
|
239
|
-
)
|
|
33
|
+
_CONNECTION_CACHE = OpenConnectionCache()
|
|
240
34
|
|
|
241
35
|
|
|
36
|
+
@dataclass
|
|
242
37
|
class _CliGlobalContextManager:
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
38
|
+
connection_context: ConnectionContext = field(default_factory=ConnectionContext)
|
|
39
|
+
connection_cache: OpenConnectionCache = (
|
|
40
|
+
_CONNECTION_CACHE # by default, use global cache
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
output_format: OutputFormat = OutputFormat.TABLE
|
|
44
|
+
silent: bool = False
|
|
45
|
+
verbose: bool = False
|
|
46
|
+
experimental: bool = False
|
|
47
|
+
enable_tracebacks: bool = True
|
|
48
|
+
|
|
49
|
+
project_path_arg: str | None = None
|
|
50
|
+
project_is_optional: bool = True
|
|
51
|
+
project_env_overrides_args: dict[str, str] = field(default_factory=dict)
|
|
52
|
+
|
|
53
|
+
# FIXME: this property only exists to help implement
|
|
54
|
+
# nativeapp_definition_v2_to_v1. Consider changing the way
|
|
55
|
+
# this calculation is provided to commands in order to remove
|
|
56
|
+
# this logic (then make project_definition a non-cloned @property)
|
|
57
|
+
override_project_definition: ProjectDefinition | None = None
|
|
58
|
+
|
|
59
|
+
_definition_manager: DefinitionManager | None = None
|
|
60
|
+
|
|
61
|
+
# which properties invalidate our current DefinitionManager?
|
|
62
|
+
DEFINITION_MANAGER_DEPENDENCIES = [
|
|
63
|
+
"project_path_arg",
|
|
64
|
+
"project_is_optional",
|
|
65
|
+
"project_env_overrides_args",
|
|
66
|
+
]
|
|
256
67
|
|
|
257
68
|
def reset(self):
|
|
258
69
|
self.__init__()
|
|
259
70
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
@property
|
|
268
|
-
def output_format(self) -> OutputFormat:
|
|
269
|
-
return self._output_format
|
|
270
|
-
|
|
271
|
-
def set_output_format(self, value: OutputFormat):
|
|
272
|
-
self._output_format = value
|
|
273
|
-
|
|
274
|
-
@property
|
|
275
|
-
def verbose(self) -> bool:
|
|
276
|
-
return self._verbose
|
|
277
|
-
|
|
278
|
-
def set_verbose(self, value: bool):
|
|
279
|
-
self._verbose = value
|
|
280
|
-
|
|
281
|
-
@property
|
|
282
|
-
def experimental(self) -> bool:
|
|
283
|
-
return self._experimental
|
|
284
|
-
|
|
285
|
-
def set_experimental(self, value: bool):
|
|
286
|
-
self._experimental = value
|
|
287
|
-
|
|
288
|
-
@property
|
|
289
|
-
def project_definition(self) -> Optional[ProjectDefinition]:
|
|
290
|
-
return self._project_definition
|
|
291
|
-
|
|
292
|
-
def set_project_definition(self, value: ProjectDefinition):
|
|
293
|
-
self._project_definition = value
|
|
71
|
+
def clone(self) -> _CliGlobalContextManager:
|
|
72
|
+
return replace(
|
|
73
|
+
self,
|
|
74
|
+
connection_context=self.connection_context.clone(),
|
|
75
|
+
project_env_overrides_args=self.project_env_overrides_args.copy(),
|
|
76
|
+
)
|
|
294
77
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
78
|
+
def __setattr__(self, prop, val):
|
|
79
|
+
if prop in self.DEFINITION_MANAGER_DEPENDENCIES:
|
|
80
|
+
self._clear_definition_manager()
|
|
298
81
|
|
|
299
|
-
|
|
300
|
-
self._project_root = project_root
|
|
82
|
+
super().__setattr__(prop, val)
|
|
301
83
|
|
|
302
84
|
@property
|
|
303
|
-
def
|
|
304
|
-
|
|
85
|
+
def project_definition(self) -> ProjectDefinition | None:
|
|
86
|
+
if self.override_project_definition:
|
|
87
|
+
return self.override_project_definition
|
|
305
88
|
|
|
306
|
-
|
|
307
|
-
self._project_path_arg = project_path_arg
|
|
89
|
+
return self._definition_manager_or_raise().project_definition
|
|
308
90
|
|
|
309
91
|
@property
|
|
310
|
-
def
|
|
311
|
-
return self.
|
|
312
|
-
|
|
313
|
-
def set_project_env_overrides_args(
|
|
314
|
-
self, project_env_overrides_args: dict[str, str]
|
|
315
|
-
):
|
|
316
|
-
self._project_env_overrides_args = project_env_overrides_args
|
|
92
|
+
def project_root(self) -> Path:
|
|
93
|
+
return Path(self._definition_manager_or_raise().project_root)
|
|
317
94
|
|
|
318
95
|
@property
|
|
319
96
|
def template_context(self) -> dict:
|
|
320
|
-
return self.
|
|
321
|
-
|
|
322
|
-
def set_template_context(self, template_context: dict):
|
|
323
|
-
self._template_context = template_context
|
|
97
|
+
return self._definition_manager_or_raise().template_context
|
|
324
98
|
|
|
325
99
|
@property
|
|
326
|
-
def
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
self.
|
|
100
|
+
def connection(self) -> SnowflakeConnection:
|
|
101
|
+
"""
|
|
102
|
+
Returns a connection for our configured context from the configured cache.
|
|
103
|
+
By default, this is the global _CONNECTION_CACHE. If a matching connection
|
|
104
|
+
does not already exist, creates a new connection and caches it.
|
|
105
|
+
"""
|
|
106
|
+
self.connection_context.validate_and_complete()
|
|
107
|
+
return self.connection_cache[self.connection_context]
|
|
333
108
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
109
|
+
def _definition_manager_or_raise(self) -> DefinitionManager:
|
|
110
|
+
"""
|
|
111
|
+
(Re-)parses project definition based on project args (project_path_arg and
|
|
112
|
+
project_env_overrides_args). If we cannot provide a project definition
|
|
113
|
+
(i.e. no snowflake.yml) and require one, raises MissingConfiguration.
|
|
114
|
+
"""
|
|
115
|
+
from snowflake.cli.api.project.definition_manager import DefinitionManager
|
|
337
116
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
117
|
+
# don't need to re-parse definition if we already have one
|
|
118
|
+
if not self._definition_manager:
|
|
119
|
+
dm = DefinitionManager(
|
|
120
|
+
self.project_path_arg,
|
|
121
|
+
{CONTEXT_KEY: {"env": self.project_env_overrides_args}},
|
|
122
|
+
)
|
|
123
|
+
if not dm.has_definition_file and not self.project_is_optional:
|
|
124
|
+
raise MissingConfiguration(
|
|
125
|
+
"Cannot find project definition (snowflake.yml). Please provide a path to the project or run this command in a valid project directory."
|
|
126
|
+
)
|
|
127
|
+
self._definition_manager = dm
|
|
341
128
|
|
|
342
|
-
|
|
343
|
-
def silent(self) -> bool:
|
|
344
|
-
return self._silent
|
|
129
|
+
return self._definition_manager
|
|
345
130
|
|
|
346
|
-
def
|
|
347
|
-
|
|
131
|
+
def _clear_definition_manager(self):
|
|
132
|
+
"""
|
|
133
|
+
Force re-calculation of definition_manager and its dependent attributes
|
|
134
|
+
(template_context, project_definition, and project_root).
|
|
135
|
+
"""
|
|
136
|
+
self._definition_manager = None
|
|
348
137
|
|
|
349
138
|
|
|
350
139
|
class _CliGlobalContextAccess:
|
|
@@ -356,7 +145,7 @@ class _CliGlobalContextAccess:
|
|
|
356
145
|
return self._manager.connection
|
|
357
146
|
|
|
358
147
|
@property
|
|
359
|
-
def connection_context(self) ->
|
|
148
|
+
def connection_context(self) -> ConnectionContext:
|
|
360
149
|
return self._manager.connection_context
|
|
361
150
|
|
|
362
151
|
@property
|
|
@@ -380,8 +169,8 @@ class _CliGlobalContextAccess:
|
|
|
380
169
|
return self._manager.project_definition
|
|
381
170
|
|
|
382
171
|
@property
|
|
383
|
-
def project_root(self) -> Path:
|
|
384
|
-
return
|
|
172
|
+
def project_root(self) -> Path | None:
|
|
173
|
+
return self._manager.project_root
|
|
385
174
|
|
|
386
175
|
@property
|
|
387
176
|
def template_context(self) -> dict:
|
|
@@ -399,19 +188,53 @@ class _CliGlobalContextAccess:
|
|
|
399
188
|
return self._manager.output_format == OutputFormat.JSON
|
|
400
189
|
|
|
401
190
|
|
|
402
|
-
_CLI_CONTEXT_MANAGER: _CliGlobalContextManager | None =
|
|
403
|
-
|
|
191
|
+
_CLI_CONTEXT_MANAGER: ContextVar[_CliGlobalContextManager | None] = ContextVar(
|
|
192
|
+
"cli_context", default=None
|
|
193
|
+
)
|
|
404
194
|
|
|
405
195
|
|
|
406
196
|
def get_cli_context_manager() -> _CliGlobalContextManager:
|
|
407
|
-
|
|
408
|
-
if
|
|
409
|
-
|
|
410
|
-
|
|
197
|
+
mgr = _CLI_CONTEXT_MANAGER.get()
|
|
198
|
+
if not mgr:
|
|
199
|
+
mgr = _CliGlobalContextManager()
|
|
200
|
+
_CLI_CONTEXT_MANAGER.set(mgr)
|
|
201
|
+
return mgr
|
|
411
202
|
|
|
412
203
|
|
|
413
204
|
def get_cli_context() -> _CliGlobalContextAccess:
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
205
|
+
return _CliGlobalContextAccess(get_cli_context_manager())
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@contextmanager
|
|
209
|
+
def fork_cli_context(
|
|
210
|
+
connection_overrides: dict | None = None,
|
|
211
|
+
project_env_overrides: dict[str, str] | None = None,
|
|
212
|
+
project_is_optional: bool | None = None,
|
|
213
|
+
project_path: str | None = None,
|
|
214
|
+
) -> Iterator[_CliGlobalContextAccess]:
|
|
215
|
+
"""
|
|
216
|
+
Forks the global CLI context, making changes that are only visible
|
|
217
|
+
(e.g. via get_cli_context()) while inside this context manager.
|
|
218
|
+
|
|
219
|
+
Please note that environment variable changes are only visible through
|
|
220
|
+
the project definition; os.getenv / os.environ / get_env_value are not
|
|
221
|
+
affected by these new values.
|
|
222
|
+
"""
|
|
223
|
+
old_manager = get_cli_context_manager()
|
|
224
|
+
new_manager = old_manager.clone()
|
|
225
|
+
token = _CLI_CONTEXT_MANAGER.set(new_manager)
|
|
226
|
+
|
|
227
|
+
if connection_overrides:
|
|
228
|
+
new_manager.connection_context.update(**connection_overrides)
|
|
229
|
+
|
|
230
|
+
if project_env_overrides:
|
|
231
|
+
new_manager.project_env_overrides_args.update(project_env_overrides)
|
|
232
|
+
|
|
233
|
+
if project_is_optional is not None:
|
|
234
|
+
new_manager.project_is_optional = project_is_optional
|
|
235
|
+
|
|
236
|
+
if project_path:
|
|
237
|
+
new_manager.project_path_arg = project_path
|
|
238
|
+
|
|
239
|
+
yield _CliGlobalContextAccess(new_manager)
|
|
240
|
+
_CLI_CONTEXT_MANAGER.reset(token)
|