snowflake-cli-labs 2.5.0rc3__py3-none-any.whl → 2.6.0__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/api/cli_global_context.py +31 -3
- snowflake/cli/api/commands/decorators.py +21 -6
- snowflake/cli/api/commands/flags.py +60 -51
- snowflake/cli/api/commands/snow_typer.py +24 -0
- snowflake/cli/api/commands/typer_pre_execute.py +26 -0
- snowflake/cli/api/console/abc.py +8 -0
- snowflake/cli/api/console/console.py +29 -4
- snowflake/cli/api/constants.py +3 -0
- snowflake/cli/api/project/definition.py +17 -35
- snowflake/cli/api/project/definition_manager.py +22 -19
- snowflake/cli/api/project/errors.py +9 -6
- snowflake/cli/api/project/schemas/identifier_model.py +1 -1
- snowflake/cli/api/project/schemas/native_app/application.py +15 -3
- snowflake/cli/api/project/schemas/native_app/native_app.py +5 -1
- snowflake/cli/api/project/schemas/native_app/path_mapping.py +14 -3
- snowflake/cli/api/project/schemas/project_definition.py +37 -6
- snowflake/cli/api/project/schemas/streamlit/streamlit.py +3 -0
- snowflake/cli/api/project/schemas/updatable_model.py +2 -6
- snowflake/cli/api/rest_api.py +113 -0
- snowflake/cli/api/sanitizers.py +43 -0
- snowflake/cli/api/sql_execution.py +7 -0
- snowflake/cli/api/utils/definition_rendering.py +95 -25
- snowflake/cli/api/utils/models.py +31 -26
- snowflake/cli/api/utils/rendering.py +24 -3
- snowflake/cli/app/cli_app.py +2 -0
- snowflake/cli/app/commands_registration/command_plugins_loader.py +8 -0
- snowflake/cli/app/dev/docs/commands_docs_generator.py +100 -0
- snowflake/cli/app/dev/docs/generator.py +8 -67
- snowflake/cli/app/dev/docs/project_definition_docs_generator.py +58 -0
- snowflake/cli/app/dev/docs/project_definition_generate_json_schema.py +227 -0
- snowflake/cli/app/dev/docs/template_utils.py +23 -0
- snowflake/cli/app/dev/docs/templates/definition_description.rst.jinja2 +38 -0
- snowflake/cli/app/dev/docs/templates/usage.rst.jinja2 +6 -1
- snowflake/cli/app/loggers.py +25 -0
- snowflake/cli/app/printing.py +7 -5
- snowflake/cli/app/telemetry.py +11 -0
- snowflake/cli/plugins/nativeapp/artifacts.py +78 -9
- snowflake/cli/plugins/nativeapp/codegen/artifact_processor.py +3 -11
- snowflake/cli/plugins/nativeapp/codegen/compiler.py +6 -24
- snowflake/cli/plugins/nativeapp/codegen/snowpark/python_processor.py +27 -27
- snowflake/cli/plugins/nativeapp/commands.py +23 -12
- snowflake/cli/plugins/nativeapp/constants.py +2 -0
- snowflake/cli/plugins/nativeapp/errno.py +15 -0
- snowflake/cli/plugins/nativeapp/feature_flags.py +24 -0
- snowflake/cli/plugins/nativeapp/init.py +5 -0
- snowflake/cli/plugins/nativeapp/manager.py +101 -103
- snowflake/cli/plugins/nativeapp/project_model.py +181 -0
- snowflake/cli/plugins/nativeapp/run_processor.py +178 -110
- snowflake/cli/plugins/nativeapp/teardown_processor.py +89 -64
- snowflake/cli/plugins/nativeapp/utils.py +2 -2
- snowflake/cli/plugins/nativeapp/version/commands.py +3 -3
- snowflake/cli/plugins/object/commands.py +70 -4
- snowflake/cli/plugins/object/manager.py +44 -3
- snowflake/cli/plugins/snowpark/commands.py +2 -2
- snowflake/cli/plugins/sql/commands.py +2 -10
- snowflake/cli/plugins/sql/manager.py +4 -2
- snowflake/cli/plugins/stage/commands.py +23 -4
- snowflake/cli/plugins/stage/diff.py +81 -51
- snowflake/cli/plugins/stage/manager.py +2 -1
- snowflake/cli/plugins/streamlit/commands.py +2 -1
- snowflake/cli/plugins/streamlit/manager.py +6 -0
- {snowflake_cli_labs-2.5.0rc3.dist-info → snowflake_cli_labs-2.6.0.dist-info}/METADATA +15 -9
- {snowflake_cli_labs-2.5.0rc3.dist-info → snowflake_cli_labs-2.6.0.dist-info}/RECORD +67 -56
- {snowflake_cli_labs-2.5.0rc3.dist-info → snowflake_cli_labs-2.6.0.dist-info}/WHEEL +1 -1
- {snowflake_cli_labs-2.5.0rc3.dist-info → snowflake_cli_labs-2.6.0.dist-info}/entry_points.txt +0 -0
- {snowflake_cli_labs-2.5.0rc3.dist-info → snowflake_cli_labs-2.6.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Copyright (c) 2024 Snowflake Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from enum import unique
|
|
16
|
+
|
|
17
|
+
from snowflake.cli.api.feature_flags import BooleanFlag, FeatureFlagMixin
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@unique
|
|
21
|
+
class FeatureFlag(FeatureFlagMixin):
|
|
22
|
+
ENABLE_NATIVE_APP_PYTHON_SETUP = BooleanFlag(
|
|
23
|
+
"ENABLE_NATIVE_APP_PYTHON_SETUP", False
|
|
24
|
+
)
|
|
@@ -21,6 +21,7 @@ from typing import Optional
|
|
|
21
21
|
|
|
22
22
|
from click.exceptions import ClickException
|
|
23
23
|
from snowflake.cli.api.constants import DEFAULT_SIZE_LIMIT_MB
|
|
24
|
+
from snowflake.cli.api.exceptions import MissingConfiguration
|
|
24
25
|
from snowflake.cli.api.project.definition_manager import DefinitionManager
|
|
25
26
|
from snowflake.cli.api.project.util import (
|
|
26
27
|
is_valid_identifier,
|
|
@@ -197,6 +198,10 @@ def _validate_and_update_snowflake_yml(target_directory: Path, project_identifie
|
|
|
197
198
|
"""
|
|
198
199
|
# 1. Determine if a snowflake.yml file exists, at the very least
|
|
199
200
|
definition_manager = DefinitionManager(target_directory)
|
|
201
|
+
if not definition_manager.has_definition_file:
|
|
202
|
+
raise MissingConfiguration(
|
|
203
|
+
"Cannot find project definition (snowflake.yml). Please provide a path to the project or run this command in a valid project directory."
|
|
204
|
+
)
|
|
200
205
|
|
|
201
206
|
# 2. Change the project name in snowflake.yml if necessary
|
|
202
207
|
_replace_snowflake_yml_name_with_project(
|
|
@@ -26,17 +26,13 @@ import jinja2
|
|
|
26
26
|
from click import ClickException
|
|
27
27
|
from snowflake.cli.api.console import cli_console as cc
|
|
28
28
|
from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
|
|
29
|
-
from snowflake.cli.api.project.
|
|
30
|
-
|
|
31
|
-
default_application,
|
|
32
|
-
default_role,
|
|
29
|
+
from snowflake.cli.api.project.schemas.native_app.application import (
|
|
30
|
+
ApplicationPostDeployHook,
|
|
33
31
|
)
|
|
34
32
|
from snowflake.cli.api.project.schemas.native_app.native_app import NativeApp
|
|
35
33
|
from snowflake.cli.api.project.schemas.native_app.path_mapping import PathMapping
|
|
36
34
|
from snowflake.cli.api.project.util import (
|
|
37
|
-
extract_schema,
|
|
38
35
|
identifier_for_url,
|
|
39
|
-
to_identifier,
|
|
40
36
|
unquote_identifier,
|
|
41
37
|
)
|
|
42
38
|
from snowflake.cli.api.sql_execution import SqlExecutionMixin
|
|
@@ -68,18 +64,21 @@ from snowflake.cli.plugins.nativeapp.exceptions import (
|
|
|
68
64
|
SetupScriptFailedValidation,
|
|
69
65
|
UnexpectedOwnerError,
|
|
70
66
|
)
|
|
67
|
+
from snowflake.cli.plugins.nativeapp.project_model import (
|
|
68
|
+
NativeAppProjectModel,
|
|
69
|
+
)
|
|
71
70
|
from snowflake.cli.plugins.nativeapp.utils import verify_exists, verify_no_directories
|
|
72
71
|
from snowflake.cli.plugins.stage.diff import (
|
|
73
72
|
DiffResult,
|
|
74
73
|
StagePath,
|
|
75
74
|
compute_stage_diff,
|
|
76
75
|
preserve_from_diff,
|
|
76
|
+
print_diff_to_console,
|
|
77
77
|
sync_local_diff_with_stage,
|
|
78
78
|
to_stage_path,
|
|
79
79
|
)
|
|
80
80
|
from snowflake.cli.plugins.stage.manager import StageManager
|
|
81
81
|
from snowflake.connector import ProgrammingError
|
|
82
|
-
from snowflake.connector.cursor import DictCursor
|
|
83
82
|
|
|
84
83
|
ApplicationOwnedObject = TypedDict("ApplicationOwnedObject", {"name": str, "type": str})
|
|
85
84
|
|
|
@@ -164,125 +163,94 @@ class NativeAppManager(SqlExecutionMixin):
|
|
|
164
163
|
|
|
165
164
|
def __init__(self, project_definition: NativeApp, project_root: Path):
|
|
166
165
|
super().__init__()
|
|
167
|
-
self.
|
|
168
|
-
|
|
166
|
+
self._na_project = NativeAppProjectModel(
|
|
167
|
+
project_definition=project_definition,
|
|
168
|
+
project_root=project_root,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def na_project(self) -> NativeAppProjectModel:
|
|
173
|
+
return self._na_project
|
|
169
174
|
|
|
170
175
|
@property
|
|
171
176
|
def project_root(self) -> Path:
|
|
172
|
-
return self.
|
|
177
|
+
return self.na_project.project_root
|
|
173
178
|
|
|
174
179
|
@property
|
|
175
180
|
def definition(self) -> NativeApp:
|
|
176
|
-
return self.
|
|
181
|
+
return self.na_project.definition
|
|
177
182
|
|
|
178
|
-
@
|
|
183
|
+
@property
|
|
179
184
|
def artifacts(self) -> List[PathMapping]:
|
|
180
|
-
return self.
|
|
185
|
+
return self.na_project.artifacts
|
|
181
186
|
|
|
182
|
-
@
|
|
187
|
+
@property
|
|
188
|
+
def bundle_root(self) -> Path:
|
|
189
|
+
return self.na_project.bundle_root
|
|
190
|
+
|
|
191
|
+
@property
|
|
183
192
|
def deploy_root(self) -> Path:
|
|
184
|
-
return
|
|
193
|
+
return self.na_project.deploy_root
|
|
185
194
|
|
|
186
|
-
@
|
|
195
|
+
@property
|
|
187
196
|
def generated_root(self) -> Path:
|
|
188
|
-
return
|
|
197
|
+
return self.na_project.generated_root
|
|
189
198
|
|
|
190
|
-
@
|
|
199
|
+
@property
|
|
191
200
|
def package_scripts(self) -> List[str]:
|
|
192
|
-
|
|
193
|
-
Relative paths to package scripts from the project root.
|
|
194
|
-
"""
|
|
195
|
-
if self.definition.package and self.definition.package.scripts:
|
|
196
|
-
return self.definition.package.scripts
|
|
197
|
-
else:
|
|
198
|
-
return []
|
|
201
|
+
return self.na_project.package_scripts
|
|
199
202
|
|
|
200
|
-
@
|
|
203
|
+
@property
|
|
201
204
|
def stage_fqn(self) -> str:
|
|
202
|
-
return
|
|
205
|
+
return self.na_project.stage_fqn
|
|
203
206
|
|
|
204
|
-
@
|
|
207
|
+
@property
|
|
205
208
|
def scratch_stage_fqn(self) -> str:
|
|
206
|
-
return
|
|
209
|
+
return self.na_project.scratch_stage_fqn
|
|
207
210
|
|
|
208
|
-
@
|
|
211
|
+
@property
|
|
209
212
|
def stage_schema(self) -> Optional[str]:
|
|
210
|
-
return
|
|
213
|
+
return self.na_project.stage_schema
|
|
211
214
|
|
|
212
|
-
@
|
|
215
|
+
@property
|
|
213
216
|
def package_warehouse(self) -> Optional[str]:
|
|
214
|
-
|
|
215
|
-
return self.definition.package.warehouse
|
|
216
|
-
else:
|
|
217
|
-
return self._conn.warehouse
|
|
217
|
+
return self.na_project.package_warehouse
|
|
218
218
|
|
|
219
|
-
@
|
|
219
|
+
@property
|
|
220
220
|
def application_warehouse(self) -> Optional[str]:
|
|
221
|
-
|
|
222
|
-
return self.definition.application.warehouse
|
|
223
|
-
else:
|
|
224
|
-
return self._conn.warehouse
|
|
221
|
+
return self.na_project.application_warehouse
|
|
225
222
|
|
|
226
|
-
@
|
|
223
|
+
@property
|
|
227
224
|
def project_identifier(self) -> str:
|
|
228
|
-
|
|
229
|
-
# will sometimes strip out double quotes so we try to get them back here.
|
|
230
|
-
return to_identifier(self.definition.name)
|
|
225
|
+
return self.na_project.project_identifier
|
|
231
226
|
|
|
232
|
-
@
|
|
227
|
+
@property
|
|
233
228
|
def package_name(self) -> str:
|
|
234
|
-
|
|
235
|
-
return to_identifier(self.definition.package.name)
|
|
236
|
-
else:
|
|
237
|
-
return to_identifier(default_app_package(self.project_identifier))
|
|
229
|
+
return self.na_project.package_name
|
|
238
230
|
|
|
239
|
-
@
|
|
231
|
+
@property
|
|
240
232
|
def package_role(self) -> str:
|
|
241
|
-
|
|
242
|
-
return self.definition.package.role
|
|
243
|
-
else:
|
|
244
|
-
return self._default_role
|
|
233
|
+
return self.na_project.package_role
|
|
245
234
|
|
|
246
|
-
@
|
|
235
|
+
@property
|
|
247
236
|
def package_distribution(self) -> str:
|
|
248
|
-
|
|
249
|
-
return self.definition.package.distribution.lower()
|
|
250
|
-
else:
|
|
251
|
-
return "internal"
|
|
237
|
+
return self.na_project.package_distribution
|
|
252
238
|
|
|
253
|
-
@
|
|
239
|
+
@property
|
|
254
240
|
def app_name(self) -> str:
|
|
255
|
-
|
|
256
|
-
return to_identifier(self.definition.application.name)
|
|
257
|
-
else:
|
|
258
|
-
return to_identifier(default_application(self.project_identifier))
|
|
241
|
+
return self.na_project.app_name
|
|
259
242
|
|
|
260
|
-
@
|
|
243
|
+
@property
|
|
261
244
|
def app_role(self) -> str:
|
|
262
|
-
|
|
263
|
-
return self.definition.application.role
|
|
264
|
-
else:
|
|
265
|
-
return self._default_role
|
|
245
|
+
return self.na_project.app_role
|
|
266
246
|
|
|
267
|
-
@
|
|
268
|
-
def
|
|
269
|
-
|
|
270
|
-
if role is None:
|
|
271
|
-
role = self._get_current_role()
|
|
272
|
-
return role
|
|
273
|
-
|
|
274
|
-
def _get_current_role(self) -> str:
|
|
275
|
-
role_result = self._execute_query(
|
|
276
|
-
"select current_role()", cursor_class=DictCursor
|
|
277
|
-
).fetchone()
|
|
278
|
-
return role_result["CURRENT_ROLE()"]
|
|
247
|
+
@property
|
|
248
|
+
def app_post_deploy_hooks(self) -> Optional[List[ApplicationPostDeployHook]]:
|
|
249
|
+
return self.na_project.app_post_deploy_hooks
|
|
279
250
|
|
|
280
|
-
@
|
|
251
|
+
@property
|
|
281
252
|
def debug_mode(self) -> bool:
|
|
282
|
-
|
|
283
|
-
return self.definition.application.debug
|
|
284
|
-
else:
|
|
285
|
-
return True
|
|
253
|
+
return self.na_project.debug_mode
|
|
286
254
|
|
|
287
255
|
@cached_property
|
|
288
256
|
def get_app_pkg_distribution_in_snowflake(self) -> str:
|
|
@@ -341,15 +309,12 @@ class NativeAppManager(SqlExecutionMixin):
|
|
|
341
309
|
"""
|
|
342
310
|
Populates the local deploy root from artifact sources.
|
|
343
311
|
"""
|
|
344
|
-
|
|
312
|
+
bundle_map = build_bundle(self.project_root, self.deploy_root, self.artifacts)
|
|
345
313
|
compiler = NativeAppCompiler(
|
|
346
|
-
|
|
347
|
-
project_root=self.project_root,
|
|
348
|
-
deploy_root=self.deploy_root,
|
|
349
|
-
generated_root=self.generated_root,
|
|
314
|
+
na_project=self.na_project,
|
|
350
315
|
)
|
|
351
316
|
compiler.compile_artifacts()
|
|
352
|
-
return
|
|
317
|
+
return bundle_map
|
|
353
318
|
|
|
354
319
|
def sync_deploy_root_with_stage(
|
|
355
320
|
self,
|
|
@@ -359,6 +324,7 @@ class NativeAppManager(SqlExecutionMixin):
|
|
|
359
324
|
recursive: bool,
|
|
360
325
|
stage_fqn: str,
|
|
361
326
|
local_paths_to_sync: List[Path] | None = None,
|
|
327
|
+
print_diff: bool = True,
|
|
362
328
|
) -> DiffResult:
|
|
363
329
|
"""
|
|
364
330
|
Ensures that the files on our remote stage match the artifacts we have in
|
|
@@ -372,6 +338,7 @@ class NativeAppManager(SqlExecutionMixin):
|
|
|
372
338
|
stage_fqn (str): The name of the stage to diff against and upload to.
|
|
373
339
|
local_paths_to_sync (List[Path], optional): List of local paths to sync. Defaults to None to sync all
|
|
374
340
|
local paths. Note that providing an empty list here is equivalent to None.
|
|
341
|
+
print_diff (bool): Whether to print the diff between the local files and the remote stage. Defaults to True
|
|
375
342
|
|
|
376
343
|
Returns:
|
|
377
344
|
A `DiffResult` instance describing the changes that were performed.
|
|
@@ -394,13 +361,13 @@ class NativeAppManager(SqlExecutionMixin):
|
|
|
394
361
|
)
|
|
395
362
|
|
|
396
363
|
# Perform a diff operation and display results to the user for informational purposes
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
364
|
+
if print_diff:
|
|
365
|
+
cc.step(
|
|
366
|
+
"Performing a diff between the Snowflake stage and your local deploy_root ('%s') directory."
|
|
367
|
+
% self.deploy_root.resolve()
|
|
368
|
+
)
|
|
401
369
|
diff: DiffResult = compute_stage_diff(self.deploy_root, stage_fqn)
|
|
402
370
|
|
|
403
|
-
files_not_removed = []
|
|
404
371
|
if local_paths_to_sync:
|
|
405
372
|
# Deploying specific files/directories
|
|
406
373
|
resolved_paths_to_sync = [
|
|
@@ -413,6 +380,17 @@ class NativeAppManager(SqlExecutionMixin):
|
|
|
413
380
|
for resolved_path in resolved_paths_to_sync:
|
|
414
381
|
verify_exists(resolved_path)
|
|
415
382
|
deploy_paths = bundle_map.to_deploy_paths(resolved_path)
|
|
383
|
+
if not deploy_paths:
|
|
384
|
+
if resolved_path.is_dir() and recursive:
|
|
385
|
+
# No direct artifact mapping found for this path. Check to see
|
|
386
|
+
# if there are subpaths of this directory that are matches. We
|
|
387
|
+
# loop over sources because it's likely a much smaller list
|
|
388
|
+
# than the project directory.
|
|
389
|
+
for src in bundle_map.all_sources(absolute=True):
|
|
390
|
+
if resolved_path in src.parents:
|
|
391
|
+
# There is a source that contains this path, get its dest path(s)
|
|
392
|
+
deploy_paths.extend(bundle_map.to_deploy_paths(src))
|
|
393
|
+
|
|
416
394
|
if not deploy_paths:
|
|
417
395
|
raise ClickException(f"No artifact found for {resolved_path}")
|
|
418
396
|
deploy_paths_to_sync.extend(deploy_paths)
|
|
@@ -424,8 +402,7 @@ class NativeAppManager(SqlExecutionMixin):
|
|
|
424
402
|
else:
|
|
425
403
|
# Full deploy
|
|
426
404
|
if not recursive:
|
|
427
|
-
|
|
428
|
-
verify_no_directories(deploy_files)
|
|
405
|
+
verify_no_directories(self.deploy_root.resolve().iterdir())
|
|
429
406
|
|
|
430
407
|
if not prune:
|
|
431
408
|
files_not_removed = [str(path) for path in diff.only_on_stage]
|
|
@@ -437,13 +414,14 @@ class NativeAppManager(SqlExecutionMixin):
|
|
|
437
414
|
f"The following files exist only on the stage:\n{files_not_removed_str}\n\nUse the --prune flag to delete them from the stage."
|
|
438
415
|
)
|
|
439
416
|
|
|
440
|
-
|
|
417
|
+
if print_diff:
|
|
418
|
+
print_diff_to_console(diff, bundle_map)
|
|
441
419
|
|
|
442
420
|
# Upload diff-ed files to application package stage
|
|
443
421
|
if diff.has_changes():
|
|
444
422
|
cc.step(
|
|
445
|
-
"
|
|
446
|
-
% self.deploy_root,
|
|
423
|
+
"Updating the Snowflake stage from your local %s directory."
|
|
424
|
+
% self.deploy_root.resolve(),
|
|
447
425
|
)
|
|
448
426
|
sync_local_diff_with_stage(
|
|
449
427
|
role=role,
|
|
@@ -484,6 +462,23 @@ class NativeAppManager(SqlExecutionMixin):
|
|
|
484
462
|
).fetchall()
|
|
485
463
|
return [{"name": row[1], "type": row[2]} for row in results]
|
|
486
464
|
|
|
465
|
+
def _application_objects_to_str(
|
|
466
|
+
self, application_objects: list[ApplicationOwnedObject]
|
|
467
|
+
) -> str:
|
|
468
|
+
"""
|
|
469
|
+
Returns a list in an "(Object Type) Object Name" format. Database-level and schema-level object names are fully qualified:
|
|
470
|
+
(COMPUTE_POOL) POOL_NAME
|
|
471
|
+
(DATABASE) DB_NAME
|
|
472
|
+
(SCHEMA) DB_NAME.PUBLIC
|
|
473
|
+
...
|
|
474
|
+
"""
|
|
475
|
+
return "\n".join(
|
|
476
|
+
[self._application_object_to_str(obj) for obj in application_objects]
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
def _application_object_to_str(self, obj: ApplicationOwnedObject) -> str:
|
|
480
|
+
return f"({obj['type']}) {obj['name']}"
|
|
481
|
+
|
|
487
482
|
def get_snowsight_url(self) -> str:
|
|
488
483
|
"""Returns the URL that can be used to visit this app via Snowsight."""
|
|
489
484
|
name = identifier_for_url(self.app_name)
|
|
@@ -580,6 +575,7 @@ class NativeAppManager(SqlExecutionMixin):
|
|
|
580
575
|
stage_fqn: Optional[str] = None,
|
|
581
576
|
local_paths_to_sync: List[Path] | None = None,
|
|
582
577
|
validate: bool = True,
|
|
578
|
+
print_diff: bool = True,
|
|
583
579
|
) -> DiffResult:
|
|
584
580
|
"""app deploy process"""
|
|
585
581
|
|
|
@@ -599,6 +595,7 @@ class NativeAppManager(SqlExecutionMixin):
|
|
|
599
595
|
recursive=recursive,
|
|
600
596
|
stage_fqn=stage_fqn,
|
|
601
597
|
local_paths_to_sync=local_paths_to_sync,
|
|
598
|
+
print_diff=print_diff,
|
|
602
599
|
)
|
|
603
600
|
|
|
604
601
|
if validate:
|
|
@@ -637,6 +634,7 @@ class NativeAppManager(SqlExecutionMixin):
|
|
|
637
634
|
recursive=True,
|
|
638
635
|
stage_fqn=stage_fqn,
|
|
639
636
|
validate=False,
|
|
637
|
+
print_diff=False,
|
|
640
638
|
)
|
|
641
639
|
prefixed_stage_fqn = StageManager.get_standard_stage_prefix(stage_fqn)
|
|
642
640
|
try:
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# Copyright (c) 2024 Snowflake Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from functools import cached_property
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import List, Optional
|
|
20
|
+
|
|
21
|
+
from snowflake.cli.api.cli_global_context import cli_context
|
|
22
|
+
from snowflake.cli.api.project.definition import (
|
|
23
|
+
default_app_package,
|
|
24
|
+
default_application,
|
|
25
|
+
default_role,
|
|
26
|
+
)
|
|
27
|
+
from snowflake.cli.api.project.schemas.native_app.application import (
|
|
28
|
+
ApplicationPostDeployHook,
|
|
29
|
+
)
|
|
30
|
+
from snowflake.cli.api.project.schemas.native_app.native_app import NativeApp
|
|
31
|
+
from snowflake.cli.api.project.schemas.native_app.path_mapping import PathMapping
|
|
32
|
+
from snowflake.cli.api.project.util import extract_schema, to_identifier
|
|
33
|
+
from snowflake.cli.plugins.nativeapp.artifacts import resolve_without_follow
|
|
34
|
+
from snowflake.connector import DictCursor
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def current_role() -> str:
|
|
38
|
+
conn = cli_context.connection
|
|
39
|
+
*_, cursor = conn.execute_string("select current_role()", cursor_class=DictCursor)
|
|
40
|
+
role_result = cursor.fetchone()
|
|
41
|
+
return role_result["CURRENT_ROLE()"]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class NativeAppProjectModel:
|
|
45
|
+
"""
|
|
46
|
+
Exposes properties of a native app project defined in a Snowflake Project Definition file. Whenever
|
|
47
|
+
appropriate, APIs defined in this class provide suitable defaults or fallback logic to access properties
|
|
48
|
+
of the project.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
project_definition: NativeApp,
|
|
54
|
+
project_root: Path,
|
|
55
|
+
):
|
|
56
|
+
self._project_definition = project_definition
|
|
57
|
+
self._project_root = resolve_without_follow(project_root)
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def project_root(self) -> Path:
|
|
61
|
+
return self._project_root
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def definition(self) -> NativeApp:
|
|
65
|
+
return self._project_definition
|
|
66
|
+
|
|
67
|
+
@cached_property
|
|
68
|
+
def artifacts(self) -> List[PathMapping]:
|
|
69
|
+
return self.definition.artifacts
|
|
70
|
+
|
|
71
|
+
@cached_property
|
|
72
|
+
def bundle_root(self) -> Path:
|
|
73
|
+
return self.project_root / self.definition.bundle_root
|
|
74
|
+
|
|
75
|
+
@cached_property
|
|
76
|
+
def deploy_root(self) -> Path:
|
|
77
|
+
return self.project_root / self.definition.deploy_root
|
|
78
|
+
|
|
79
|
+
@cached_property
|
|
80
|
+
def generated_root(self) -> Path:
|
|
81
|
+
return self.deploy_root / self.definition.generated_root
|
|
82
|
+
|
|
83
|
+
@cached_property
|
|
84
|
+
def package_scripts(self) -> List[str]:
|
|
85
|
+
"""
|
|
86
|
+
Relative paths to package scripts from the project root.
|
|
87
|
+
"""
|
|
88
|
+
if self.definition.package and self.definition.package.scripts:
|
|
89
|
+
return self.definition.package.scripts
|
|
90
|
+
else:
|
|
91
|
+
return []
|
|
92
|
+
|
|
93
|
+
@cached_property
|
|
94
|
+
def stage_fqn(self) -> str:
|
|
95
|
+
return f"{self.package_name}.{self.definition.source_stage}"
|
|
96
|
+
|
|
97
|
+
@cached_property
|
|
98
|
+
def scratch_stage_fqn(self) -> str:
|
|
99
|
+
return f"{self.package_name}.{self.definition.scratch_stage}"
|
|
100
|
+
|
|
101
|
+
@cached_property
|
|
102
|
+
def stage_schema(self) -> Optional[str]:
|
|
103
|
+
return extract_schema(self.stage_fqn)
|
|
104
|
+
|
|
105
|
+
@cached_property
|
|
106
|
+
def package_warehouse(self) -> Optional[str]:
|
|
107
|
+
if self.definition.package and self.definition.package.warehouse:
|
|
108
|
+
return self.definition.package.warehouse
|
|
109
|
+
else:
|
|
110
|
+
return cli_context.connection.warehouse
|
|
111
|
+
|
|
112
|
+
@cached_property
|
|
113
|
+
def application_warehouse(self) -> Optional[str]:
|
|
114
|
+
if self.definition.application and self.definition.application.warehouse:
|
|
115
|
+
return self.definition.application.warehouse
|
|
116
|
+
else:
|
|
117
|
+
return cli_context.connection.warehouse
|
|
118
|
+
|
|
119
|
+
@cached_property
|
|
120
|
+
def project_identifier(self) -> str:
|
|
121
|
+
# name is expected to be a valid Snowflake identifier, but YAML parsers will
|
|
122
|
+
# sometimes strip out double quotes, so we try to get them back here.
|
|
123
|
+
return to_identifier(self.definition.name)
|
|
124
|
+
|
|
125
|
+
@cached_property
|
|
126
|
+
def package_name(self) -> str:
|
|
127
|
+
if self.definition.package and self.definition.package.name:
|
|
128
|
+
return to_identifier(self.definition.package.name)
|
|
129
|
+
else:
|
|
130
|
+
return to_identifier(default_app_package(self.project_identifier))
|
|
131
|
+
|
|
132
|
+
@cached_property
|
|
133
|
+
def package_role(self) -> str:
|
|
134
|
+
if self.definition.package and self.definition.package.role:
|
|
135
|
+
return self.definition.package.role
|
|
136
|
+
else:
|
|
137
|
+
return self._default_role
|
|
138
|
+
|
|
139
|
+
@cached_property
|
|
140
|
+
def package_distribution(self) -> str:
|
|
141
|
+
if self.definition.package and self.definition.package.distribution:
|
|
142
|
+
return self.definition.package.distribution.lower()
|
|
143
|
+
else:
|
|
144
|
+
return "internal"
|
|
145
|
+
|
|
146
|
+
@cached_property
|
|
147
|
+
def app_name(self) -> str:
|
|
148
|
+
if self.definition.application and self.definition.application.name:
|
|
149
|
+
return to_identifier(self.definition.application.name)
|
|
150
|
+
else:
|
|
151
|
+
return to_identifier(default_application(self.project_identifier))
|
|
152
|
+
|
|
153
|
+
@cached_property
|
|
154
|
+
def app_role(self) -> str:
|
|
155
|
+
if self.definition.application and self.definition.application.role:
|
|
156
|
+
return self.definition.application.role
|
|
157
|
+
else:
|
|
158
|
+
return self._default_role
|
|
159
|
+
|
|
160
|
+
@cached_property
|
|
161
|
+
def app_post_deploy_hooks(self) -> Optional[List[ApplicationPostDeployHook]]:
|
|
162
|
+
"""
|
|
163
|
+
List of application post deploy hooks.
|
|
164
|
+
"""
|
|
165
|
+
if self.definition.application and self.definition.application.post_deploy:
|
|
166
|
+
return self.definition.application.post_deploy
|
|
167
|
+
else:
|
|
168
|
+
return None
|
|
169
|
+
|
|
170
|
+
@cached_property
|
|
171
|
+
def _default_role(self) -> str:
|
|
172
|
+
role = default_role()
|
|
173
|
+
if role is None:
|
|
174
|
+
role = current_role()
|
|
175
|
+
return role
|
|
176
|
+
|
|
177
|
+
@cached_property
|
|
178
|
+
def debug_mode(self) -> Optional[bool]:
|
|
179
|
+
if self.definition.application:
|
|
180
|
+
return self.definition.application.debug
|
|
181
|
+
return None
|