snowflake-cli-labs 2.3.1__py3-none-any.whl → 2.4.0rc1__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/__init__.py +2 -0
- snowflake/cli/api/cli_global_context.py +8 -1
- snowflake/cli/api/commands/decorators.py +2 -2
- snowflake/cli/api/commands/flags.py +49 -4
- snowflake/cli/api/commands/snow_typer.py +2 -0
- snowflake/cli/api/console/abc.py +2 -0
- snowflake/cli/api/console/console.py +6 -5
- snowflake/cli/api/constants.py +5 -0
- snowflake/cli/api/exceptions.py +12 -0
- snowflake/cli/api/identifiers.py +123 -0
- snowflake/cli/api/plugins/command/__init__.py +2 -0
- snowflake/cli/api/plugins/plugin_config.py +2 -0
- snowflake/cli/api/project/definition.py +2 -0
- snowflake/cli/api/project/errors.py +3 -3
- snowflake/cli/api/project/schemas/identifier_model.py +35 -0
- snowflake/cli/api/project/schemas/native_app/native_app.py +4 -0
- snowflake/cli/api/project/schemas/native_app/path_mapping.py +21 -3
- snowflake/cli/api/project/schemas/project_definition.py +58 -6
- snowflake/cli/api/project/schemas/snowpark/argument.py +2 -0
- snowflake/cli/api/project/schemas/snowpark/callable.py +8 -17
- snowflake/cli/api/project/schemas/streamlit/streamlit.py +2 -2
- snowflake/cli/api/project/schemas/updatable_model.py +2 -0
- snowflake/cli/api/project/util.py +2 -0
- snowflake/cli/api/secure_path.py +2 -0
- snowflake/cli/api/sql_execution.py +14 -54
- snowflake/cli/api/utils/cursor.py +2 -0
- snowflake/cli/api/utils/models.py +23 -0
- snowflake/cli/api/utils/naming_utils.py +0 -27
- snowflake/cli/api/utils/rendering.py +178 -23
- snowflake/cli/app/api_impl/plugin/plugin_config_provider_impl.py +2 -0
- snowflake/cli/app/cli_app.py +4 -1
- snowflake/cli/app/commands_registration/builtin_plugins.py +8 -0
- snowflake/cli/app/commands_registration/command_plugins_loader.py +2 -0
- snowflake/cli/app/commands_registration/commands_registration_with_callbacks.py +2 -0
- snowflake/cli/app/commands_registration/typer_registration.py +2 -0
- snowflake/cli/app/dev/pycharm_remote_debug.py +2 -0
- snowflake/cli/app/loggers.py +2 -0
- snowflake/cli/app/main_typer.py +1 -1
- snowflake/cli/app/printing.py +3 -1
- snowflake/cli/app/snow_connector.py +2 -2
- snowflake/cli/plugins/connection/commands.py +5 -14
- snowflake/cli/plugins/connection/util.py +1 -1
- snowflake/cli/plugins/cortex/__init__.py +0 -0
- snowflake/cli/plugins/cortex/commands.py +312 -0
- snowflake/cli/plugins/cortex/constants.py +3 -0
- snowflake/cli/plugins/cortex/manager.py +175 -0
- snowflake/cli/plugins/cortex/plugin_spec.py +16 -0
- snowflake/cli/plugins/cortex/types.py +8 -0
- snowflake/cli/plugins/git/commands.py +15 -0
- snowflake/cli/plugins/nativeapp/artifacts.py +368 -123
- snowflake/cli/plugins/nativeapp/codegen/artifact_processor.py +45 -0
- snowflake/cli/plugins/nativeapp/codegen/compiler.py +104 -0
- snowflake/cli/plugins/nativeapp/codegen/sandbox.py +2 -0
- snowflake/cli/plugins/nativeapp/codegen/snowpark/callback_source.py.jinja +181 -0
- snowflake/cli/plugins/nativeapp/codegen/snowpark/extension_function_utils.py +196 -0
- snowflake/cli/plugins/nativeapp/codegen/snowpark/models.py +47 -0
- snowflake/cli/plugins/nativeapp/codegen/snowpark/python_processor.py +489 -0
- snowflake/cli/plugins/nativeapp/commands.py +11 -4
- snowflake/cli/plugins/nativeapp/common_flags.py +12 -5
- snowflake/cli/plugins/nativeapp/manager.py +49 -16
- snowflake/cli/plugins/nativeapp/policy.py +2 -0
- snowflake/cli/plugins/nativeapp/run_processor.py +2 -0
- snowflake/cli/plugins/nativeapp/teardown_processor.py +80 -8
- snowflake/cli/plugins/nativeapp/utils.py +7 -6
- snowflake/cli/plugins/nativeapp/version/commands.py +6 -5
- snowflake/cli/plugins/nativeapp/version/version_processor.py +2 -0
- snowflake/cli/plugins/notebook/commands.py +21 -0
- snowflake/cli/plugins/notebook/exceptions.py +6 -0
- snowflake/cli/plugins/notebook/manager.py +46 -3
- snowflake/cli/plugins/notebook/types.py +2 -0
- snowflake/cli/plugins/object/command_aliases.py +80 -0
- snowflake/cli/plugins/object/commands.py +10 -6
- snowflake/cli/plugins/object/common.py +2 -0
- snowflake/cli/plugins/object_stage_deprecated/__init__.py +1 -0
- snowflake/cli/plugins/object_stage_deprecated/plugin_spec.py +20 -0
- snowflake/cli/plugins/snowpark/commands.py +62 -6
- snowflake/cli/plugins/snowpark/common.py +17 -6
- snowflake/cli/plugins/spcs/compute_pool/commands.py +22 -1
- snowflake/cli/plugins/spcs/compute_pool/manager.py +2 -0
- snowflake/cli/plugins/spcs/image_repository/commands.py +25 -1
- snowflake/cli/plugins/spcs/image_repository/manager.py +3 -1
- snowflake/cli/plugins/spcs/services/commands.py +39 -5
- snowflake/cli/plugins/spcs/services/manager.py +2 -0
- snowflake/cli/plugins/sql/commands.py +13 -5
- snowflake/cli/plugins/sql/manager.py +40 -19
- snowflake/cli/plugins/stage/commands.py +29 -3
- snowflake/cli/plugins/stage/diff.py +2 -0
- snowflake/cli/plugins/streamlit/commands.py +26 -10
- snowflake/cli/plugins/streamlit/manager.py +9 -10
- {snowflake_cli_labs-2.3.1.dist-info → snowflake_cli_labs-2.4.0rc1.dist-info}/METADATA +4 -2
- {snowflake_cli_labs-2.3.1.dist-info → snowflake_cli_labs-2.4.0rc1.dist-info}/RECORD +96 -76
- /snowflake/cli/plugins/{object/stage_deprecated → object_stage_deprecated}/commands.py +0 -0
- {snowflake_cli_labs-2.3.1.dist-info → snowflake_cli_labs-2.4.0rc1.dist-info}/WHEEL +0 -0
- {snowflake_cli_labs-2.3.1.dist-info → snowflake_cli_labs-2.4.0rc1.dist-info}/entry_points.txt +0 -0
- {snowflake_cli_labs-2.3.1.dist-info → snowflake_cli_labs-2.4.0rc1.dist-info}/licenses/LICENSE +0 -0
|
@@ -5,9 +5,10 @@ from abc import ABC, abstractmethod
|
|
|
5
5
|
from functools import cached_property
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from textwrap import dedent
|
|
8
|
-
from typing import List, Optional
|
|
8
|
+
from typing import List, Optional, TypedDict
|
|
9
9
|
|
|
10
10
|
import jinja2
|
|
11
|
+
from click import ClickException
|
|
11
12
|
from snowflake.cli.api.console import cli_console as cc
|
|
12
13
|
from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
|
|
13
14
|
from snowflake.cli.api.project.definition import (
|
|
@@ -24,13 +25,15 @@ from snowflake.cli.api.project.util import (
|
|
|
24
25
|
from snowflake.cli.api.sql_execution import SqlExecutionMixin
|
|
25
26
|
from snowflake.cli.plugins.connection.util import make_snowsight_url
|
|
26
27
|
from snowflake.cli.plugins.nativeapp.artifacts import (
|
|
27
|
-
ArtifactDeploymentMap,
|
|
28
28
|
ArtifactMapping,
|
|
29
|
+
BundleMap,
|
|
29
30
|
build_bundle,
|
|
30
31
|
resolve_without_follow,
|
|
31
|
-
source_path_to_deploy_path,
|
|
32
32
|
translate_artifact,
|
|
33
33
|
)
|
|
34
|
+
from snowflake.cli.plugins.nativeapp.codegen.compiler import (
|
|
35
|
+
NativeAppCompiler,
|
|
36
|
+
)
|
|
34
37
|
from snowflake.cli.plugins.nativeapp.constants import (
|
|
35
38
|
ALLOWED_SPECIAL_COMMENTS,
|
|
36
39
|
COMMENT_COL,
|
|
@@ -47,6 +50,7 @@ from snowflake.cli.plugins.nativeapp.exceptions import (
|
|
|
47
50
|
MissingPackageScriptError,
|
|
48
51
|
UnexpectedOwnerError,
|
|
49
52
|
)
|
|
53
|
+
from snowflake.cli.plugins.nativeapp.feature_flags import FeatureFlag
|
|
50
54
|
from snowflake.cli.plugins.nativeapp.utils import verify_exists, verify_no_directories
|
|
51
55
|
from snowflake.cli.plugins.stage.diff import (
|
|
52
56
|
DiffResult,
|
|
@@ -59,6 +63,8 @@ from snowflake.cli.plugins.stage.diff import (
|
|
|
59
63
|
from snowflake.connector import ProgrammingError
|
|
60
64
|
from snowflake.connector.cursor import DictCursor
|
|
61
65
|
|
|
66
|
+
ApplicationOwnedObject = TypedDict("ApplicationOwnedObject", {"name": str, "type": str})
|
|
67
|
+
|
|
62
68
|
|
|
63
69
|
def generic_sql_error_handler(
|
|
64
70
|
err: ProgrammingError, role: Optional[str] = None, warehouse: Optional[str] = None
|
|
@@ -159,6 +165,10 @@ class NativeAppManager(SqlExecutionMixin):
|
|
|
159
165
|
def deploy_root(self) -> Path:
|
|
160
166
|
return Path(self.project_root, self.definition.deploy_root)
|
|
161
167
|
|
|
168
|
+
@cached_property
|
|
169
|
+
def generated_root(self) -> Path:
|
|
170
|
+
return Path(self.deploy_root, self.definition.generated_root)
|
|
171
|
+
|
|
162
172
|
@cached_property
|
|
163
173
|
def package_scripts(self) -> List[str]:
|
|
164
174
|
"""
|
|
@@ -305,11 +315,20 @@ class NativeAppManager(SqlExecutionMixin):
|
|
|
305
315
|
return False
|
|
306
316
|
return True
|
|
307
317
|
|
|
308
|
-
def build_bundle(self) ->
|
|
318
|
+
def build_bundle(self) -> BundleMap:
|
|
309
319
|
"""
|
|
310
320
|
Populates the local deploy root from artifact sources.
|
|
311
321
|
"""
|
|
312
|
-
|
|
322
|
+
mapped_files = build_bundle(self.project_root, self.deploy_root, self.artifacts)
|
|
323
|
+
if FeatureFlag.ENABLE_SETUP_SCRIPT_GENERATION.is_enabled():
|
|
324
|
+
compiler = NativeAppCompiler(
|
|
325
|
+
project_definition=self._project_definition,
|
|
326
|
+
project_root=self.project_root,
|
|
327
|
+
deploy_root=self.deploy_root,
|
|
328
|
+
generated_root=self.generated_root,
|
|
329
|
+
)
|
|
330
|
+
compiler.compile_artifacts()
|
|
331
|
+
return mapped_files
|
|
313
332
|
|
|
314
333
|
def sync_deploy_root_with_stage(
|
|
315
334
|
self,
|
|
@@ -317,7 +336,7 @@ class NativeAppManager(SqlExecutionMixin):
|
|
|
317
336
|
prune: bool,
|
|
318
337
|
recursive: bool,
|
|
319
338
|
local_paths_to_sync: List[Path] | None = None,
|
|
320
|
-
|
|
339
|
+
bundle_map: Optional[BundleMap] = None,
|
|
321
340
|
) -> DiffResult:
|
|
322
341
|
"""
|
|
323
342
|
Ensures that the files on our remote stage match the artifacts we have in
|
|
@@ -329,7 +348,7 @@ class NativeAppManager(SqlExecutionMixin):
|
|
|
329
348
|
recursive (bool): Whether to traverse directories recursively.
|
|
330
349
|
local_paths_to_sync (List[Path], optional): List of local paths to sync. Defaults to None to sync all
|
|
331
350
|
local paths. Note that providing an empty list here is equivalent to None.
|
|
332
|
-
|
|
351
|
+
bundle_map: the artifact mapping computed during the `bundle` step. Required when local_paths_to_sync is
|
|
333
352
|
provided.
|
|
334
353
|
|
|
335
354
|
Returns:
|
|
@@ -359,7 +378,7 @@ class NativeAppManager(SqlExecutionMixin):
|
|
|
359
378
|
|
|
360
379
|
files_not_removed = []
|
|
361
380
|
if local_paths_to_sync:
|
|
362
|
-
assert
|
|
381
|
+
assert bundle_map is not None
|
|
363
382
|
|
|
364
383
|
# Deploying specific files/directories
|
|
365
384
|
resolved_paths_to_sync = [
|
|
@@ -367,13 +386,17 @@ class NativeAppManager(SqlExecutionMixin):
|
|
|
367
386
|
]
|
|
368
387
|
if not recursive:
|
|
369
388
|
verify_no_directories(resolved_paths_to_sync)
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
389
|
+
|
|
390
|
+
deploy_paths_to_sync = []
|
|
391
|
+
for resolved_path in resolved_paths_to_sync:
|
|
392
|
+
verify_exists(resolved_path)
|
|
393
|
+
deploy_paths = bundle_map.to_deploy_paths(resolved_path)
|
|
394
|
+
if not deploy_paths:
|
|
395
|
+
raise ClickException(f"No artifact found for {resolved_path}")
|
|
396
|
+
deploy_paths_to_sync.extend(deploy_paths)
|
|
397
|
+
|
|
375
398
|
stage_paths_to_sync = _get_stage_paths_to_sync(
|
|
376
|
-
deploy_paths_to_sync, self.deploy_root
|
|
399
|
+
deploy_paths_to_sync, resolve_without_follow(self.deploy_root)
|
|
377
400
|
)
|
|
378
401
|
diff = preserve_from_diff(diff, stage_paths_to_sync)
|
|
379
402
|
else:
|
|
@@ -429,6 +452,16 @@ class NativeAppManager(SqlExecutionMixin):
|
|
|
429
452
|
"application packages", self.package_name, name_col=NAME_COL
|
|
430
453
|
)
|
|
431
454
|
|
|
455
|
+
def get_objects_owned_by_application(self) -> List[ApplicationOwnedObject]:
|
|
456
|
+
"""
|
|
457
|
+
Returns all application objects owned by this application.
|
|
458
|
+
"""
|
|
459
|
+
with self.use_role(self.app_role):
|
|
460
|
+
results = self._execute_query(
|
|
461
|
+
f"show objects owned by application {self.app_name}"
|
|
462
|
+
).fetchall()
|
|
463
|
+
return [{"name": row[1], "type": row[2]} for row in results]
|
|
464
|
+
|
|
432
465
|
def get_snowsight_url(self) -> str:
|
|
433
466
|
"""Returns the URL that can be used to visit this app via Snowsight."""
|
|
434
467
|
name = unquote_identifier(self.app_name)
|
|
@@ -522,7 +555,7 @@ class NativeAppManager(SqlExecutionMixin):
|
|
|
522
555
|
prune: bool,
|
|
523
556
|
recursive: bool,
|
|
524
557
|
local_paths_to_sync: List[Path] | None = None,
|
|
525
|
-
|
|
558
|
+
bundle_map: Optional[BundleMap] = None,
|
|
526
559
|
) -> DiffResult:
|
|
527
560
|
"""app deploy process"""
|
|
528
561
|
|
|
@@ -535,7 +568,7 @@ class NativeAppManager(SqlExecutionMixin):
|
|
|
535
568
|
|
|
536
569
|
# 3. Upload files from deploy root local folder to the above stage
|
|
537
570
|
diff = self.sync_deploy_root_with_stage(
|
|
538
|
-
self.package_role, prune, recursive, local_paths_to_sync,
|
|
571
|
+
self.package_role, prune, recursive, local_paths_to_sync, bundle_map
|
|
539
572
|
)
|
|
540
573
|
|
|
541
574
|
return diff
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from pathlib import Path
|
|
2
4
|
from textwrap import dedent
|
|
3
|
-
from typing import Dict
|
|
5
|
+
from typing import Dict, Optional
|
|
4
6
|
|
|
5
7
|
import typer
|
|
6
8
|
from snowflake.cli.api.console import cli_console as cc
|
|
@@ -16,11 +18,14 @@ from snowflake.cli.plugins.nativeapp.exceptions import (
|
|
|
16
18
|
CouldNotDropApplicationPackageWithVersions,
|
|
17
19
|
)
|
|
18
20
|
from snowflake.cli.plugins.nativeapp.manager import (
|
|
21
|
+
ApplicationOwnedObject,
|
|
19
22
|
NativeAppCommandProcessor,
|
|
20
23
|
NativeAppManager,
|
|
21
24
|
ensure_correct_owner,
|
|
22
25
|
)
|
|
23
|
-
from snowflake.cli.plugins.nativeapp.utils import
|
|
26
|
+
from snowflake.cli.plugins.nativeapp.utils import (
|
|
27
|
+
needs_confirmation,
|
|
28
|
+
)
|
|
24
29
|
from snowflake.connector.cursor import DictCursor
|
|
25
30
|
|
|
26
31
|
|
|
@@ -28,13 +33,17 @@ class NativeAppTeardownProcessor(NativeAppManager, NativeAppCommandProcessor):
|
|
|
28
33
|
def __init__(self, project_definition: Dict, project_root: Path):
|
|
29
34
|
super().__init__(project_definition, project_root)
|
|
30
35
|
|
|
31
|
-
def drop_generic_object(
|
|
36
|
+
def drop_generic_object(
|
|
37
|
+
self, object_type: str, object_name: str, role: str, cascade: bool = False
|
|
38
|
+
):
|
|
32
39
|
"""
|
|
33
40
|
Drop object using the given role.
|
|
34
41
|
"""
|
|
35
42
|
with self.use_role(role):
|
|
36
43
|
cc.step(f"Dropping {object_type} {object_name} now.")
|
|
37
44
|
drop_query = f"drop {object_type} {object_name}"
|
|
45
|
+
if cascade:
|
|
46
|
+
drop_query += " cascade"
|
|
38
47
|
try:
|
|
39
48
|
self._execute_query(drop_query)
|
|
40
49
|
except:
|
|
@@ -42,7 +51,23 @@ class NativeAppTeardownProcessor(NativeAppManager, NativeAppCommandProcessor):
|
|
|
42
51
|
|
|
43
52
|
cc.message(f"Dropped {object_type} {object_name} successfully.")
|
|
44
53
|
|
|
45
|
-
def
|
|
54
|
+
def _application_objects_to_str(
|
|
55
|
+
self, application_objects: ApplicationOwnedObject
|
|
56
|
+
) -> str:
|
|
57
|
+
"""
|
|
58
|
+
Returns a list in an "(Object Type) Object Name" format. Database-level and schema-level object names are fully qualified:
|
|
59
|
+
(COMPUTE_POOL) POOL_NAME
|
|
60
|
+
(DATABASE) DB_NAME
|
|
61
|
+
(SCHEMA) DB_NAME.PUBLIC
|
|
62
|
+
...
|
|
63
|
+
"""
|
|
64
|
+
return "\n".join(
|
|
65
|
+
[f"({obj['type']}) {obj['name']}" for obj in application_objects]
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def drop_application(
|
|
69
|
+
self, auto_yes: bool, interactive: bool = False, cascade: Optional[bool] = None
|
|
70
|
+
):
|
|
46
71
|
"""
|
|
47
72
|
Attempts to drop the application object if all validations and user prompts allow so.
|
|
48
73
|
"""
|
|
@@ -89,9 +114,47 @@ class NativeAppTeardownProcessor(NativeAppManager, NativeAppCommandProcessor):
|
|
|
89
114
|
cc.message(f"Did not drop application object {self.app_name}.")
|
|
90
115
|
return # The user desires to keep the app, therefore exit gracefully
|
|
91
116
|
|
|
92
|
-
# 4.
|
|
117
|
+
# 4. Check for application objects owned by the application
|
|
118
|
+
application_objects = self.get_objects_owned_by_application()
|
|
119
|
+
if len(application_objects) > 0:
|
|
120
|
+
application_objects_str = self._application_objects_to_str(
|
|
121
|
+
application_objects
|
|
122
|
+
)
|
|
123
|
+
if cascade is True:
|
|
124
|
+
cc.message(
|
|
125
|
+
f"The following objects are owned by application {self.app_name} and will be dropped:\n{application_objects_str}"
|
|
126
|
+
)
|
|
127
|
+
elif cascade is False:
|
|
128
|
+
cc.message(
|
|
129
|
+
f"The following objects are owned by application {self.app_name}:\n{application_objects_str}"
|
|
130
|
+
)
|
|
131
|
+
elif interactive:
|
|
132
|
+
if interactive:
|
|
133
|
+
user_response = typer.prompt(
|
|
134
|
+
f"The following objects are owned by application {self.app_name}:\n{application_objects_str}\n\nWould you like to drop these objects in addition to the application? [y/n/ABORT]",
|
|
135
|
+
show_default=False,
|
|
136
|
+
default="ABORT",
|
|
137
|
+
)
|
|
138
|
+
if user_response in ["y", "yes", "Y", "Yes", "YES"]:
|
|
139
|
+
cascade = True
|
|
140
|
+
elif user_response in ["n", "no", "N", "No", "NO"]:
|
|
141
|
+
cascade = False
|
|
142
|
+
else:
|
|
143
|
+
raise typer.Abort()
|
|
144
|
+
else:
|
|
145
|
+
cc.message(
|
|
146
|
+
f"The following application objects are owned by application {self.app_name}:\n{application_objects_str}\n\nRe-run teardown again with --cascade or --no-cascade to specify whether these objects should be dropped along with the application."
|
|
147
|
+
)
|
|
148
|
+
raise typer.Abort()
|
|
149
|
+
elif cascade is None:
|
|
150
|
+
cascade = False
|
|
151
|
+
|
|
152
|
+
# 5. All validations have passed, drop object
|
|
93
153
|
self.drop_generic_object(
|
|
94
|
-
object_type="application",
|
|
154
|
+
object_type="application",
|
|
155
|
+
object_name=self.app_name,
|
|
156
|
+
role=self.app_role,
|
|
157
|
+
cascade=cascade,
|
|
95
158
|
)
|
|
96
159
|
return # The application object was successfully dropped, therefore exit gracefully
|
|
97
160
|
|
|
@@ -176,10 +239,19 @@ class NativeAppTeardownProcessor(NativeAppManager, NativeAppCommandProcessor):
|
|
|
176
239
|
)
|
|
177
240
|
return # The application package was successfully dropped, therefore exit gracefully
|
|
178
241
|
|
|
179
|
-
def process(
|
|
242
|
+
def process(
|
|
243
|
+
self,
|
|
244
|
+
interactive: bool,
|
|
245
|
+
force_drop: bool = False,
|
|
246
|
+
cascade: Optional[bool] = None,
|
|
247
|
+
*args,
|
|
248
|
+
**kwargs,
|
|
249
|
+
):
|
|
180
250
|
|
|
181
251
|
# Drop the application object
|
|
182
|
-
self.drop_application(
|
|
252
|
+
self.drop_application(
|
|
253
|
+
auto_yes=force_drop, interactive=interactive, cascade=cascade
|
|
254
|
+
)
|
|
183
255
|
|
|
184
256
|
# Drop the application package
|
|
185
257
|
self.drop_package(auto_yes=force_drop)
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
2
4
|
from pathlib import Path
|
|
3
5
|
from sys import stdin, stdout
|
|
4
6
|
from typing import List, Optional, Union
|
|
@@ -43,7 +45,7 @@ def get_first_paragraph_from_markdown_file(file_path: Path) -> Optional[str]:
|
|
|
43
45
|
return paragraph_text
|
|
44
46
|
|
|
45
47
|
|
|
46
|
-
def shallow_git_clone(url: Union[str, PathLike], to_path: Union[str, PathLike]):
|
|
48
|
+
def shallow_git_clone(url: Union[str, os.PathLike], to_path: Union[str, os.PathLike]):
|
|
47
49
|
"""
|
|
48
50
|
Performs a shallow clone of the repository at the provided url to the path specified
|
|
49
51
|
|
|
@@ -77,7 +79,6 @@ def verify_no_directories(paths_to_sync: List[Path]):
|
|
|
77
79
|
)
|
|
78
80
|
|
|
79
81
|
|
|
80
|
-
def verify_exists(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
raise ClickException(f"The following path does not exist: {path}")
|
|
82
|
+
def verify_exists(path: Path):
|
|
83
|
+
if not path.exists():
|
|
84
|
+
raise ClickException(f"The following path does not exist: {path}")
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import logging
|
|
2
4
|
from typing import Optional
|
|
3
5
|
|
|
@@ -16,7 +18,6 @@ from snowflake.cli.plugins.nativeapp.policy import (
|
|
|
16
18
|
DenyAlwaysPolicy,
|
|
17
19
|
)
|
|
18
20
|
from snowflake.cli.plugins.nativeapp.run_processor import NativeAppRunProcessor
|
|
19
|
-
from snowflake.cli.plugins.nativeapp.utils import is_tty_interactive
|
|
20
21
|
from snowflake.cli.plugins.nativeapp.version.version_processor import (
|
|
21
22
|
NativeAppVersionCreateProcessor,
|
|
22
23
|
NativeAppVersionDropProcessor,
|
|
@@ -49,7 +50,7 @@ def create(
|
|
|
49
50
|
help="When enabled, the Snowflake CLI skips checking if your project has any untracked or stages files in git. Default: unset.",
|
|
50
51
|
is_flag=True,
|
|
51
52
|
),
|
|
52
|
-
interactive:
|
|
53
|
+
interactive: bool = InteractiveOption,
|
|
53
54
|
force: Optional[bool] = ForceOption,
|
|
54
55
|
**options,
|
|
55
56
|
) -> CommandResult:
|
|
@@ -62,7 +63,7 @@ def create(
|
|
|
62
63
|
is_interactive = False
|
|
63
64
|
if force:
|
|
64
65
|
policy = AllowAlwaysPolicy()
|
|
65
|
-
elif interactive
|
|
66
|
+
elif interactive:
|
|
66
67
|
is_interactive = True
|
|
67
68
|
policy = AskAlwaysPolicy()
|
|
68
69
|
else:
|
|
@@ -113,7 +114,7 @@ def drop(
|
|
|
113
114
|
None,
|
|
114
115
|
help="Version defined in an application package that you want to drop. Defaults to the version specified in the `manifest.yml` file.",
|
|
115
116
|
),
|
|
116
|
-
interactive:
|
|
117
|
+
interactive: bool = InteractiveOption,
|
|
117
118
|
force: Optional[bool] = ForceOption,
|
|
118
119
|
**options,
|
|
119
120
|
) -> CommandResult:
|
|
@@ -124,7 +125,7 @@ def drop(
|
|
|
124
125
|
is_interactive = False
|
|
125
126
|
if force:
|
|
126
127
|
policy = AllowAlwaysPolicy()
|
|
127
|
-
elif interactive
|
|
128
|
+
elif interactive:
|
|
128
129
|
is_interactive = True
|
|
129
130
|
policy = AskAlwaysPolicy()
|
|
130
131
|
else:
|
|
@@ -6,6 +6,8 @@ from snowflake.cli.api.commands.snow_typer import SnowTyper
|
|
|
6
6
|
from snowflake.cli.api.feature_flags import FeatureFlag
|
|
7
7
|
from snowflake.cli.api.output.types import MessageResult
|
|
8
8
|
from snowflake.cli.plugins.notebook.manager import NotebookManager
|
|
9
|
+
from snowflake.cli.plugins.notebook.types import NotebookName, NotebookStagePath
|
|
10
|
+
from typing_extensions import Annotated
|
|
9
11
|
|
|
10
12
|
app = SnowTyper(
|
|
11
13
|
name="notebook",
|
|
@@ -15,6 +17,11 @@ app = SnowTyper(
|
|
|
15
17
|
log = logging.getLogger(__name__)
|
|
16
18
|
|
|
17
19
|
NOTEBOOK_IDENTIFIER = identifier_argument(sf_object="notebook", example="MY_NOTEBOOK")
|
|
20
|
+
NotebookFile: NotebookStagePath = typer.Option(
|
|
21
|
+
"--notebook-file",
|
|
22
|
+
"-f",
|
|
23
|
+
help="Stage path with notebook file. For example `@stage/path/to/notebook.ipynb`",
|
|
24
|
+
)
|
|
18
25
|
|
|
19
26
|
|
|
20
27
|
@app.command(requires_connection=True)
|
|
@@ -49,3 +56,17 @@ def open_cmd(
|
|
|
49
56
|
url = NotebookManager().get_url(notebook_name=identifier)
|
|
50
57
|
typer.launch(url)
|
|
51
58
|
return MessageResult(message=url)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@app.command(requires_connection=True)
|
|
62
|
+
def create(
|
|
63
|
+
identifier: Annotated[NotebookName, NOTEBOOK_IDENTIFIER],
|
|
64
|
+
notebook_file: Annotated[NotebookStagePath, NotebookFile],
|
|
65
|
+
**options,
|
|
66
|
+
):
|
|
67
|
+
"""Creates notebook from stage."""
|
|
68
|
+
notebook_url = NotebookManager().create(
|
|
69
|
+
notebook_name=identifier,
|
|
70
|
+
notebook_file=notebook_file,
|
|
71
|
+
)
|
|
72
|
+
return MessageResult(message=notebook_url)
|
|
@@ -1,14 +1,57 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from textwrap import dedent
|
|
3
|
+
|
|
4
|
+
from snowflake.cli.api.cli_global_context import cli_context
|
|
5
|
+
from snowflake.cli.api.identifiers import FQN
|
|
1
6
|
from snowflake.cli.api.sql_execution import SqlExecutionMixin
|
|
2
7
|
from snowflake.cli.plugins.connection.util import make_snowsight_url
|
|
8
|
+
from snowflake.cli.plugins.notebook.exceptions import NotebookStagePathError
|
|
9
|
+
from snowflake.cli.plugins.notebook.types import NotebookName, NotebookStagePath
|
|
3
10
|
|
|
4
11
|
|
|
5
12
|
class NotebookManager(SqlExecutionMixin):
|
|
6
|
-
def execute(self, notebook_name:
|
|
13
|
+
def execute(self, notebook_name: NotebookName):
|
|
7
14
|
query = f"EXECUTE NOTEBOOK {notebook_name}()"
|
|
8
15
|
return self._execute_query(query=query)
|
|
9
16
|
|
|
10
|
-
def get_url(self, notebook_name:
|
|
17
|
+
def get_url(self, notebook_name: NotebookName):
|
|
18
|
+
fqn = FQN.from_string(notebook_name).using_connection(self._conn)
|
|
11
19
|
return make_snowsight_url(
|
|
12
20
|
self._conn,
|
|
13
|
-
f"/#/notebooks/{
|
|
21
|
+
f"/#/notebooks/{fqn.url_identifier}",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def parse_stage_as_path(notebook_file: NotebookName) -> Path:
|
|
26
|
+
"""Parses notebook file path to pathlib.Path."""
|
|
27
|
+
if not notebook_file.endswith(".ipynb"):
|
|
28
|
+
raise NotebookStagePathError(notebook_file)
|
|
29
|
+
stage_path = Path(notebook_file)
|
|
30
|
+
if len(stage_path.parts) < 2:
|
|
31
|
+
raise NotebookStagePathError(notebook_file)
|
|
32
|
+
|
|
33
|
+
return stage_path
|
|
34
|
+
|
|
35
|
+
def create(
|
|
36
|
+
self,
|
|
37
|
+
notebook_name: NotebookName,
|
|
38
|
+
notebook_file: NotebookStagePath,
|
|
39
|
+
) -> str:
|
|
40
|
+
notebook_fqn = FQN.from_string(notebook_name).using_connection(self._conn)
|
|
41
|
+
stage_path = self.parse_stage_as_path(notebook_file)
|
|
42
|
+
|
|
43
|
+
queries = dedent(
|
|
44
|
+
f"""
|
|
45
|
+
CREATE OR REPLACE NOTEBOOK {notebook_fqn.identifier}
|
|
46
|
+
FROM '{stage_path.parent}'
|
|
47
|
+
QUERY_WAREHOUSE = '{cli_context.connection.warehouse}'
|
|
48
|
+
MAIN_FILE = '{stage_path.name}';
|
|
49
|
+
|
|
50
|
+
ALTER NOTEBOOK {notebook_fqn.identifier} ADD LIVE VERSION FROM LAST;
|
|
51
|
+
"""
|
|
52
|
+
)
|
|
53
|
+
self._execute_queries(queries=queries)
|
|
54
|
+
|
|
55
|
+
return make_snowsight_url(
|
|
56
|
+
self._conn, f"/#/notebooks/{notebook_fqn.url_identifier}"
|
|
14
57
|
)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import List, Optional, Tuple
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from click import ClickException
|
|
7
|
+
from snowflake.cli.api.commands.snow_typer import SnowTyper
|
|
8
|
+
from snowflake.cli.api.constants import ObjectType
|
|
9
|
+
from snowflake.cli.plugins.object.commands import (
|
|
10
|
+
ScopeOption,
|
|
11
|
+
describe,
|
|
12
|
+
drop,
|
|
13
|
+
list_,
|
|
14
|
+
scope_option, # noqa: F401
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def add_object_command_aliases(
|
|
19
|
+
app: SnowTyper,
|
|
20
|
+
object_type: ObjectType,
|
|
21
|
+
name_argument: typer.Argument,
|
|
22
|
+
like_option: Optional[typer.Option],
|
|
23
|
+
scope_option: Optional[typer.Option],
|
|
24
|
+
ommit_commands: List[str] = [],
|
|
25
|
+
):
|
|
26
|
+
if "list" not in ommit_commands:
|
|
27
|
+
if not like_option:
|
|
28
|
+
raise ClickException('[like_option] have to be defined for "list" command')
|
|
29
|
+
|
|
30
|
+
if not scope_option:
|
|
31
|
+
|
|
32
|
+
@app.command("list", requires_connection=True)
|
|
33
|
+
def list_cmd(like: str = like_option, **options): # type: ignore
|
|
34
|
+
list_(
|
|
35
|
+
object_type=object_type.value.cli_name,
|
|
36
|
+
like=like,
|
|
37
|
+
scope=ScopeOption.default,
|
|
38
|
+
**options,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
else:
|
|
42
|
+
|
|
43
|
+
@app.command("list", requires_connection=True)
|
|
44
|
+
def list_cmd(
|
|
45
|
+
like: str = like_option, # type: ignore
|
|
46
|
+
scope: Tuple[str, str] = scope_option, # type: ignore
|
|
47
|
+
**options,
|
|
48
|
+
):
|
|
49
|
+
list_(
|
|
50
|
+
object_type=object_type.value.cli_name,
|
|
51
|
+
like=like,
|
|
52
|
+
scope=scope,
|
|
53
|
+
**options,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
list_cmd.__doc__ = f"Lists all available {object_type.value.sf_plural_name}."
|
|
57
|
+
|
|
58
|
+
if "drop" not in ommit_commands:
|
|
59
|
+
|
|
60
|
+
@app.command("drop", requires_connection=True)
|
|
61
|
+
def drop_cmd(name: str = name_argument, **options):
|
|
62
|
+
drop(
|
|
63
|
+
object_type=object_type.value.cli_name,
|
|
64
|
+
object_name=name,
|
|
65
|
+
**options,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
drop_cmd.__doc__ = f"Drops {object_type.value.sf_name} with given name."
|
|
69
|
+
|
|
70
|
+
if "describe" not in ommit_commands:
|
|
71
|
+
|
|
72
|
+
@app.command("describe", requires_connection=True)
|
|
73
|
+
def describe_cmd(name: str = name_argument, **options):
|
|
74
|
+
describe(
|
|
75
|
+
object_type=object_type.value.cli_name,
|
|
76
|
+
object_name=name,
|
|
77
|
+
**options,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
describe_cmd.__doc__ = f"Provides description of {object_type.value.sf_name}."
|
|
@@ -10,13 +10,11 @@ from snowflake.cli.api.constants import SUPPORTED_OBJECTS, VALID_SCOPES
|
|
|
10
10
|
from snowflake.cli.api.output.types import QueryResult
|
|
11
11
|
from snowflake.cli.api.project.util import is_valid_identifier
|
|
12
12
|
from snowflake.cli.plugins.object.manager import ObjectManager
|
|
13
|
-
from snowflake.cli.plugins.object.stage_deprecated.commands import app as stage_app
|
|
14
13
|
|
|
15
14
|
app = SnowTyper(
|
|
16
15
|
name="object",
|
|
17
16
|
help="Manages Snowflake objects like warehouses and stages",
|
|
18
17
|
)
|
|
19
|
-
app.add_typer(stage_app)
|
|
20
18
|
|
|
21
19
|
|
|
22
20
|
NameArgument = typer.Argument(help="Name of the object")
|
|
@@ -40,10 +38,16 @@ def _scope_validate(object_type: str, scope: Tuple[str, str]):
|
|
|
40
38
|
raise ClickException("compute-pool scope is only supported for listing service")
|
|
41
39
|
|
|
42
40
|
|
|
43
|
-
|
|
44
|
-
(
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
def scope_option(help_example: str):
|
|
42
|
+
return typer.Option(
|
|
43
|
+
(None, None),
|
|
44
|
+
"--in",
|
|
45
|
+
help=f"Specifies the scope of this command using '--in <scope> <name>', for example {help_example}.",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
ScopeOption = scope_option(
|
|
50
|
+
help_example="`list table --in database my_db`. Some object types have specialized scopes (e.g. list service --in compute-pool my_pool)"
|
|
47
51
|
)
|
|
48
52
|
|
|
49
53
|
SUPPORTED_TYPES_MSG = "\n\nSupported types: " + ", ".join(SUPPORTED_OBJECTS)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# TODO 3.0: remove this file
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# TODO 3.0: remove this file
|
|
2
|
+
|
|
3
|
+
from snowflake.cli.api.plugins.command import (
|
|
4
|
+
CommandPath,
|
|
5
|
+
CommandSpec,
|
|
6
|
+
CommandType,
|
|
7
|
+
plugin_hook_impl,
|
|
8
|
+
)
|
|
9
|
+
from snowflake.cli.plugins.object_stage_deprecated.commands import (
|
|
10
|
+
app as stage_deprecated_app,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@plugin_hook_impl
|
|
15
|
+
def command_spec():
|
|
16
|
+
return CommandSpec(
|
|
17
|
+
parent_command_path=CommandPath(["object"]),
|
|
18
|
+
command_type=CommandType.COMMAND_GROUP,
|
|
19
|
+
typer_instance=stage_deprecated_app,
|
|
20
|
+
)
|