snowflake-cli 3.3.0__py3-none-any.whl → 3.5.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/_app/__main__.py +2 -2
- snowflake/cli/_app/cli_app.py +220 -197
- snowflake/cli/_app/commands_registration/builtin_plugins.py +5 -1
- snowflake/cli/_app/commands_registration/command_plugins_loader.py +3 -1
- snowflake/cli/_app/commands_registration/commands_registration_with_callbacks.py +4 -30
- snowflake/cli/_app/printing.py +2 -2
- snowflake/cli/_plugins/connection/commands.py +2 -4
- snowflake/cli/_plugins/cortex/commands.py +2 -4
- snowflake/cli/_plugins/git/manager.py +1 -1
- snowflake/cli/_plugins/helpers/commands.py +3 -4
- snowflake/cli/_plugins/nativeapp/artifacts.py +6 -624
- snowflake/cli/_plugins/nativeapp/bundle_context.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/artifact_processor.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/compiler.py +1 -3
- snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +2 -2
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +2 -2
- snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +2 -2
- snowflake/cli/_plugins/nativeapp/commands.py +21 -19
- snowflake/cli/_plugins/nativeapp/entities/application.py +16 -19
- snowflake/cli/_plugins/nativeapp/entities/application_package.py +142 -55
- snowflake/cli/_plugins/nativeapp/release_channel/commands.py +37 -3
- snowflake/cli/_plugins/nativeapp/release_directive/commands.py +80 -2
- snowflake/cli/_plugins/nativeapp/sf_sql_facade.py +224 -44
- snowflake/cli/_plugins/nativeapp/v2_conversions/compat.py +2 -2
- snowflake/cli/_plugins/nativeapp/version/commands.py +1 -1
- snowflake/cli/_plugins/notebook/commands.py +54 -2
- snowflake/cli/_plugins/notebook/exceptions.py +1 -1
- snowflake/cli/_plugins/notebook/manager.py +3 -3
- snowflake/cli/_plugins/notebook/notebook_entity.py +120 -0
- snowflake/cli/_plugins/notebook/notebook_entity_model.py +42 -0
- snowflake/cli/_plugins/notebook/notebook_project_paths.py +15 -0
- snowflake/cli/_plugins/notebook/types.py +3 -0
- snowflake/cli/_plugins/plugin/commands.py +79 -0
- snowflake/cli/_plugins/plugin/manager.py +74 -0
- snowflake/cli/_plugins/plugin/plugin_spec.py +30 -0
- snowflake/cli/_plugins/project/__init__.py +0 -0
- snowflake/cli/_plugins/project/commands.py +157 -0
- snowflake/cli/_plugins/project/feature_flags.py +22 -0
- snowflake/cli/_plugins/project/manager.py +76 -0
- snowflake/cli/_plugins/project/plugin_spec.py +30 -0
- snowflake/cli/_plugins/project/project_entity_model.py +40 -0
- snowflake/cli/_plugins/snowpark/commands.py +49 -30
- snowflake/cli/_plugins/snowpark/common.py +47 -2
- snowflake/cli/_plugins/snowpark/snowpark_entity.py +38 -25
- snowflake/cli/_plugins/snowpark/snowpark_entity_model.py +18 -30
- snowflake/cli/_plugins/snowpark/snowpark_project_paths.py +156 -23
- snowflake/cli/_plugins/snowpark/zipper.py +33 -1
- snowflake/cli/_plugins/spcs/compute_pool/commands.py +53 -5
- snowflake/cli/_plugins/spcs/compute_pool/compute_pool_entity.py +8 -0
- snowflake/cli/_plugins/spcs/compute_pool/compute_pool_entity_model.py +37 -0
- snowflake/cli/_plugins/spcs/compute_pool/manager.py +45 -0
- snowflake/cli/_plugins/spcs/image_repository/commands.py +29 -0
- snowflake/cli/_plugins/spcs/image_repository/image_repository_entity.py +8 -0
- snowflake/cli/_plugins/spcs/image_repository/image_repository_entity_model.py +8 -0
- snowflake/cli/_plugins/spcs/image_repository/manager.py +1 -1
- snowflake/cli/_plugins/spcs/services/commands.py +51 -1
- snowflake/cli/_plugins/spcs/services/manager.py +114 -0
- snowflake/cli/_plugins/spcs/services/service_entity.py +6 -0
- snowflake/cli/_plugins/spcs/services/service_entity_model.py +45 -0
- snowflake/cli/_plugins/spcs/services/service_project_paths.py +15 -0
- snowflake/cli/_plugins/stage/commands.py +2 -1
- snowflake/cli/_plugins/stage/diff.py +60 -39
- snowflake/cli/_plugins/stage/manager.py +26 -13
- snowflake/cli/_plugins/stage/utils.py +1 -1
- snowflake/cli/_plugins/streamlit/commands.py +18 -24
- snowflake/cli/_plugins/streamlit/manager.py +37 -27
- snowflake/cli/_plugins/streamlit/streamlit_entity.py +20 -41
- snowflake/cli/_plugins/streamlit/streamlit_entity_model.py +14 -24
- snowflake/cli/_plugins/streamlit/streamlit_project_paths.py +30 -0
- snowflake/cli/_plugins/workspace/commands.py +3 -3
- snowflake/cli/_plugins/workspace/manager.py +1 -1
- snowflake/cli/api/artifacts/bundle_map.py +500 -0
- snowflake/cli/api/artifacts/common.py +78 -0
- snowflake/cli/api/artifacts/upload.py +51 -0
- snowflake/cli/api/artifacts/utils.py +82 -0
- snowflake/cli/api/cli_global_context.py +14 -1
- snowflake/cli/api/commands/flags.py +34 -13
- snowflake/cli/api/commands/snow_typer.py +12 -0
- snowflake/cli/api/commands/utils.py +30 -2
- snowflake/cli/api/config.py +15 -10
- snowflake/cli/api/constants.py +1 -0
- snowflake/cli/api/entities/common.py +14 -32
- snowflake/cli/api/entities/resolver.py +160 -0
- snowflake/cli/api/entities/utils.py +56 -15
- snowflake/cli/api/errno.py +3 -0
- snowflake/cli/api/exceptions.py +8 -1
- snowflake/cli/api/feature_flags.py +1 -1
- snowflake/cli/api/plugins/plugin_config.py +43 -4
- snowflake/cli/api/project/definition_conversion.py +3 -2
- snowflake/cli/api/project/definition_helper.py +31 -0
- snowflake/cli/api/project/project_paths.py +28 -0
- snowflake/cli/api/project/schemas/entities/common.py +130 -1
- snowflake/cli/api/project/schemas/entities/entities.py +30 -0
- snowflake/cli/api/project/schemas/project_definition.py +27 -0
- snowflake/cli/api/project/schemas/updatable_model.py +2 -2
- snowflake/cli/api/project/schemas/v1/native_app/native_app.py +5 -7
- snowflake/cli/api/secure_path.py +6 -0
- snowflake/cli/api/sql_execution.py +5 -1
- snowflake/cli/api/stage_path.py +7 -2
- snowflake/cli/api/utils/graph.py +3 -0
- snowflake/cli/api/utils/path_utils.py +24 -0
- {snowflake_cli-3.3.0.dist-info → snowflake_cli-3.5.0.dist-info}/METADATA +12 -13
- {snowflake_cli-3.3.0.dist-info → snowflake_cli-3.5.0.dist-info}/RECORD +109 -85
- snowflake/cli/_app/api_impl/plugin/plugin_config_provider_impl.py +0 -66
- snowflake/cli/api/__init__.py +0 -48
- snowflake/cli/api/project/schemas/v1/native_app/path_mapping.py +0 -65
- /snowflake/cli/{_app/api_impl → _plugins/plugin}/__init__.py +0 -0
- /snowflake/cli/{_app/api_impl/plugin → api/artifacts}/__init__.py +0 -0
- {snowflake_cli-3.3.0.dist-info → snowflake_cli-3.5.0.dist-info}/WHEEL +0 -0
- {snowflake_cli-3.3.0.dist-info → snowflake_cli-3.5.0.dist-info}/entry_points.txt +0 -0
- {snowflake_cli-3.3.0.dist-info → snowflake_cli-3.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -45,6 +45,7 @@ from snowflake.cli._plugins.nativeapp.sf_facade_exceptions import (
|
|
|
45
45
|
UserScriptError,
|
|
46
46
|
handle_unclassified_error,
|
|
47
47
|
)
|
|
48
|
+
from snowflake.cli._plugins.stage.manager import StageManager
|
|
48
49
|
from snowflake.cli.api.cli_global_context import get_cli_context
|
|
49
50
|
from snowflake.cli.api.constants import ObjectType
|
|
50
51
|
from snowflake.cli.api.errno import (
|
|
@@ -53,6 +54,8 @@ from snowflake.cli.api.errno import (
|
|
|
53
54
|
APPLICATION_PACKAGE_MAX_VERSIONS_HIT,
|
|
54
55
|
APPLICATION_PACKAGE_PATCH_ALREADY_EXISTS,
|
|
55
56
|
APPLICATION_REQUIRES_TELEMETRY_SHARING,
|
|
57
|
+
CANNOT_ADD_PATCH_WITH_NON_INCREASING_PATCH_NUMBER,
|
|
58
|
+
CANNOT_CREATE_VERSION_WITH_NON_ZERO_PATCH,
|
|
56
59
|
CANNOT_DEREGISTER_VERSION_ASSOCIATED_WITH_CHANNEL,
|
|
57
60
|
CANNOT_DISABLE_MANDATORY_TELEMETRY,
|
|
58
61
|
CANNOT_DISABLE_RELEASE_CHANNELS,
|
|
@@ -64,6 +67,7 @@ from snowflake.cli.api.errno import (
|
|
|
64
67
|
MAX_VERSIONS_IN_RELEASE_CHANNEL_REACHED,
|
|
65
68
|
NO_WAREHOUSE_SELECTED_IN_SESSION,
|
|
66
69
|
RELEASE_DIRECTIVE_DOES_NOT_EXIST,
|
|
70
|
+
RELEASE_DIRECTIVE_UNAPPROVED_VERSION_OR_PATCH,
|
|
67
71
|
RELEASE_DIRECTIVES_VERSION_PATCH_NOT_FOUND,
|
|
68
72
|
SQL_COMPILATION_ERROR,
|
|
69
73
|
TARGET_ACCOUNT_USED_BY_OTHER_RELEASE_DIRECTIVE,
|
|
@@ -289,7 +293,7 @@ class SnowflakeSQLFacade:
|
|
|
289
293
|
def create_version_in_package(
|
|
290
294
|
self,
|
|
291
295
|
package_name: str,
|
|
292
|
-
|
|
296
|
+
path_to_version_directory: str,
|
|
293
297
|
version: str,
|
|
294
298
|
label: str | None = None,
|
|
295
299
|
role: str | None = None,
|
|
@@ -297,7 +301,7 @@ class SnowflakeSQLFacade:
|
|
|
297
301
|
"""
|
|
298
302
|
Creates a new version in an existing application package.
|
|
299
303
|
@param package_name: Name of the application package to alter.
|
|
300
|
-
@param
|
|
304
|
+
@param path_to_version_directory: Path to artifacts on the stage to create a version from.
|
|
301
305
|
@param version: Version name to create.
|
|
302
306
|
@param [Optional] role: Switch to this role while executing create version.
|
|
303
307
|
@param [Optional] label: Label for this version, visible to consumers.
|
|
@@ -312,6 +316,9 @@ class SnowflakeSQLFacade:
|
|
|
312
316
|
with_label_clause = (
|
|
313
317
|
f"label={to_string_literal(label)}" if label is not None else ""
|
|
314
318
|
)
|
|
319
|
+
using_clause = (
|
|
320
|
+
f"using {StageManager.quote_stage_name(path_to_version_directory)}"
|
|
321
|
+
)
|
|
315
322
|
|
|
316
323
|
action = "register" if available_release_channels else "add"
|
|
317
324
|
|
|
@@ -320,7 +327,7 @@ class SnowflakeSQLFacade:
|
|
|
320
327
|
f"""\
|
|
321
328
|
alter application package {package_name}
|
|
322
329
|
{action} version {version}
|
|
323
|
-
|
|
330
|
+
{using_clause}
|
|
324
331
|
{with_label_clause}
|
|
325
332
|
"""
|
|
326
333
|
)
|
|
@@ -342,6 +349,10 @@ class SnowflakeSQLFacade:
|
|
|
342
349
|
f"Maximum versions reached for application package {package_name}. "
|
|
343
350
|
"Please drop the other versions first."
|
|
344
351
|
) from err
|
|
352
|
+
if err.errno == CANNOT_CREATE_VERSION_WITH_NON_ZERO_PATCH:
|
|
353
|
+
raise UserInputError(
|
|
354
|
+
"Cannot create a new version with a non-zero patch in the manifest file."
|
|
355
|
+
) from err
|
|
345
356
|
handle_unclassified_error(
|
|
346
357
|
err,
|
|
347
358
|
f"Failed to {action} version {version} to application package {package_name}.",
|
|
@@ -389,7 +400,7 @@ class SnowflakeSQLFacade:
|
|
|
389
400
|
def add_patch_to_package_version(
|
|
390
401
|
self,
|
|
391
402
|
package_name: str,
|
|
392
|
-
|
|
403
|
+
path_to_version_directory: str,
|
|
393
404
|
version: str,
|
|
394
405
|
patch: int | None = None,
|
|
395
406
|
label: str | None = None,
|
|
@@ -398,7 +409,7 @@ class SnowflakeSQLFacade:
|
|
|
398
409
|
"""
|
|
399
410
|
Add a new patch, optionally a custom one, to an existing version in an application package.
|
|
400
411
|
@param package_name: Name of the application package to alter.
|
|
401
|
-
@param
|
|
412
|
+
@param path_to_version_directory: Path to artifacts on the stage to create a version from.
|
|
402
413
|
@param version: Version name to create.
|
|
403
414
|
@param [Optional] patch: Patch number to create.
|
|
404
415
|
@param [Optional] label: Label for this patch, visible to consumers.
|
|
@@ -416,13 +427,13 @@ class SnowflakeSQLFacade:
|
|
|
416
427
|
)
|
|
417
428
|
|
|
418
429
|
patch_query = f" {patch}" if patch is not None else ""
|
|
419
|
-
|
|
430
|
+
using_clause = StageManager.quote_stage_name(path_to_version_directory)
|
|
420
431
|
# No space between patch and patch{patch_query} to avoid extra space when patch is None
|
|
421
432
|
add_patch_query = dedent(
|
|
422
433
|
f"""\
|
|
423
434
|
alter application package {package_name}
|
|
424
435
|
add patch{patch_query} for version {version}
|
|
425
|
-
using
|
|
436
|
+
using {using_clause}{with_label_clause}
|
|
426
437
|
"""
|
|
427
438
|
)
|
|
428
439
|
with self._use_role_optional(role):
|
|
@@ -433,8 +444,17 @@ class SnowflakeSQLFacade:
|
|
|
433
444
|
except Exception as err:
|
|
434
445
|
if isinstance(err, ProgrammingError):
|
|
435
446
|
if err.errno == APPLICATION_PACKAGE_PATCH_ALREADY_EXISTS:
|
|
447
|
+
extra_message = (
|
|
448
|
+
" Check the manifest file for any hard-coded patch value."
|
|
449
|
+
if patch is None
|
|
450
|
+
else ""
|
|
451
|
+
)
|
|
436
452
|
raise UserInputError(
|
|
437
|
-
f"Patch
|
|
453
|
+
f"Patch{patch_query} already exists for version {version} in application package {package_name}.{extra_message}"
|
|
454
|
+
) from err
|
|
455
|
+
if err.errno == CANNOT_ADD_PATCH_WITH_NON_INCREASING_PATCH_NUMBER:
|
|
456
|
+
raise UserInputError(
|
|
457
|
+
f"Cannot add a patch with a non-increasing patch number to version {version} in application package {package_name}."
|
|
438
458
|
) from err
|
|
439
459
|
handle_unclassified_error(
|
|
440
460
|
err,
|
|
@@ -713,7 +733,7 @@ class SnowflakeSQLFacade:
|
|
|
713
733
|
self,
|
|
714
734
|
name: str,
|
|
715
735
|
install_method: SameAccountInstallMethod,
|
|
716
|
-
|
|
736
|
+
path_to_version_directory: str,
|
|
717
737
|
role: str,
|
|
718
738
|
warehouse: str,
|
|
719
739
|
debug_mode: bool | None,
|
|
@@ -725,7 +745,7 @@ class SnowflakeSQLFacade:
|
|
|
725
745
|
|
|
726
746
|
@param name: Name of the application object
|
|
727
747
|
@param install_method: Method of installing the application
|
|
728
|
-
@param
|
|
748
|
+
@param path_to_version_directory: Path to directory in stage housing the application artifacts
|
|
729
749
|
@param role: Role to use when creating the application and provider-side objects
|
|
730
750
|
@param warehouse: Warehouse which is required to create an application object
|
|
731
751
|
@param debug_mode: Whether to enable debug mode; None means not explicitly enabled or disabled
|
|
@@ -750,7 +770,7 @@ class SnowflakeSQLFacade:
|
|
|
750
770
|
|
|
751
771
|
with self._use_role_optional(role), self._use_warehouse_optional(warehouse):
|
|
752
772
|
try:
|
|
753
|
-
using_clause = install_method.using_clause(
|
|
773
|
+
using_clause = install_method.using_clause(path_to_version_directory)
|
|
754
774
|
|
|
755
775
|
current_release_channel = (
|
|
756
776
|
get_app_properties().get(CHANNEL_COL) or DEFAULT_CHANNEL
|
|
@@ -828,7 +848,7 @@ class SnowflakeSQLFacade:
|
|
|
828
848
|
name: str,
|
|
829
849
|
package_name: str,
|
|
830
850
|
install_method: SameAccountInstallMethod,
|
|
831
|
-
|
|
851
|
+
path_to_version_directory: str,
|
|
832
852
|
role: str,
|
|
833
853
|
warehouse: str,
|
|
834
854
|
debug_mode: bool | None,
|
|
@@ -842,7 +862,7 @@ class SnowflakeSQLFacade:
|
|
|
842
862
|
@param name: Name of the application object
|
|
843
863
|
@param package_name: Name of the application package to install the application from
|
|
844
864
|
@param install_method: Method of installing the application
|
|
845
|
-
@param
|
|
865
|
+
@param path_to_version_directory: Path to directory in stage housing the application artifacts
|
|
846
866
|
@param role: Role to use when creating the application and provider-side objects
|
|
847
867
|
@param warehouse: Warehouse which is required to create an application object
|
|
848
868
|
@param debug_mode: Whether to enable debug mode; None means not explicitly enabled or disabled
|
|
@@ -868,7 +888,7 @@ class SnowflakeSQLFacade:
|
|
|
868
888
|
)
|
|
869
889
|
authorize_telemetry_clause = f"AUTHORIZE_TELEMETRY_EVENT_SHARING = {str(should_authorize_event_sharing).upper()}"
|
|
870
890
|
|
|
871
|
-
using_clause = install_method.using_clause(
|
|
891
|
+
using_clause = install_method.using_clause(path_to_version_directory)
|
|
872
892
|
release_channel_clause = (
|
|
873
893
|
f"using release channel {release_channel}" if release_channel else ""
|
|
874
894
|
)
|
|
@@ -1096,13 +1116,22 @@ class SnowflakeSQLFacade:
|
|
|
1096
1116
|
raise UserInputError(
|
|
1097
1117
|
f"Some target accounts are already referenced by other release directives in application package {package_name}.\n{str(err.msg)}"
|
|
1098
1118
|
) from err
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1119
|
+
if err.errno == VERSION_NOT_ADDED_TO_RELEASE_CHANNEL:
|
|
1120
|
+
raise UserInputError(
|
|
1121
|
+
f"Version {version} is not added to release channel {release_channel}. Please add it to the release channel first."
|
|
1122
|
+
) from err
|
|
1123
|
+
if err.errno == RELEASE_DIRECTIVES_VERSION_PATCH_NOT_FOUND:
|
|
1124
|
+
raise UserInputError(
|
|
1125
|
+
f"Patch {patch} for version {version} not found in application package {package_name}."
|
|
1126
|
+
) from err
|
|
1127
|
+
if err.errno == RELEASE_DIRECTIVE_UNAPPROVED_VERSION_OR_PATCH:
|
|
1128
|
+
raise UserInputError(
|
|
1129
|
+
f"Version {version}, patch {patch} has not yet been approved to release to accounts outside of this organization."
|
|
1130
|
+
) from err
|
|
1131
|
+
if err.errno == VERSION_DOES_NOT_EXIST:
|
|
1132
|
+
raise UserInputError(
|
|
1133
|
+
f"Version {version} does not exist in application package {package_name}."
|
|
1134
|
+
) from err
|
|
1106
1135
|
handle_unclassified_error(
|
|
1107
1136
|
err,
|
|
1108
1137
|
f"Failed to set release directive {release_directive} for application package {package_name}.",
|
|
@@ -1154,6 +1183,138 @@ class SnowflakeSQLFacade:
|
|
|
1154
1183
|
f"Failed to unset release directive {release_directive} for application package {package_name}.",
|
|
1155
1184
|
)
|
|
1156
1185
|
|
|
1186
|
+
def add_accounts_to_release_directive(
|
|
1187
|
+
self,
|
|
1188
|
+
package_name: str,
|
|
1189
|
+
release_directive: str,
|
|
1190
|
+
release_channel: str | None,
|
|
1191
|
+
target_accounts: List[str],
|
|
1192
|
+
role: str | None = None,
|
|
1193
|
+
):
|
|
1194
|
+
"""
|
|
1195
|
+
Adds target accounts to a release directive of a release channel in an application package.
|
|
1196
|
+
Release directive must already exist in the application package.
|
|
1197
|
+
Default release directive does not support target accounts.
|
|
1198
|
+
|
|
1199
|
+
@param package_name: Name of the application package to alter.
|
|
1200
|
+
@param release_directive: Name of the release directive to add target accounts to.
|
|
1201
|
+
@param release_channel: Name of the release channel where the release directive belongs to.
|
|
1202
|
+
@param target_accounts: List of target accounts to add to the release directive.
|
|
1203
|
+
@param [Optional] role: Role to switch to while running this script. Current role will be used if no role is passed in.
|
|
1204
|
+
"""
|
|
1205
|
+
package_name = to_identifier(package_name)
|
|
1206
|
+
release_channel = to_identifier(release_channel) if release_channel else None
|
|
1207
|
+
release_directive = to_identifier(release_directive)
|
|
1208
|
+
|
|
1209
|
+
if same_identifiers(release_directive, DEFAULT_DIRECTIVE):
|
|
1210
|
+
raise UserInputError(
|
|
1211
|
+
"Default release directive does not support adding accounts. Please specify a non-default release directive."
|
|
1212
|
+
)
|
|
1213
|
+
|
|
1214
|
+
release_channel_statement = ""
|
|
1215
|
+
if release_channel:
|
|
1216
|
+
release_channel_statement = f"modify release channel {release_channel}"
|
|
1217
|
+
|
|
1218
|
+
with self._use_role_optional(role):
|
|
1219
|
+
try:
|
|
1220
|
+
self._sql_executor.execute_query(
|
|
1221
|
+
dedent(
|
|
1222
|
+
_strip_empty_lines(
|
|
1223
|
+
f"""\
|
|
1224
|
+
alter application package {package_name}
|
|
1225
|
+
{release_channel_statement}
|
|
1226
|
+
modify release directive {release_directive}
|
|
1227
|
+
add accounts = ({','.join(target_accounts)})
|
|
1228
|
+
"""
|
|
1229
|
+
)
|
|
1230
|
+
)
|
|
1231
|
+
)
|
|
1232
|
+
except Exception as err:
|
|
1233
|
+
if isinstance(err, ProgrammingError):
|
|
1234
|
+
if (
|
|
1235
|
+
err.errno == ACCOUNT_DOES_NOT_EXIST
|
|
1236
|
+
or err.errno == ACCOUNT_HAS_TOO_MANY_QUALIFIERS
|
|
1237
|
+
):
|
|
1238
|
+
raise UserInputError(
|
|
1239
|
+
f"Invalid account passed in.\n{str(err.msg)}"
|
|
1240
|
+
) from err
|
|
1241
|
+
if err.errno == RELEASE_DIRECTIVE_DOES_NOT_EXIST:
|
|
1242
|
+
raise UserInputError(
|
|
1243
|
+
f"Release directive {release_directive} does not exist in application package {package_name}."
|
|
1244
|
+
) from err
|
|
1245
|
+
if err.errno == TARGET_ACCOUNT_USED_BY_OTHER_RELEASE_DIRECTIVE:
|
|
1246
|
+
raise UserInputError(
|
|
1247
|
+
f"Some target accounts are already referenced by other release directives in application package {package_name}.\n{str(err.msg)}"
|
|
1248
|
+
) from err
|
|
1249
|
+
handle_unclassified_error(
|
|
1250
|
+
err,
|
|
1251
|
+
f"Failed to add accounts to release directive {release_directive} for application package {package_name}.",
|
|
1252
|
+
)
|
|
1253
|
+
|
|
1254
|
+
def remove_accounts_from_release_directive(
|
|
1255
|
+
self,
|
|
1256
|
+
package_name: str,
|
|
1257
|
+
release_directive: str,
|
|
1258
|
+
release_channel: str | None,
|
|
1259
|
+
target_accounts: List[str],
|
|
1260
|
+
role: str | None = None,
|
|
1261
|
+
):
|
|
1262
|
+
"""
|
|
1263
|
+
Removes target accounts from a release directive of a release channel in an application package.
|
|
1264
|
+
Release directive must already exist in the application package.
|
|
1265
|
+
Default release directive does not support target accounts.
|
|
1266
|
+
|
|
1267
|
+
@param package_name: Name of the application package to alter.
|
|
1268
|
+
@param release_directive: Name of the release directive to remove target accounts from.
|
|
1269
|
+
@param release_channel: Name of the release channel where the release directive belongs to.
|
|
1270
|
+
@param target_accounts: List of target accounts to remove from the release directive.
|
|
1271
|
+
@param [Optional] role: Role to switch to while running this script. Current role will be used if no role is passed in.
|
|
1272
|
+
"""
|
|
1273
|
+
package_name = to_identifier(package_name)
|
|
1274
|
+
release_channel = to_identifier(release_channel) if release_channel else None
|
|
1275
|
+
release_directive = to_identifier(release_directive)
|
|
1276
|
+
|
|
1277
|
+
if same_identifiers(release_directive, DEFAULT_DIRECTIVE):
|
|
1278
|
+
raise UserInputError(
|
|
1279
|
+
"Default release directive does not support removing accounts. Please specify a non-default release directive."
|
|
1280
|
+
)
|
|
1281
|
+
|
|
1282
|
+
release_channel_statement = ""
|
|
1283
|
+
if release_channel:
|
|
1284
|
+
release_channel_statement = f"modify release channel {release_channel}"
|
|
1285
|
+
|
|
1286
|
+
with self._use_role_optional(role):
|
|
1287
|
+
try:
|
|
1288
|
+
self._sql_executor.execute_query(
|
|
1289
|
+
dedent(
|
|
1290
|
+
_strip_empty_lines(
|
|
1291
|
+
f"""\
|
|
1292
|
+
alter application package {package_name}
|
|
1293
|
+
{release_channel_statement}
|
|
1294
|
+
modify release directive {release_directive}
|
|
1295
|
+
remove accounts = ({','.join(target_accounts)})
|
|
1296
|
+
"""
|
|
1297
|
+
)
|
|
1298
|
+
)
|
|
1299
|
+
)
|
|
1300
|
+
except Exception as err:
|
|
1301
|
+
if isinstance(err, ProgrammingError):
|
|
1302
|
+
if (
|
|
1303
|
+
err.errno == ACCOUNT_DOES_NOT_EXIST
|
|
1304
|
+
or err.errno == ACCOUNT_HAS_TOO_MANY_QUALIFIERS
|
|
1305
|
+
):
|
|
1306
|
+
raise UserInputError(
|
|
1307
|
+
f"Invalid account passed in.\n{str(err.msg)}"
|
|
1308
|
+
) from err
|
|
1309
|
+
if err.errno == RELEASE_DIRECTIVE_DOES_NOT_EXIST:
|
|
1310
|
+
raise UserInputError(
|
|
1311
|
+
f"Release directive {release_directive} does not exist in application package {package_name}."
|
|
1312
|
+
) from err
|
|
1313
|
+
handle_unclassified_error(
|
|
1314
|
+
err,
|
|
1315
|
+
f"Failed to remove accounts from release directive {release_directive} for application package {package_name}.",
|
|
1316
|
+
)
|
|
1317
|
+
|
|
1157
1318
|
def show_release_channels(
|
|
1158
1319
|
self, package_name: str, role: str | None = None
|
|
1159
1320
|
) -> list[ReleaseChannel]:
|
|
@@ -1295,6 +1456,48 @@ class SnowflakeSQLFacade:
|
|
|
1295
1456
|
f"Failed to remove accounts from release channel {release_channel} in application package {package_name}.",
|
|
1296
1457
|
)
|
|
1297
1458
|
|
|
1459
|
+
def set_accounts_for_release_channel(
|
|
1460
|
+
self,
|
|
1461
|
+
package_name: str,
|
|
1462
|
+
release_channel: str,
|
|
1463
|
+
target_accounts: List[str],
|
|
1464
|
+
role: str | None = None,
|
|
1465
|
+
):
|
|
1466
|
+
"""
|
|
1467
|
+
Sets accounts for a release channel.
|
|
1468
|
+
|
|
1469
|
+
@param package_name: Name of the application package
|
|
1470
|
+
@param release_channel: Name of the release channel
|
|
1471
|
+
@param target_accounts: List of target accounts to set for the release channel
|
|
1472
|
+
@param [Optional] role: Role to switch to while running this script. Current role will be used if no role is passed in.
|
|
1473
|
+
"""
|
|
1474
|
+
|
|
1475
|
+
package_name = to_identifier(package_name)
|
|
1476
|
+
release_channel = to_identifier(release_channel)
|
|
1477
|
+
|
|
1478
|
+
with self._use_role_optional(role):
|
|
1479
|
+
try:
|
|
1480
|
+
self._sql_executor.execute_query(
|
|
1481
|
+
f"alter application package {package_name} modify release channel {release_channel} set accounts = ({','.join(target_accounts)})"
|
|
1482
|
+
)
|
|
1483
|
+
except Exception as err:
|
|
1484
|
+
if isinstance(err, ProgrammingError):
|
|
1485
|
+
if (
|
|
1486
|
+
err.errno == ACCOUNT_DOES_NOT_EXIST
|
|
1487
|
+
or err.errno == ACCOUNT_HAS_TOO_MANY_QUALIFIERS
|
|
1488
|
+
):
|
|
1489
|
+
raise UserInputError(
|
|
1490
|
+
f"Invalid account passed in.\n{str(err.msg)}"
|
|
1491
|
+
) from err
|
|
1492
|
+
if err.errno == CANNOT_MODIFY_RELEASE_CHANNEL_ACCOUNTS:
|
|
1493
|
+
raise UserInputError(
|
|
1494
|
+
f"Cannot modify accounts for release channel {release_channel} in application package {package_name}."
|
|
1495
|
+
) from err
|
|
1496
|
+
handle_unclassified_error(
|
|
1497
|
+
err,
|
|
1498
|
+
f"Failed to set accounts for release channel {release_channel} in application package {package_name}.",
|
|
1499
|
+
)
|
|
1500
|
+
|
|
1298
1501
|
def add_version_to_release_channel(
|
|
1299
1502
|
self,
|
|
1300
1503
|
package_name: str,
|
|
@@ -1424,26 +1627,3 @@ def _strip_empty_lines(text: str) -> str:
|
|
|
1424
1627
|
other_lines = [line for line in all_lines[:-1] if line.strip()]
|
|
1425
1628
|
|
|
1426
1629
|
return "\n".join(other_lines) + "\n" + last_line
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
def _handle_release_directive_version_error(
|
|
1430
|
-
err: ProgrammingError,
|
|
1431
|
-
*,
|
|
1432
|
-
package_name: str,
|
|
1433
|
-
release_channel: str | None,
|
|
1434
|
-
version: str,
|
|
1435
|
-
patch: int,
|
|
1436
|
-
) -> None:
|
|
1437
|
-
|
|
1438
|
-
if err.errno == VERSION_NOT_ADDED_TO_RELEASE_CHANNEL:
|
|
1439
|
-
raise UserInputError(
|
|
1440
|
-
f"Version {version} is not added to release channel {release_channel}. Please add it to the release channel first."
|
|
1441
|
-
) from err
|
|
1442
|
-
if err.errno == RELEASE_DIRECTIVES_VERSION_PATCH_NOT_FOUND:
|
|
1443
|
-
raise UserInputError(
|
|
1444
|
-
f"Patch {patch} for version {version} not found in application package {package_name}."
|
|
1445
|
-
) from err
|
|
1446
|
-
if err.errno == VERSION_DOES_NOT_EXIST:
|
|
1447
|
-
raise UserInputError(
|
|
1448
|
-
f"Version {version} does not exist in application package {package_name}."
|
|
1449
|
-
) from err
|
|
@@ -46,7 +46,7 @@ APP_AND_PACKAGE_OPTIONS = [
|
|
|
46
46
|
annotation=Optional[str],
|
|
47
47
|
default=typer.Option(
|
|
48
48
|
default="",
|
|
49
|
-
help="The ID of the package entity on which to operate when definition_version is 2 or higher.",
|
|
49
|
+
help="The ID of the package entity on which to operate when the definition_version is 2 or higher.",
|
|
50
50
|
),
|
|
51
51
|
),
|
|
52
52
|
inspect.Parameter(
|
|
@@ -55,7 +55,7 @@ APP_AND_PACKAGE_OPTIONS = [
|
|
|
55
55
|
annotation=Optional[str],
|
|
56
56
|
default=typer.Option(
|
|
57
57
|
default="",
|
|
58
|
-
help="The ID of the application entity on which to operate when definition_version is 2 or higher.",
|
|
58
|
+
help="The ID of the application entity on which to operate when the definition_version is 2 or higher.",
|
|
59
59
|
),
|
|
60
60
|
),
|
|
61
61
|
]
|
|
@@ -29,7 +29,7 @@ from snowflake.cli.api.commands.decorators import (
|
|
|
29
29
|
with_project_definition,
|
|
30
30
|
)
|
|
31
31
|
from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
|
|
32
|
-
from snowflake.cli.api.entities.
|
|
32
|
+
from snowflake.cli.api.entities.utils import EntityActions
|
|
33
33
|
from snowflake.cli.api.output.formats import OutputFormat
|
|
34
34
|
from snowflake.cli.api.output.types import (
|
|
35
35
|
CollectionResult,
|
|
@@ -15,12 +15,26 @@
|
|
|
15
15
|
import logging
|
|
16
16
|
|
|
17
17
|
import typer
|
|
18
|
+
from click import UsageError
|
|
18
19
|
from snowflake.cli._plugins.notebook.manager import NotebookManager
|
|
20
|
+
from snowflake.cli._plugins.notebook.notebook_entity_model import NotebookEntityModel
|
|
19
21
|
from snowflake.cli._plugins.notebook.types import NotebookStagePath
|
|
20
|
-
from snowflake.cli.
|
|
22
|
+
from snowflake.cli._plugins.workspace.manager import WorkspaceManager
|
|
23
|
+
from snowflake.cli.api.cli_global_context import get_cli_context
|
|
24
|
+
from snowflake.cli.api.commands.decorators import with_project_definition
|
|
25
|
+
from snowflake.cli.api.commands.flags import (
|
|
26
|
+
ReplaceOption,
|
|
27
|
+
entity_argument,
|
|
28
|
+
identifier_argument,
|
|
29
|
+
)
|
|
21
30
|
from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
|
|
31
|
+
from snowflake.cli.api.commands.utils import get_entity_for_operation
|
|
32
|
+
from snowflake.cli.api.entities.common import EntityActions
|
|
22
33
|
from snowflake.cli.api.identifiers import FQN
|
|
23
|
-
from snowflake.cli.api.output.types import
|
|
34
|
+
from snowflake.cli.api.output.types import (
|
|
35
|
+
CommandResult,
|
|
36
|
+
MessageResult,
|
|
37
|
+
)
|
|
24
38
|
from typing_extensions import Annotated
|
|
25
39
|
|
|
26
40
|
app = SnowTyperFactory(
|
|
@@ -84,3 +98,41 @@ def create(
|
|
|
84
98
|
notebook_file=notebook_file,
|
|
85
99
|
)
|
|
86
100
|
return MessageResult(message=notebook_url)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@app.command(requires_connection=True)
|
|
104
|
+
@with_project_definition()
|
|
105
|
+
def deploy(
|
|
106
|
+
entity_id: str = entity_argument("notebook"),
|
|
107
|
+
replace: bool = ReplaceOption(
|
|
108
|
+
help="Replace notebook object if it already exists. It only uploads new and overwrites existing files, "
|
|
109
|
+
"but does not remove any files already on the stage.",
|
|
110
|
+
),
|
|
111
|
+
**options,
|
|
112
|
+
) -> CommandResult:
|
|
113
|
+
"""Uploads a notebook and required files to a stage and creates a Snowflake notebook."""
|
|
114
|
+
cli_context = get_cli_context()
|
|
115
|
+
pd = cli_context.project_definition
|
|
116
|
+
if not pd.meets_version_requirement("2"):
|
|
117
|
+
raise UsageError(
|
|
118
|
+
"This command requires project definition of version at least 2."
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
notebook: NotebookEntityModel = get_entity_for_operation(
|
|
122
|
+
cli_context=cli_context,
|
|
123
|
+
entity_id=entity_id,
|
|
124
|
+
project_definition=pd,
|
|
125
|
+
entity_type="notebook",
|
|
126
|
+
)
|
|
127
|
+
ws = WorkspaceManager(
|
|
128
|
+
project_definition=cli_context.project_definition,
|
|
129
|
+
project_root=cli_context.project_root,
|
|
130
|
+
)
|
|
131
|
+
notebook_url = ws.perform_action(
|
|
132
|
+
notebook.entity_id,
|
|
133
|
+
EntityActions.DEPLOY,
|
|
134
|
+
replace=replace,
|
|
135
|
+
)
|
|
136
|
+
return MessageResult(
|
|
137
|
+
f"Notebook successfully deployed and available under {notebook_url}"
|
|
138
|
+
)
|
|
@@ -16,7 +16,7 @@ from pathlib import Path
|
|
|
16
16
|
from textwrap import dedent
|
|
17
17
|
|
|
18
18
|
from snowflake.cli._plugins.connection.util import make_snowsight_url
|
|
19
|
-
from snowflake.cli._plugins.notebook.exceptions import
|
|
19
|
+
from snowflake.cli._plugins.notebook.exceptions import NotebookFilePathError
|
|
20
20
|
from snowflake.cli._plugins.notebook.types import NotebookStagePath
|
|
21
21
|
from snowflake.cli.api.cli_global_context import get_cli_context
|
|
22
22
|
from snowflake.cli.api.identifiers import FQN
|
|
@@ -40,11 +40,11 @@ class NotebookManager(SqlExecutionMixin):
|
|
|
40
40
|
def parse_stage_as_path(notebook_file: str) -> Path:
|
|
41
41
|
"""Parses notebook file path to pathlib.Path."""
|
|
42
42
|
if not notebook_file.endswith(".ipynb"):
|
|
43
|
-
raise
|
|
43
|
+
raise NotebookFilePathError(notebook_file)
|
|
44
44
|
# we don't perform any operations on the path, so we don't need to differentiate git repository paths
|
|
45
45
|
stage_path = StagePath.from_stage_str(notebook_file)
|
|
46
46
|
if len(stage_path.parts) < 1:
|
|
47
|
-
raise
|
|
47
|
+
raise NotebookFilePathError(notebook_file)
|
|
48
48
|
|
|
49
49
|
return stage_path
|
|
50
50
|
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
|
|
3
|
+
from click import ClickException
|
|
4
|
+
from snowflake.cli._plugins.connection.util import make_snowsight_url
|
|
5
|
+
from snowflake.cli._plugins.notebook.notebook_entity_model import NotebookEntityModel
|
|
6
|
+
from snowflake.cli._plugins.notebook.notebook_project_paths import NotebookProjectPaths
|
|
7
|
+
from snowflake.cli._plugins.stage.manager import StageManager
|
|
8
|
+
from snowflake.cli._plugins.workspace.context import ActionContext
|
|
9
|
+
from snowflake.cli.api.artifacts.utils import bundle_artifacts
|
|
10
|
+
from snowflake.cli.api.cli_global_context import get_cli_context
|
|
11
|
+
from snowflake.cli.api.console.console import cli_console
|
|
12
|
+
from snowflake.cli.api.entities.common import EntityBase
|
|
13
|
+
from snowflake.cli.api.stage_path import StagePath
|
|
14
|
+
from snowflake.connector import ProgrammingError
|
|
15
|
+
from snowflake.connector.cursor import SnowflakeCursor
|
|
16
|
+
|
|
17
|
+
_DEFAULT_NOTEBOOK_STAGE_NAME = "@notebooks"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class NotebookEntity(EntityBase[NotebookEntityModel]):
|
|
21
|
+
"""
|
|
22
|
+
A notebook.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
@functools.cached_property
|
|
26
|
+
def _stage_path(self) -> StagePath:
|
|
27
|
+
stage_path = self.model.stage_path
|
|
28
|
+
if stage_path is None:
|
|
29
|
+
stage_path = f"{_DEFAULT_NOTEBOOK_STAGE_NAME}/{self.fqn.name}"
|
|
30
|
+
return StagePath.from_stage_str(stage_path)
|
|
31
|
+
|
|
32
|
+
@functools.cached_property
|
|
33
|
+
def _project_paths(self):
|
|
34
|
+
return NotebookProjectPaths(get_cli_context().project_root)
|
|
35
|
+
|
|
36
|
+
def _object_exists(self) -> bool:
|
|
37
|
+
# currently notebook objects are not supported by object manager - re-implementing "exists"
|
|
38
|
+
try:
|
|
39
|
+
self.action_describe()
|
|
40
|
+
return True
|
|
41
|
+
except ProgrammingError:
|
|
42
|
+
return False
|
|
43
|
+
|
|
44
|
+
def _upload_artifacts(self):
|
|
45
|
+
stage_fqn = self._stage_path.stage_fqn
|
|
46
|
+
stage_manager = StageManager()
|
|
47
|
+
cli_console.step(f"Creating stage {stage_fqn} if not exists")
|
|
48
|
+
stage_manager.create(fqn=stage_fqn)
|
|
49
|
+
|
|
50
|
+
cli_console.step("Uploading artifacts")
|
|
51
|
+
|
|
52
|
+
# creating bundle map to handle glob patterns logic
|
|
53
|
+
bundle_map = bundle_artifacts(self._project_paths, self.model.artifacts)
|
|
54
|
+
for absolute_src, absolute_dest in bundle_map.all_mappings(
|
|
55
|
+
absolute=True, expand_directories=True
|
|
56
|
+
):
|
|
57
|
+
artifact_stage_path = self._stage_path / (
|
|
58
|
+
absolute_dest.relative_to(self._project_paths.bundle_root).parent
|
|
59
|
+
)
|
|
60
|
+
stage_manager.put(
|
|
61
|
+
local_path=absolute_src, stage_path=artifact_stage_path, overwrite=True
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def get_create_sql(self, replace: bool) -> str:
|
|
65
|
+
main_file_stage_path = self._stage_path / (
|
|
66
|
+
self.model.notebook_file.absolute().relative_to(
|
|
67
|
+
self._project_paths.project_root
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
query = "CREATE OR REPLACE " if replace else "CREATE "
|
|
71
|
+
query += (
|
|
72
|
+
f"NOTEBOOK {self.fqn.sql_identifier}\n"
|
|
73
|
+
f"FROM '{main_file_stage_path.stage_with_at}'\n"
|
|
74
|
+
f"QUERY_WAREHOUSE = '{self.model.query_warehouse}'\n"
|
|
75
|
+
f"MAIN_FILE = '{main_file_stage_path.path}'"
|
|
76
|
+
)
|
|
77
|
+
if self.model.compute_pool:
|
|
78
|
+
query += f"\nCOMPUTE_POOL = '{self.model.compute_pool}'"
|
|
79
|
+
if self.model.runtime_name:
|
|
80
|
+
query += f"\nRUNTIME_NAME = '{self.model.runtime_name}'"
|
|
81
|
+
|
|
82
|
+
query += (
|
|
83
|
+
";\n// Cannot use IDENTIFIER(...)"
|
|
84
|
+
f"\nALTER NOTEBOOK {self.fqn.identifier} ADD LIVE VERSION FROM LAST;"
|
|
85
|
+
)
|
|
86
|
+
return query
|
|
87
|
+
|
|
88
|
+
def action_describe(self) -> SnowflakeCursor:
|
|
89
|
+
return self._sql_executor.execute_query(self.get_describe_sql())
|
|
90
|
+
|
|
91
|
+
def action_create(self, replace: bool) -> str:
|
|
92
|
+
self._sql_executor.execute_query(self.get_create_sql(replace))
|
|
93
|
+
return make_snowsight_url(
|
|
94
|
+
self._conn,
|
|
95
|
+
f"/#/notebooks/{self.fqn.using_connection(self._conn).url_identifier}",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def action_deploy(
|
|
99
|
+
self,
|
|
100
|
+
action_ctx: ActionContext,
|
|
101
|
+
replace: bool,
|
|
102
|
+
*args,
|
|
103
|
+
**kwargs,
|
|
104
|
+
) -> str:
|
|
105
|
+
if self._object_exists():
|
|
106
|
+
if not replace:
|
|
107
|
+
raise ClickException(
|
|
108
|
+
f"Notebook {self.fqn.name} already exists. Consider using --replace."
|
|
109
|
+
)
|
|
110
|
+
with cli_console.phase(f"Uploading artifacts to {self._stage_path}"):
|
|
111
|
+
self._upload_artifacts()
|
|
112
|
+
with cli_console.phase(f"Creating notebook {self.fqn}"):
|
|
113
|
+
return self.action_create(replace=replace)
|
|
114
|
+
|
|
115
|
+
# complementary actions, currently not used - to be implemented in future
|
|
116
|
+
def action_drop(self, *args, **kwargs):
|
|
117
|
+
raise ClickException("action DROP not supported by NOTEBOOK entity")
|
|
118
|
+
|
|
119
|
+
def action_teardown(self, *args, **kwargs):
|
|
120
|
+
raise ClickException("action TEARDOWN not supported by NOTEBOOK entity")
|