snowflake-cli 3.2.2__py3-none-any.whl → 3.4.1__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 +224 -192
- snowflake/cli/_app/commands_registration/commands_registration_with_callbacks.py +1 -27
- snowflake/cli/_app/constants.py +4 -0
- snowflake/cli/_app/snow_connector.py +12 -0
- snowflake/cli/_app/telemetry.py +10 -3
- snowflake/cli/_plugins/connection/util.py +12 -19
- snowflake/cli/_plugins/cortex/commands.py +2 -4
- snowflake/cli/_plugins/git/manager.py +1 -1
- snowflake/cli/_plugins/helpers/commands.py +207 -1
- snowflake/cli/_plugins/nativeapp/artifacts.py +16 -628
- 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 +42 -20
- snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +9 -2
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +6 -3
- snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +44 -34
- snowflake/cli/_plugins/nativeapp/commands.py +113 -21
- snowflake/cli/_plugins/nativeapp/constants.py +5 -0
- snowflake/cli/_plugins/nativeapp/entities/application.py +226 -296
- snowflake/cli/_plugins/nativeapp/entities/application_package.py +911 -141
- snowflake/cli/_plugins/nativeapp/entities/application_package_child_interface.py +43 -0
- snowflake/cli/_plugins/nativeapp/feature_flags.py +5 -1
- snowflake/cli/_plugins/nativeapp/release_channel/__init__.py +13 -0
- snowflake/cli/_plugins/nativeapp/release_channel/commands.py +246 -0
- snowflake/cli/_plugins/nativeapp/release_directive/__init__.py +13 -0
- snowflake/cli/_plugins/nativeapp/release_directive/commands.py +243 -0
- snowflake/cli/_plugins/nativeapp/same_account_install_method.py +9 -17
- snowflake/cli/_plugins/nativeapp/sf_facade_exceptions.py +80 -0
- snowflake/cli/_plugins/nativeapp/sf_sql_facade.py +1184 -80
- snowflake/cli/_plugins/nativeapp/utils.py +11 -0
- snowflake/cli/_plugins/nativeapp/v2_conversions/compat.py +7 -3
- snowflake/cli/_plugins/nativeapp/version/commands.py +32 -5
- snowflake/cli/_plugins/notebook/commands.py +55 -2
- snowflake/cli/_plugins/notebook/exceptions.py +1 -1
- snowflake/cli/_plugins/notebook/manager.py +7 -5
- 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/snowpark/commands.py +48 -30
- snowflake/cli/_plugins/snowpark/common.py +47 -2
- snowflake/cli/_plugins/snowpark/snowpark_entity.py +247 -4
- 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/common.py +129 -0
- snowflake/cli/_plugins/spcs/services/commands.py +131 -14
- snowflake/cli/_plugins/spcs/services/manager.py +169 -1
- snowflake/cli/_plugins/stage/commands.py +2 -1
- snowflake/cli/_plugins/stage/diff.py +60 -39
- snowflake/cli/_plugins/stage/manager.py +34 -13
- snowflake/cli/_plugins/stage/utils.py +1 -1
- snowflake/cli/_plugins/streamlit/commands.py +10 -1
- snowflake/cli/_plugins/streamlit/manager.py +70 -22
- snowflake/cli/_plugins/streamlit/streamlit_entity.py +131 -1
- 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 +6 -5
- snowflake/cli/_plugins/workspace/manager.py +9 -5
- snowflake/cli/api/artifacts/__init__.py +13 -0
- snowflake/cli/api/artifacts/bundle_map.py +500 -0
- snowflake/cli/api/artifacts/common.py +78 -0
- snowflake/cli/api/artifacts/utils.py +82 -0
- snowflake/cli/api/cli_global_context.py +36 -2
- snowflake/cli/api/commands/flags.py +10 -4
- snowflake/cli/api/commands/utils.py +28 -2
- snowflake/cli/api/config.py +6 -2
- snowflake/cli/api/connections.py +12 -1
- snowflake/cli/api/constants.py +10 -1
- snowflake/cli/api/entities/common.py +81 -14
- snowflake/cli/api/entities/resolver.py +160 -0
- snowflake/cli/api/entities/utils.py +65 -23
- snowflake/cli/api/errno.py +63 -3
- snowflake/cli/api/feature_flags.py +19 -4
- snowflake/cli/api/metrics.py +21 -27
- snowflake/cli/api/project/definition_conversion.py +4 -4
- 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 +4 -0
- snowflake/cli/api/project/schemas/project_definition.py +54 -6
- 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/project/schemas/v1/streamlit/streamlit.py +1 -1
- snowflake/cli/api/project/util.py +45 -0
- 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.2.2.dist-info → snowflake_cli-3.4.1.dist-info}/METADATA +14 -15
- {snowflake_cli-3.2.2.dist-info → snowflake_cli-3.4.1.dist-info}/RECORD +96 -82
- {snowflake_cli-3.2.2.dist-info → snowflake_cli-3.4.1.dist-info}/WHEEL +1 -1
- snowflake/cli/api/project/schemas/v1/native_app/path_mapping.py +0 -65
- {snowflake_cli-3.2.2.dist-info → snowflake_cli-3.4.1.dist-info}/entry_points.txt +0 -0
- {snowflake_cli-3.2.2.dist-info → snowflake_cli-3.4.1.dist-info}/licenses/LICENSE +0 -0
snowflake/cli/api/errno.py
CHANGED
|
@@ -14,17 +14,77 @@
|
|
|
14
14
|
|
|
15
15
|
# General errors
|
|
16
16
|
NO_WAREHOUSE_SELECTED_IN_SESSION = 606
|
|
17
|
+
EMPTY_SQL_STATEMENT = 900
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
SQL_COMPILATION_ERROR = 1003
|
|
20
|
+
OBJECT_ALREADY_EXISTS_IN_DOMAIN = 1998
|
|
21
|
+
OBJECT_ALREADY_EXISTS = 2002
|
|
22
|
+
DOES_NOT_EXIST_OR_NOT_AUTHORIZED = 2003 # BASE_TABLE_OR_VIEW_NOT_FOUND
|
|
23
|
+
DUPLICATE_COLUMN_NAME = 2025
|
|
24
|
+
VIEW_EXPANSION_FAILED = 2037
|
|
25
|
+
DOES_NOT_EXIST_OR_CANNOT_BE_PERFORMED = (
|
|
26
|
+
2043 # OBJECT_DOES_NOT_EXIST_OR_CANNOT_PERFORM_OPERATION
|
|
27
|
+
)
|
|
28
|
+
INSUFFICIENT_PRIVILEGES = 3001 # NOT_AUTHORIZED
|
|
29
|
+
INVALID_OBJECT_TYPE_FOR_SPECIFIED_PRIVILEGE = 3008
|
|
30
|
+
ROLE_NOT_ASSIGNED = 3013
|
|
31
|
+
NO_INDIVIDUAL_PRIVS = 3028
|
|
32
|
+
OBJECT_ALREADY_EXISTS_NO_PRIVILEGES = 3041
|
|
21
33
|
|
|
22
34
|
# Native Apps
|
|
35
|
+
APPLICATION_PACKAGE_MANIFEST_SPECIFIED_FILE_NOT_FOUND = 93003
|
|
36
|
+
APPLICATION_FILE_NOT_FOUND_ON_STAGE = 93009
|
|
37
|
+
CANNOT_GRANT_OBJECT_NOT_IN_APP_PACKAGE = 93011
|
|
38
|
+
CANNOT_GRANT_RESTRICTED_PRIVILEGE_TO_APP_PACKAGE_SHARE = 93012
|
|
39
|
+
APPLICATION_PACKAGE_VERSION_ALREADY_EXISTS = 93030
|
|
40
|
+
APPLICATION_PACKAGE_VERSION_NAME_TOO_LONG = 93035
|
|
41
|
+
APPLICATION_PACKAGE_PATCH_DOES_NOT_EXIST = 93036
|
|
42
|
+
APPLICATION_PACKAGE_MAX_VERSIONS_HIT = 93037
|
|
23
43
|
CANNOT_UPGRADE_FROM_LOOSE_FILES_TO_VERSION = 93044
|
|
24
44
|
CANNOT_UPGRADE_FROM_VERSION_TO_LOOSE_FILES = 93045
|
|
25
45
|
ONLY_SUPPORTED_ON_DEV_MODE_APPLICATIONS = 93046
|
|
46
|
+
NO_VERSIONS_AVAILABLE_FOR_ACCOUNT = 93054
|
|
26
47
|
NOT_SUPPORTED_ON_DEV_MODE_APPLICATIONS = 93055
|
|
27
48
|
APPLICATION_NO_LONGER_AVAILABLE = 93079
|
|
49
|
+
APPLICATION_INSTANCE_FAILED_TO_RUN_SETUP_SCRIPT = 93082
|
|
50
|
+
APPLICATION_INSTANCE_NO_ACTIVE_WAREHOUSE_FOR_CREATE_OR_UPGRADE = 93083
|
|
51
|
+
APPLICATION_INSTANCE_EMPTY_SETUP_SCRIPT = 93084
|
|
52
|
+
VERSION_REFERENCED_BY_RELEASE_DIRECTIVE = 93088
|
|
53
|
+
APPLICATION_PACKAGE_MANIFEST_CONTAINER_IMAGE_URL_BAD_VALUE = 93148
|
|
54
|
+
CANNOT_GRANT_NON_MANIFEST_PRIVILEGE = 93118
|
|
28
55
|
APPLICATION_OWNS_EXTERNAL_OBJECTS = 93128
|
|
56
|
+
APPLICATION_PACKAGE_PATCH_ALREADY_EXISTS = 93168
|
|
57
|
+
CANNOT_ADD_PATCH_WITH_NON_INCREASING_PATCH_NUMBER = 93167
|
|
58
|
+
APPLICATION_PACKAGE_CANNOT_SET_EXTERNAL_DISTRIBUTION_WITH_SPCS = 93197
|
|
59
|
+
NATIVE_APPLICATION_MANIFEST_UNRECOGNIZED_FIELD = 93301
|
|
60
|
+
NATIVE_APPLICATION_MANIFEST_UNEXPECTED_VALUE_FOR_PROPERTY = 93302
|
|
61
|
+
NATIVE_APPLICATION_MANIFEST_GENERIC_JSON_ERROR = 93303
|
|
62
|
+
NATIVE_APPLICATION_MANIFEST_INVALID_SYNTAX = 93300
|
|
29
63
|
APPLICATION_REQUIRES_TELEMETRY_SHARING = 93321
|
|
30
64
|
CANNOT_DISABLE_MANDATORY_TELEMETRY = 93329
|
|
65
|
+
VERSION_NOT_ADDED_TO_RELEASE_CHANNEL = 512008
|
|
66
|
+
CANNOT_DISABLE_RELEASE_CHANNELS = 512001
|
|
67
|
+
RELEASE_DIRECTIVES_VERSION_PATCH_NOT_FOUND = 93036
|
|
68
|
+
RELEASE_DIRECTIVE_UNAPPROVED_VERSION_OR_PATCH = 93074
|
|
69
|
+
RELEASE_DIRECTIVE_DOES_NOT_EXIST = 93090
|
|
70
|
+
VERSION_DOES_NOT_EXIST = 93031
|
|
71
|
+
CANNOT_CREATE_VERSION_WITH_NON_ZERO_PATCH = 93170
|
|
72
|
+
VERSION_NOT_IN_RELEASE_CHANNEL = 512010
|
|
73
|
+
ACCOUNT_DOES_NOT_EXIST = 1999
|
|
74
|
+
ACCOUNT_HAS_TOO_MANY_QUALIFIERS = 906
|
|
75
|
+
CANNOT_MODIFY_RELEASE_CHANNEL_ACCOUNTS = 512017
|
|
76
|
+
VERSION_ALREADY_ADDED_TO_RELEASE_CHANNEL = 512005
|
|
77
|
+
MAX_VERSIONS_IN_RELEASE_CHANNEL_REACHED = 512004
|
|
78
|
+
MAX_UNBOUND_VERSIONS_REACHED = 512023
|
|
79
|
+
CANNOT_DEREGISTER_VERSION_ASSOCIATED_WITH_CHANNEL = 512021
|
|
80
|
+
TARGET_ACCOUNT_USED_BY_OTHER_RELEASE_DIRECTIVE = 93091
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
ERR_JAVASCRIPT_EXECUTION = 100132
|
|
84
|
+
|
|
85
|
+
SNOWSERVICES_IMAGE_REPOSITORY_IMAGE_IMPORT_TO_NATIVE_APP_FAIL = 397007
|
|
86
|
+
SNOWSERVICES_IMAGE_MANIFEST_NOT_FOUND = 397012
|
|
87
|
+
SNOWSERVICES_IMAGE_REPOSITORY_FAILS_TO_RETRIEVE_IMAGE_HASH_NEW = 397013
|
|
88
|
+
|
|
89
|
+
NO_REFERENCE_SET_FOR_DEFINITION = 505019
|
|
90
|
+
NO_ACTIVE_REF_DEFINITION_WITH_REF_NAME_IN_APPLICATION = 505026
|
|
@@ -24,20 +24,31 @@ from snowflake.cli.api.config import (
|
|
|
24
24
|
|
|
25
25
|
class BooleanFlag(NamedTuple):
|
|
26
26
|
name: str
|
|
27
|
-
default: bool = False
|
|
27
|
+
default: bool | None = False
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
@unique
|
|
31
31
|
class FeatureFlagMixin(Enum):
|
|
32
|
-
def
|
|
32
|
+
def get_value(self) -> bool | None:
|
|
33
33
|
return get_config_bool_value(
|
|
34
34
|
*FEATURE_FLAGS_SECTION_PATH,
|
|
35
35
|
key=self.value.name.lower(),
|
|
36
36
|
default=self.value.default,
|
|
37
37
|
)
|
|
38
38
|
|
|
39
|
-
def
|
|
40
|
-
return
|
|
39
|
+
def is_enabled(self) -> bool:
|
|
40
|
+
return self.get_value() is True
|
|
41
|
+
|
|
42
|
+
def is_disabled(self) -> bool:
|
|
43
|
+
return self.get_value() is False
|
|
44
|
+
|
|
45
|
+
def is_set(self) -> bool:
|
|
46
|
+
return (
|
|
47
|
+
get_config_bool_value(
|
|
48
|
+
*FEATURE_FLAGS_SECTION_PATH, key=self.value.name.lower(), default=None
|
|
49
|
+
)
|
|
50
|
+
is not None
|
|
51
|
+
)
|
|
41
52
|
|
|
42
53
|
def env_variable(self):
|
|
43
54
|
return get_env_variable_name(*FEATURE_FLAGS_SECTION_PATH, key=self.value.name)
|
|
@@ -52,3 +63,7 @@ class FeatureFlag(FeatureFlagMixin):
|
|
|
52
63
|
ENABLE_STREAMLIT_VERSIONED_STAGE = BooleanFlag(
|
|
53
64
|
"ENABLE_STREAMLIT_VERSIONED_STAGE", False
|
|
54
65
|
)
|
|
66
|
+
ENABLE_SEPARATE_AUTHENTICATION_POLICY_ID = BooleanFlag(
|
|
67
|
+
"ENABLE_SEPARATE_AUTHENTICATION_POLICY_ID", False
|
|
68
|
+
)
|
|
69
|
+
ENABLE_SNOWPARK_GLOB_SUPPORT = BooleanFlag("ENABLE_SNOWPARK_GLOB_SUPPORT", False)
|
snowflake/cli/api/metrics.py
CHANGED
|
@@ -116,10 +116,8 @@ class CLIMetricsSpan:
|
|
|
116
116
|
children: Set[CLIMetricsSpan] = field(init=False, default_factory=set)
|
|
117
117
|
|
|
118
118
|
# private state
|
|
119
|
-
# start time of the step from
|
|
120
|
-
|
|
121
|
-
init=False, default_factory=lambda: time.monotonic()
|
|
122
|
-
)
|
|
119
|
+
# start time of the step from a performance counter in order to calculate execution time
|
|
120
|
+
_start_time: float = field(init=False, default_factory=time.perf_counter)
|
|
123
121
|
|
|
124
122
|
def __hash__(self) -> int:
|
|
125
123
|
return hash(self.span_id)
|
|
@@ -154,7 +152,7 @@ class CLIMetricsSpan:
|
|
|
154
152
|
if error:
|
|
155
153
|
self.error = error
|
|
156
154
|
|
|
157
|
-
self.execution_time = time.
|
|
155
|
+
self.execution_time = time.perf_counter() - self._start_time
|
|
158
156
|
|
|
159
157
|
def to_dict(self) -> Dict:
|
|
160
158
|
"""
|
|
@@ -191,9 +189,10 @@ class CLIMetrics:
|
|
|
191
189
|
_in_progress_spans: List[CLIMetricsSpan] = field(init=False, default_factory=list)
|
|
192
190
|
# list of finished steps for telemetry to process
|
|
193
191
|
_completed_spans: List[CLIMetricsSpan] = field(init=False, default_factory=list)
|
|
194
|
-
#
|
|
195
|
-
|
|
196
|
-
|
|
192
|
+
# clock time of a performance counter when this class was initialized
|
|
193
|
+
# to approximate when the command first started executing
|
|
194
|
+
_start_time: float = field(
|
|
195
|
+
init=False, default_factory=time.perf_counter, compare=False
|
|
197
196
|
)
|
|
198
197
|
|
|
199
198
|
def clone(self) -> CLIMetrics:
|
|
@@ -223,7 +222,7 @@ class CLIMetrics:
|
|
|
223
222
|
return self._in_progress_spans[-1] if len(self._in_progress_spans) > 0 else None
|
|
224
223
|
|
|
225
224
|
@contextmanager
|
|
226
|
-
def
|
|
225
|
+
def span(self, name: str) -> Iterator[CLIMetricsSpan]:
|
|
227
226
|
"""
|
|
228
227
|
Starts a new span that tracks various metrics throughout its execution
|
|
229
228
|
|
|
@@ -236,7 +235,7 @@ class CLIMetrics:
|
|
|
236
235
|
"""
|
|
237
236
|
new_span = CLIMetricsSpan(
|
|
238
237
|
name=name,
|
|
239
|
-
start_time=time.
|
|
238
|
+
start_time=time.perf_counter() - self._start_time,
|
|
240
239
|
parent=self.current_span,
|
|
241
240
|
)
|
|
242
241
|
|
|
@@ -275,34 +274,29 @@ class CLIMetrics:
|
|
|
275
274
|
@property
|
|
276
275
|
def completed_spans(self) -> List[Dict]:
|
|
277
276
|
"""
|
|
278
|
-
Returns the completed spans tracked throughout a command
|
|
277
|
+
Returns the completed spans tracked throughout a command for reporting telemetry
|
|
279
278
|
|
|
280
279
|
Ensures that the spans we send are within the configured limits and marks
|
|
281
280
|
certain spans as trimmed if their children would bypass the limits we set
|
|
282
281
|
"""
|
|
283
282
|
# take spans breadth-first within the depth and total limits
|
|
284
283
|
# since we care more about the big picture than granularity
|
|
285
|
-
spans_to_report =
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
key=lambda span: (span.span_depth, span.start_time),
|
|
294
|
-
)
|
|
284
|
+
spans_to_report = nsmallest(
|
|
285
|
+
n=self.SPAN_TOTAL_LIMIT,
|
|
286
|
+
iterable=(
|
|
287
|
+
span
|
|
288
|
+
for span in self._completed_spans
|
|
289
|
+
if span.span_depth <= self.SPAN_DEPTH_LIMIT
|
|
290
|
+
),
|
|
291
|
+
key=lambda span: (span.span_depth, span.start_time, span.execution_time),
|
|
295
292
|
)
|
|
296
293
|
|
|
297
|
-
|
|
298
|
-
sorted_spans_to_report = sorted(
|
|
299
|
-
spans_to_report, key=lambda span: span.start_time
|
|
300
|
-
)
|
|
294
|
+
spans_to_report_set = set(spans_to_report)
|
|
301
295
|
|
|
302
296
|
return [
|
|
303
297
|
{
|
|
304
298
|
**span.to_dict(),
|
|
305
|
-
CLIMetricsSpan.TRIMMED_KEY: not span.children <=
|
|
299
|
+
CLIMetricsSpan.TRIMMED_KEY: not span.children <= spans_to_report_set,
|
|
306
300
|
}
|
|
307
|
-
for span in
|
|
301
|
+
for span in spans_to_report
|
|
308
302
|
]
|
|
@@ -13,7 +13,6 @@ from snowflake.cli._plugins.nativeapp.artifacts import (
|
|
|
13
13
|
bundle_artifacts,
|
|
14
14
|
)
|
|
15
15
|
from snowflake.cli._plugins.nativeapp.bundle_context import BundleContext
|
|
16
|
-
from snowflake.cli._plugins.nativeapp.codegen.compiler import TEMPLATES_PROCESSOR
|
|
17
16
|
from snowflake.cli._plugins.nativeapp.codegen.templates.templates_processor import (
|
|
18
17
|
TemplatesProcessor,
|
|
19
18
|
)
|
|
@@ -223,10 +222,11 @@ def convert_streamlit_to_v2_data(streamlit: Streamlit) -> Dict[str, Any]:
|
|
|
223
222
|
environment_file,
|
|
224
223
|
pages_dir,
|
|
225
224
|
]
|
|
226
|
-
artifacts = [a for a in artifacts if a is not None]
|
|
225
|
+
artifacts = [str(a) for a in artifacts if a is not None]
|
|
227
226
|
|
|
228
227
|
if streamlit.additional_source_files:
|
|
229
|
-
|
|
228
|
+
for additional_file in streamlit.additional_source_files:
|
|
229
|
+
artifacts.append(str(additional_file))
|
|
230
230
|
|
|
231
231
|
identifier = {"name": streamlit.name}
|
|
232
232
|
if streamlit.schema_name:
|
|
@@ -457,7 +457,7 @@ def _convert_templates_in_files(
|
|
|
457
457
|
artifact
|
|
458
458
|
for artifact in pkg_model.artifacts
|
|
459
459
|
for processor in artifact.processors
|
|
460
|
-
if processor.name ==
|
|
460
|
+
if processor.name.lower() == TemplatesProcessor.NAME
|
|
461
461
|
]
|
|
462
462
|
if not in_memory and artifacts_to_template:
|
|
463
463
|
metrics.set_counter(CLICounterField.TEMPLATES_PROCESSOR, 1)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from snowflake.cli.api.secure_path import SecurePath
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class ProjectPaths:
|
|
9
|
+
"""
|
|
10
|
+
This class allows you to manage files paths related to given project.
|
|
11
|
+
Class provides bundle root path and allows to remove it.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
project_root: Path
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def bundle_root(self) -> Path:
|
|
18
|
+
return bundle_root(self.project_root)
|
|
19
|
+
|
|
20
|
+
def remove_up_bundle_root(self) -> None:
|
|
21
|
+
if self.bundle_root.exists():
|
|
22
|
+
SecurePath(self.bundle_root).rmdir(recursive=True)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def bundle_root(root: Path, app_type: str | None = None) -> Path:
|
|
26
|
+
if app_type:
|
|
27
|
+
return root / "output" / "bundle" / app_type
|
|
28
|
+
return root / "output" / "bundle"
|
|
@@ -15,9 +15,10 @@
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
17
|
from abc import ABC
|
|
18
|
-
from typing import Dict, Generic, List, Optional, TypeVar, Union
|
|
18
|
+
from typing import Any, Dict, Generic, List, Optional, TypeVar, Union
|
|
19
19
|
|
|
20
20
|
from pydantic import Field, PrivateAttr, field_validator
|
|
21
|
+
from pydantic_core.core_schema import ValidationInfo
|
|
21
22
|
from snowflake.cli.api.identifiers import FQN
|
|
22
23
|
from snowflake.cli.api.project.schemas.updatable_model import (
|
|
23
24
|
IdentifierField,
|
|
@@ -61,6 +62,15 @@ class MetaField(UpdatableModel):
|
|
|
61
62
|
default=None,
|
|
62
63
|
)
|
|
63
64
|
|
|
65
|
+
depends_on: Optional[List[str]] = Field(
|
|
66
|
+
title="Entities that need to be deployed before this one", default_factory=list
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
action_arguments: Optional[Dict[str, Dict[str, Union[int, bool, str]]]] = Field(
|
|
70
|
+
title="Arguments that will be used, when this entity is called as a dependency of other entity",
|
|
71
|
+
default_factory=dict,
|
|
72
|
+
)
|
|
73
|
+
|
|
64
74
|
@field_validator("use_mixins", mode="before")
|
|
65
75
|
@classmethod
|
|
66
76
|
def ensure_use_mixins_is_a_list(
|
|
@@ -70,6 +80,35 @@ class MetaField(UpdatableModel):
|
|
|
70
80
|
return [mixins]
|
|
71
81
|
return mixins
|
|
72
82
|
|
|
83
|
+
@field_validator("action_arguments", mode="before")
|
|
84
|
+
@classmethod
|
|
85
|
+
def arguments_validator(cls, arguments: Dict, info: ValidationInfo) -> Dict:
|
|
86
|
+
duplicated_run = (
|
|
87
|
+
info.context.get("is_duplicated_run", False) if info.context else False
|
|
88
|
+
)
|
|
89
|
+
if not duplicated_run:
|
|
90
|
+
for argument_dict in arguments.values():
|
|
91
|
+
for k, v in argument_dict.items():
|
|
92
|
+
argument_dict[k] = cls._cast_value(v)
|
|
93
|
+
|
|
94
|
+
return arguments
|
|
95
|
+
|
|
96
|
+
@staticmethod
|
|
97
|
+
def _cast_value(value: str) -> Union[int, bool, str]:
|
|
98
|
+
if value.lower() in ["true", "false"]:
|
|
99
|
+
return value.lower() == "true"
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
return int(value)
|
|
103
|
+
except ValueError:
|
|
104
|
+
return value
|
|
105
|
+
|
|
106
|
+
def __eq__(self, other):
|
|
107
|
+
return self.entity_id == other.entity_id
|
|
108
|
+
|
|
109
|
+
def __hash__(self):
|
|
110
|
+
return hash(self.entity_id)
|
|
111
|
+
|
|
73
112
|
|
|
74
113
|
class Identifier(UpdatableModel):
|
|
75
114
|
name: str = Field(title="Entity name")
|
|
@@ -141,6 +180,23 @@ class ImportsBaseModel:
|
|
|
141
180
|
return f"IMPORTS = ({imports})"
|
|
142
181
|
|
|
143
182
|
|
|
183
|
+
class Grant(UpdatableModel):
|
|
184
|
+
privilege: str = Field(title="Required privileges")
|
|
185
|
+
role: str = Field(title="Role to which the privileges will be granted")
|
|
186
|
+
|
|
187
|
+
def get_grant_sql(self, entity_model: EntityModelBase) -> str:
|
|
188
|
+
return f"GRANT {self.privilege} ON {entity_model.get_type().upper()} {entity_model.fqn.sql_identifier} TO ROLE {self.role}"
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class GrantBaseModel(UpdatableModel):
|
|
192
|
+
grants: Optional[List[Grant]] = Field(title="List of grants", default=None)
|
|
193
|
+
|
|
194
|
+
def get_grant_sqls(self) -> list[str]:
|
|
195
|
+
return (
|
|
196
|
+
[grant.get_grant_sql(self) for grant in self.grants] if self.grants else []
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
|
|
144
200
|
class ExternalAccessBaseModel:
|
|
145
201
|
external_access_integrations: Optional[List[str]] = Field(
|
|
146
202
|
title="Names of external access integrations needed for this entity to access external networks",
|
|
@@ -162,3 +218,76 @@ class ExternalAccessBaseModel:
|
|
|
162
218
|
return None
|
|
163
219
|
secrets = ", ".join(f"'{key}'={value}" for key, value in self.secrets.items())
|
|
164
220
|
return f"secrets=({secrets})"
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class ProcessorMapping(UpdatableModel):
|
|
224
|
+
name: str = Field(
|
|
225
|
+
title="Name of a processor to invoke on a collection of artifacts."
|
|
226
|
+
)
|
|
227
|
+
properties: Optional[Dict[str, Any]] = Field(
|
|
228
|
+
title="A set of key-value pairs used to configure the output of the processor. Consult a specific processor's documentation for more details on the supported properties.",
|
|
229
|
+
default=None,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class PathMapping(UpdatableModel):
|
|
234
|
+
src: str = Field(
|
|
235
|
+
title="Source path or glob pattern (relative to project root)", default=None
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
dest: Optional[str] = Field(
|
|
239
|
+
title="Destination path on stage",
|
|
240
|
+
description="Paths are relative to stage root; paths ending with a slash indicate that the destination is a directory which source files should be copied into.",
|
|
241
|
+
default=None,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
processors: Optional[List[Union[str, ProcessorMapping]]] = Field(
|
|
245
|
+
title="List of processors to apply to matching source files during bundling.",
|
|
246
|
+
default=[],
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
@field_validator("processors")
|
|
250
|
+
@classmethod
|
|
251
|
+
def transform_processors(
|
|
252
|
+
cls, input_values: Optional[List[Union[str, Dict, ProcessorMapping]]]
|
|
253
|
+
) -> List[ProcessorMapping]:
|
|
254
|
+
if input_values is None:
|
|
255
|
+
return []
|
|
256
|
+
|
|
257
|
+
transformed_processors: List[ProcessorMapping] = []
|
|
258
|
+
for input_processor in input_values:
|
|
259
|
+
if isinstance(input_processor, str):
|
|
260
|
+
transformed_processors.append(ProcessorMapping(name=input_processor))
|
|
261
|
+
elif isinstance(input_processor, Dict):
|
|
262
|
+
transformed_processors.append(ProcessorMapping(**input_processor))
|
|
263
|
+
else:
|
|
264
|
+
transformed_processors.append(input_processor)
|
|
265
|
+
return transformed_processors
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
Artifacts = List[Union[PathMapping, str]]
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
class EntityModelBaseWithArtifacts(EntityModelBase):
|
|
272
|
+
artifacts: Artifacts = Field(
|
|
273
|
+
title="List of paths or file source/destination pairs to add to the deploy root",
|
|
274
|
+
)
|
|
275
|
+
deploy_root: Optional[str] = Field(
|
|
276
|
+
title="Folder at the root of your project where the build step copies the artifacts",
|
|
277
|
+
default="output/deploy/",
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
@field_validator("artifacts")
|
|
281
|
+
@classmethod
|
|
282
|
+
def transform_artifacts(cls, orig_artifacts: Artifacts) -> List[PathMapping]:
|
|
283
|
+
transformed_artifacts: List[PathMapping] = []
|
|
284
|
+
if orig_artifacts is None:
|
|
285
|
+
return transformed_artifacts
|
|
286
|
+
|
|
287
|
+
for artifact in orig_artifacts:
|
|
288
|
+
if isinstance(artifact, PathMapping):
|
|
289
|
+
transformed_artifacts.append(artifact)
|
|
290
|
+
else:
|
|
291
|
+
transformed_artifacts.append(PathMapping(src=artifact))
|
|
292
|
+
|
|
293
|
+
return transformed_artifacts
|
|
@@ -24,6 +24,8 @@ from snowflake.cli._plugins.nativeapp.entities.application_package import (
|
|
|
24
24
|
ApplicationPackageEntity,
|
|
25
25
|
ApplicationPackageEntityModel,
|
|
26
26
|
)
|
|
27
|
+
from snowflake.cli._plugins.notebook.notebook_entity import NotebookEntity
|
|
28
|
+
from snowflake.cli._plugins.notebook.notebook_entity_model import NotebookEntityModel
|
|
27
29
|
from snowflake.cli._plugins.snowpark.snowpark_entity import (
|
|
28
30
|
FunctionEntity,
|
|
29
31
|
ProcedureEntity,
|
|
@@ -43,6 +45,7 @@ Entity = Union[
|
|
|
43
45
|
StreamlitEntity,
|
|
44
46
|
ProcedureEntity,
|
|
45
47
|
FunctionEntity,
|
|
48
|
+
NotebookEntity,
|
|
46
49
|
]
|
|
47
50
|
EntityModel = Union[
|
|
48
51
|
ApplicationEntityModel,
|
|
@@ -50,6 +53,7 @@ EntityModel = Union[
|
|
|
50
53
|
StreamlitEntityModel,
|
|
51
54
|
FunctionEntityModel,
|
|
52
55
|
ProcedureEntityModel,
|
|
56
|
+
NotebookEntityModel,
|
|
53
57
|
]
|
|
54
58
|
|
|
55
59
|
ALL_ENTITIES: List[Entity] = [*get_args(Entity)]
|
|
@@ -14,13 +14,19 @@
|
|
|
14
14
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
|
+
from collections import defaultdict
|
|
17
18
|
from dataclasses import dataclass
|
|
18
|
-
from
|
|
19
|
+
from types import UnionType
|
|
20
|
+
from typing import Any, Dict, List, Optional, Union, get_args, get_origin
|
|
19
21
|
|
|
20
22
|
from packaging.version import Version
|
|
21
23
|
from pydantic import Field, ValidationError, field_validator, model_validator
|
|
22
24
|
from pydantic_core.core_schema import ValidationInfo
|
|
23
25
|
from snowflake.cli._plugins.nativeapp.entities.application import ApplicationEntityModel
|
|
26
|
+
from snowflake.cli._plugins.nativeapp.entities.application_package import (
|
|
27
|
+
ApplicationPackageChildrenTypes,
|
|
28
|
+
ApplicationPackageEntityModel,
|
|
29
|
+
)
|
|
24
30
|
from snowflake.cli.api.project.errors import SchemaValidationError
|
|
25
31
|
from snowflake.cli.api.project.schemas.entities.common import (
|
|
26
32
|
TargetField,
|
|
@@ -159,6 +165,12 @@ class DefinitionV20(_ProjectDefinitionBase):
|
|
|
159
165
|
target_object = entity.from_
|
|
160
166
|
target_type = target_object.get_type()
|
|
161
167
|
cls._validate_target_field(target_key, target_type, entities)
|
|
168
|
+
elif entity.type == ApplicationPackageEntityModel.get_type():
|
|
169
|
+
for child_entity in entity.children:
|
|
170
|
+
target_key = child_entity.target
|
|
171
|
+
cls._validate_target_field(
|
|
172
|
+
target_key, ApplicationPackageChildrenTypes, entities
|
|
173
|
+
)
|
|
162
174
|
|
|
163
175
|
@classmethod
|
|
164
176
|
def _validate_target_field(
|
|
@@ -168,11 +180,20 @@ class DefinitionV20(_ProjectDefinitionBase):
|
|
|
168
180
|
raise ValueError(f"No such target: {target_key}")
|
|
169
181
|
|
|
170
182
|
# Validate the target type
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
183
|
+
if target_type:
|
|
184
|
+
actual_target_type = entities[target_key].__class__
|
|
185
|
+
if get_origin(target_type) in (Union, UnionType):
|
|
186
|
+
if actual_target_type not in get_args(target_type):
|
|
187
|
+
expected_types_str = ", ".join(
|
|
188
|
+
[t.__name__ for t in get_args(target_type)]
|
|
189
|
+
)
|
|
190
|
+
raise ValueError(
|
|
191
|
+
f"Target type mismatch. Expected one of [{expected_types_str}], got {actual_target_type.__name__}"
|
|
192
|
+
)
|
|
193
|
+
elif target_type is not actual_target_type:
|
|
194
|
+
raise ValueError(
|
|
195
|
+
f"Target type mismatch. Expected {target_type.__name__}, got {actual_target_type.__name__}"
|
|
196
|
+
)
|
|
176
197
|
|
|
177
198
|
@model_validator(mode="before")
|
|
178
199
|
@classmethod
|
|
@@ -200,6 +221,7 @@ class DefinitionV20(_ProjectDefinitionBase):
|
|
|
200
221
|
mixin_defs=data["mixins"],
|
|
201
222
|
)
|
|
202
223
|
entities[entity_name] = merged_values
|
|
224
|
+
|
|
203
225
|
return data
|
|
204
226
|
|
|
205
227
|
@classmethod
|
|
@@ -241,6 +263,21 @@ class DefinitionV20(_ProjectDefinitionBase):
|
|
|
241
263
|
data = cls._merge_data(data, entity)
|
|
242
264
|
return data
|
|
243
265
|
|
|
266
|
+
@model_validator(mode="after")
|
|
267
|
+
def validate_dependencies(self):
|
|
268
|
+
"""
|
|
269
|
+
Checks if entities listed in depends_on section exist in the project
|
|
270
|
+
"""
|
|
271
|
+
missing_dependencies = defaultdict(list)
|
|
272
|
+
for entity_id, entity in self.entities.items():
|
|
273
|
+
if entity.meta:
|
|
274
|
+
for dependency in entity.meta.depends_on:
|
|
275
|
+
if dependency not in self.entities:
|
|
276
|
+
missing_dependencies[entity_id].append(dependency)
|
|
277
|
+
|
|
278
|
+
if missing_dependencies:
|
|
279
|
+
raise ValueError(_get_missing_dependencies_message(missing_dependencies))
|
|
280
|
+
|
|
244
281
|
@classmethod
|
|
245
282
|
def _merge_data(
|
|
246
283
|
cls,
|
|
@@ -333,3 +370,14 @@ def _unique_extend(list_a: List, list_b: List) -> List:
|
|
|
333
370
|
if all(item != x for x in list_a):
|
|
334
371
|
new_list.append(item)
|
|
335
372
|
return new_list
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def _get_missing_dependencies_message(
|
|
376
|
+
missing_dependencies: Dict[str, List[str]]
|
|
377
|
+
) -> str:
|
|
378
|
+
missing_dependencies_message = []
|
|
379
|
+
for entity_id, dependencies in missing_dependencies.items():
|
|
380
|
+
missing_dependencies_message.append(
|
|
381
|
+
f"\n Entity {entity_id} depends on non-existing entities: {', '.join(dependencies)}"
|
|
382
|
+
)
|
|
383
|
+
return "".join(missing_dependencies_message)
|
|
@@ -122,10 +122,10 @@ class UpdatableModel(BaseModel):
|
|
|
122
122
|
class_dict = class_.__dict__
|
|
123
123
|
field_annotations.update(class_dict.get("__annotations__", {}))
|
|
124
124
|
|
|
125
|
-
if "model_fields" in class_dict:
|
|
125
|
+
if "model_fields" in class_dict and class_.model_fields:
|
|
126
126
|
# This means the class dict has already been processed by Pydantic
|
|
127
127
|
# All fields should properly be populated in model_fields
|
|
128
|
-
field_values.update(
|
|
128
|
+
field_values.update(class_.model_fields)
|
|
129
129
|
else:
|
|
130
130
|
# If Pydantic did not process this class yet, get the values from class_dict directly
|
|
131
131
|
field_values.update(class_dict)
|
|
@@ -15,16 +15,16 @@
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
17
|
import re
|
|
18
|
-
from typing import List, Optional
|
|
18
|
+
from typing import List, Optional
|
|
19
19
|
|
|
20
20
|
from pydantic import Field, field_validator
|
|
21
|
+
from snowflake.cli.api.project.schemas.entities.common import Artifacts, PathMapping
|
|
21
22
|
from snowflake.cli.api.project.schemas.updatable_model import UpdatableModel
|
|
22
23
|
from snowflake.cli.api.project.schemas.v1.native_app.application import (
|
|
23
24
|
Application,
|
|
24
25
|
ApplicationV11,
|
|
25
26
|
)
|
|
26
27
|
from snowflake.cli.api.project.schemas.v1.native_app.package import Package, PackageV11
|
|
27
|
-
from snowflake.cli.api.project.schemas.v1.native_app.path_mapping import PathMapping
|
|
28
28
|
from snowflake.cli.api.project.util import (
|
|
29
29
|
SCHEMA_AND_NAME,
|
|
30
30
|
)
|
|
@@ -34,7 +34,7 @@ class NativeApp(UpdatableModel):
|
|
|
34
34
|
name: str = Field(
|
|
35
35
|
title="Project identifier",
|
|
36
36
|
)
|
|
37
|
-
artifacts:
|
|
37
|
+
artifacts: Artifacts = Field(
|
|
38
38
|
title="List of file source and destination pairs to add to the deploy root",
|
|
39
39
|
)
|
|
40
40
|
bundle_root: Optional[str] = Field(
|
|
@@ -69,10 +69,8 @@ class NativeApp(UpdatableModel):
|
|
|
69
69
|
|
|
70
70
|
@field_validator("artifacts")
|
|
71
71
|
@classmethod
|
|
72
|
-
def transform_artifacts(
|
|
73
|
-
|
|
74
|
-
) -> List[PathMapping]:
|
|
75
|
-
transformed_artifacts = []
|
|
72
|
+
def transform_artifacts(cls, orig_artifacts: Artifacts) -> List[PathMapping]:
|
|
73
|
+
transformed_artifacts: List[PathMapping] = []
|
|
76
74
|
if orig_artifacts is None:
|
|
77
75
|
return transformed_artifacts
|
|
78
76
|
|
|
@@ -27,7 +27,7 @@ class Streamlit(UpdatableModel, ObjectIdentifierModel(object_name="Streamlit")):
|
|
|
27
27
|
title="Stage in which the app’s artifacts will be stored", default="streamlit"
|
|
28
28
|
)
|
|
29
29
|
query_warehouse: str = Field(
|
|
30
|
-
title="Snowflake warehouse to host the app", default=
|
|
30
|
+
title="Snowflake warehouse to host the app", default=None
|
|
31
31
|
)
|
|
32
32
|
main_file: Optional[Path] = Field(
|
|
33
33
|
title="Entrypoint file of the Streamlit app", default="streamlit_app.py"
|