snowflake-cli 3.0.2__py3-none-any.whl → 3.1.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/cli_app.py +3 -0
- snowflake/cli/_app/dev/docs/templates/overview.rst.jinja2 +1 -1
- snowflake/cli/_app/dev/docs/templates/usage.rst.jinja2 +2 -2
- snowflake/cli/_app/telemetry.py +69 -4
- snowflake/cli/_plugins/connection/commands.py +40 -2
- snowflake/cli/_plugins/git/commands.py +6 -3
- snowflake/cli/_plugins/git/manager.py +5 -0
- snowflake/cli/_plugins/nativeapp/artifacts.py +13 -3
- snowflake/cli/_plugins/nativeapp/codegen/artifact_processor.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/compiler.py +7 -0
- snowflake/cli/_plugins/nativeapp/codegen/sandbox.py +10 -10
- snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +2 -2
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/extension_function_utils.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +8 -8
- snowflake/cli/_plugins/nativeapp/commands.py +135 -186
- snowflake/cli/_plugins/nativeapp/entities/application.py +176 -24
- snowflake/cli/_plugins/nativeapp/entities/application_package.py +112 -136
- snowflake/cli/_plugins/nativeapp/exceptions.py +12 -0
- snowflake/cli/_plugins/nativeapp/manager.py +3 -26
- snowflake/cli/_plugins/nativeapp/v2_conversions/{v2_to_v1_decorator.py → compat.py} +131 -72
- snowflake/cli/_plugins/nativeapp/version/commands.py +30 -29
- snowflake/cli/_plugins/nativeapp/version/version_processor.py +1 -43
- snowflake/cli/_plugins/snowpark/common.py +60 -18
- snowflake/cli/_plugins/snowpark/package/anaconda_packages.py +2 -2
- snowflake/cli/_plugins/spcs/image_repository/commands.py +4 -37
- snowflake/cli/_plugins/spcs/image_repository/manager.py +4 -1
- snowflake/cli/_plugins/spcs/services/commands.py +36 -4
- snowflake/cli/_plugins/spcs/services/manager.py +36 -4
- snowflake/cli/_plugins/stage/commands.py +8 -3
- snowflake/cli/_plugins/stage/diff.py +16 -16
- snowflake/cli/_plugins/stage/manager.py +164 -73
- snowflake/cli/_plugins/stage/md5.py +1 -1
- snowflake/cli/_plugins/workspace/commands.py +21 -1
- snowflake/cli/_plugins/workspace/context.py +38 -0
- snowflake/cli/_plugins/workspace/manager.py +23 -13
- snowflake/cli/api/cli_global_context.py +3 -3
- snowflake/cli/api/commands/flags.py +23 -7
- snowflake/cli/api/config.py +7 -4
- snowflake/cli/api/connections.py +12 -1
- snowflake/cli/api/entities/common.py +4 -2
- snowflake/cli/api/entities/utils.py +17 -37
- snowflake/cli/api/exceptions.py +32 -0
- snowflake/cli/api/identifiers.py +8 -0
- snowflake/cli/api/project/definition_conversion.py +139 -40
- snowflake/cli/api/project/schemas/entities/common.py +11 -0
- snowflake/cli/api/project/schemas/project_definition.py +30 -25
- snowflake/cli/api/sql_execution.py +5 -7
- snowflake/cli/api/stage_path.py +241 -0
- snowflake/cli/api/utils/definition_rendering.py +3 -5
- {snowflake_cli-3.0.2.dist-info → snowflake_cli-3.1.0.dist-info}/METADATA +11 -11
- {snowflake_cli-3.0.2.dist-info → snowflake_cli-3.1.0.dist-info}/RECORD +55 -55
- snowflake/cli/_plugins/nativeapp/teardown_processor.py +0 -70
- snowflake/cli/_plugins/workspace/action_context.py +0 -18
- {snowflake_cli-3.0.2.dist-info → snowflake_cli-3.1.0.dist-info}/WHEEL +0 -0
- {snowflake_cli-3.0.2.dist-info → snowflake_cli-3.1.0.dist-info}/entry_points.txt +0 -0
- {snowflake_cli-3.0.2.dist-info → snowflake_cli-3.1.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -29,14 +29,40 @@ from snowflake.cli.api.cli_global_context import (
|
|
|
29
29
|
get_cli_context_manager,
|
|
30
30
|
)
|
|
31
31
|
from snowflake.cli.api.commands.decorators import _options_decorator_factory
|
|
32
|
+
from snowflake.cli.api.project.definition_conversion import (
|
|
33
|
+
convert_project_definition_to_v2,
|
|
34
|
+
)
|
|
32
35
|
from snowflake.cli.api.project.schemas.entities.common import EntityModelBase
|
|
33
36
|
from snowflake.cli.api.project.schemas.project_definition import (
|
|
34
37
|
DefinitionV11,
|
|
35
38
|
DefinitionV20,
|
|
39
|
+
ProjectDefinition,
|
|
40
|
+
ProjectDefinitionV1,
|
|
36
41
|
)
|
|
37
42
|
from snowflake.cli.api.project.schemas.v1.native_app.path_mapping import PathMapping
|
|
38
43
|
from snowflake.cli.api.utils.definition_rendering import render_definition_template
|
|
39
44
|
|
|
45
|
+
APP_AND_PACKAGE_OPTIONS = [
|
|
46
|
+
inspect.Parameter(
|
|
47
|
+
"package_entity_id",
|
|
48
|
+
inspect.Parameter.KEYWORD_ONLY,
|
|
49
|
+
annotation=Optional[str],
|
|
50
|
+
default=typer.Option(
|
|
51
|
+
default="",
|
|
52
|
+
help="The ID of the package entity on which to operate when definition_version is 2 or higher.",
|
|
53
|
+
),
|
|
54
|
+
),
|
|
55
|
+
inspect.Parameter(
|
|
56
|
+
"app_entity_id",
|
|
57
|
+
inspect.Parameter.KEYWORD_ONLY,
|
|
58
|
+
annotation=Optional[str],
|
|
59
|
+
default=typer.Option(
|
|
60
|
+
default="",
|
|
61
|
+
help="The ID of the application entity on which to operate when definition_version is 2 or higher.",
|
|
62
|
+
),
|
|
63
|
+
),
|
|
64
|
+
]
|
|
65
|
+
|
|
40
66
|
|
|
41
67
|
def _convert_v2_artifact_to_v1_dict(
|
|
42
68
|
v2_artifact: Union[PathMapping, str]
|
|
@@ -58,45 +84,12 @@ def _pdf_v2_to_v1(
|
|
|
58
84
|
) -> DefinitionV11:
|
|
59
85
|
pdfv1: Dict[str, Any] = {"definition_version": "1.1", "native_app": {}}
|
|
60
86
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
disambiguation_option="--app-entity-id",
|
|
67
|
-
required=app_required,
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
# Infer or verify the package if we have an app entity to convert
|
|
71
|
-
if app_definition:
|
|
72
|
-
target_package = app_definition.from_.target
|
|
73
|
-
if package_entity_id:
|
|
74
|
-
# If the user specified a package entity ID,
|
|
75
|
-
# check that the app entity targets the user-specified package entity
|
|
76
|
-
# if the app entity is used by the command being run
|
|
77
|
-
if target_package != package_entity_id and app_required:
|
|
78
|
-
raise ClickException(
|
|
79
|
-
f"The application entity {app_definition.entity_id} does not "
|
|
80
|
-
f"target the application package entity {package_entity_id}. Either"
|
|
81
|
-
f"use --package-entity-id {target_package} to target the correct package entity, "
|
|
82
|
-
f"or omit the --package-entity-id flag to automatically use the package entity "
|
|
83
|
-
f"that the application entity targets."
|
|
84
|
-
)
|
|
85
|
-
elif target_package in v2_definition.get_entities_by_type(
|
|
86
|
-
ApplicationPackageEntityModel.get_type()
|
|
87
|
-
):
|
|
88
|
-
# If the user didn't target a specific package entity, use the one the app entity targets
|
|
89
|
-
package_entity_id = target_package
|
|
90
|
-
|
|
91
|
-
# Determine the package entity to convert, there must be one
|
|
92
|
-
app_package_definition = find_entity(
|
|
93
|
-
v2_definition,
|
|
94
|
-
ApplicationPackageEntityModel,
|
|
95
|
-
package_entity_id,
|
|
96
|
-
disambiguation_option="--package-entity-id",
|
|
97
|
-
required=True,
|
|
87
|
+
app_definition, app_package_definition = _find_app_and_package_entities(
|
|
88
|
+
v2_definition=v2_definition,
|
|
89
|
+
package_entity_id=package_entity_id,
|
|
90
|
+
app_entity_id=app_entity_id,
|
|
91
|
+
app_required=app_required,
|
|
98
92
|
)
|
|
99
|
-
assert app_package_definition is not None # satisfy mypy
|
|
100
93
|
|
|
101
94
|
# NativeApp
|
|
102
95
|
if app_definition and app_definition.fqn.identifier:
|
|
@@ -156,6 +149,52 @@ def _pdf_v2_to_v1(
|
|
|
156
149
|
return result.project_definition
|
|
157
150
|
|
|
158
151
|
|
|
152
|
+
def _find_app_and_package_entities(
|
|
153
|
+
v2_definition: DefinitionV20,
|
|
154
|
+
package_entity_id: str,
|
|
155
|
+
app_entity_id: str,
|
|
156
|
+
app_required: bool,
|
|
157
|
+
):
|
|
158
|
+
# Determine the application entity to convert, there can be zero or one
|
|
159
|
+
app_definition = find_entity(
|
|
160
|
+
v2_definition,
|
|
161
|
+
ApplicationEntityModel,
|
|
162
|
+
app_entity_id,
|
|
163
|
+
disambiguation_option="--app-entity-id",
|
|
164
|
+
required=app_required,
|
|
165
|
+
)
|
|
166
|
+
# Infer or verify the package if we have an app entity to convert
|
|
167
|
+
if app_definition:
|
|
168
|
+
target_package = app_definition.from_.target
|
|
169
|
+
if package_entity_id:
|
|
170
|
+
# If the user specified a package entity ID,
|
|
171
|
+
# check that the app entity targets the user-specified package entity
|
|
172
|
+
# if the app entity is used by the command being run
|
|
173
|
+
if target_package != package_entity_id and app_required:
|
|
174
|
+
raise ClickException(
|
|
175
|
+
f"The application entity {app_definition.entity_id} does not "
|
|
176
|
+
f"target the application package entity {package_entity_id}. Either"
|
|
177
|
+
f"use --package-entity-id {target_package} to target the correct package entity, "
|
|
178
|
+
f"or omit the --package-entity-id flag to automatically use the package entity "
|
|
179
|
+
f"that the application entity targets."
|
|
180
|
+
)
|
|
181
|
+
elif target_package in v2_definition.get_entities_by_type(
|
|
182
|
+
ApplicationPackageEntityModel.get_type()
|
|
183
|
+
):
|
|
184
|
+
# If the user didn't target a specific package entity, use the one the app entity targets
|
|
185
|
+
package_entity_id = target_package
|
|
186
|
+
# Determine the package entity to convert, there must be one
|
|
187
|
+
app_package_definition = find_entity(
|
|
188
|
+
v2_definition,
|
|
189
|
+
ApplicationPackageEntityModel,
|
|
190
|
+
package_entity_id,
|
|
191
|
+
disambiguation_option="--package-entity-id",
|
|
192
|
+
required=True,
|
|
193
|
+
)
|
|
194
|
+
assert app_package_definition is not None # satisfy mypy
|
|
195
|
+
return app_definition, app_package_definition
|
|
196
|
+
|
|
197
|
+
|
|
159
198
|
T = TypeVar("T", bound=EntityModelBase)
|
|
160
199
|
|
|
161
200
|
|
|
@@ -209,54 +248,74 @@ def find_entity(
|
|
|
209
248
|
return entity
|
|
210
249
|
|
|
211
250
|
|
|
212
|
-
def
|
|
251
|
+
def force_project_definition_v2(
|
|
252
|
+
*, single_app_and_package: bool = True, app_required: bool = False
|
|
253
|
+
):
|
|
213
254
|
"""
|
|
214
|
-
A command decorator that
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
255
|
+
A command decorator that forces the project definition to be converted to v2.
|
|
256
|
+
|
|
257
|
+
If a v1 definition is found, it is converted to v2 in-memory and the global context
|
|
258
|
+
is updated with the new definition object.
|
|
259
|
+
|
|
260
|
+
If a v2 definition is already found, it is used as-is, optionally limiting the number
|
|
261
|
+
of application and package entities to one each (true by default).
|
|
262
|
+
|
|
263
|
+
Assumes with_project_definition() has already been called.
|
|
219
264
|
"""
|
|
220
265
|
|
|
221
266
|
def decorator(func):
|
|
222
267
|
@wraps(func)
|
|
223
268
|
def wrapper(*args, **kwargs):
|
|
224
|
-
|
|
269
|
+
cli_context = get_cli_context()
|
|
270
|
+
original_pdf: Optional[ProjectDefinition] = cli_context.project_definition
|
|
225
271
|
if not original_pdf:
|
|
226
272
|
raise ValueError(
|
|
227
|
-
"Project definition could not be found.
|
|
273
|
+
"Project definition could not be found. "
|
|
274
|
+
"The single_app_and_package() command decorator assumes "
|
|
275
|
+
"that with_project_definition() was called before it."
|
|
276
|
+
)
|
|
277
|
+
if isinstance(original_pdf, ProjectDefinitionV1):
|
|
278
|
+
pdfv2 = convert_project_definition_to_v2(
|
|
279
|
+
cli_context.project_root,
|
|
280
|
+
original_pdf,
|
|
281
|
+
accept_templates=False, # Templates should all be rendered by now
|
|
282
|
+
template_context=None, # Force inclusion of all fields
|
|
283
|
+
in_memory=True, # Convert the definition knowing it will be used immediately
|
|
228
284
|
)
|
|
229
|
-
|
|
285
|
+
for entity_id, entity in pdfv2.entities.items():
|
|
286
|
+
# Backfill kwargs for the command to use,
|
|
287
|
+
# there can only be one entity of each type
|
|
288
|
+
is_package = isinstance(entity, ApplicationPackageEntityModel)
|
|
289
|
+
key = "package_entity_id" if is_package else "app_entity_id"
|
|
290
|
+
kwargs[key] = entity_id
|
|
291
|
+
|
|
292
|
+
cm = get_cli_context_manager()
|
|
293
|
+
|
|
294
|
+
# Override the project definition so that the command operates on the new entities
|
|
295
|
+
cm.override_project_definition = pdfv2
|
|
296
|
+
elif single_app_and_package:
|
|
230
297
|
package_entity_id = kwargs.get("package_entity_id", "")
|
|
231
298
|
app_entity_id = kwargs.get("app_entity_id", "")
|
|
232
|
-
|
|
299
|
+
app_definition, app_package_definition = _find_app_and_package_entities(
|
|
233
300
|
original_pdf, package_entity_id, app_entity_id, app_required
|
|
234
301
|
)
|
|
235
|
-
|
|
302
|
+
entities_to_keep = {app_package_definition.entity_id}
|
|
303
|
+
kwargs["package_entity_id"] = app_package_definition.entity_id
|
|
304
|
+
if app_definition:
|
|
305
|
+
entities_to_keep.add(app_definition.entity_id)
|
|
306
|
+
kwargs["app_entity_id"] = app_definition.entity_id
|
|
307
|
+
for entity_id in list(original_pdf.entities):
|
|
308
|
+
if entity_id not in entities_to_keep:
|
|
309
|
+
# This happens after templates are rendered,
|
|
310
|
+
# so we can safely remove the entity
|
|
311
|
+
del original_pdf.entities[entity_id]
|
|
236
312
|
return func(*args, **kwargs)
|
|
237
313
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
annotation=Optional[str],
|
|
245
|
-
default=typer.Option(
|
|
246
|
-
default="",
|
|
247
|
-
help="The ID of the package entity on which to operate when definition_version is 2 or higher.",
|
|
248
|
-
),
|
|
249
|
-
),
|
|
250
|
-
inspect.Parameter(
|
|
251
|
-
"app_entity_id",
|
|
252
|
-
inspect.Parameter.KEYWORD_ONLY,
|
|
253
|
-
annotation=Optional[str],
|
|
254
|
-
default=typer.Option(
|
|
255
|
-
default="",
|
|
256
|
-
help="The ID of the application entity on which to operate when definition_version is 2 or higher.",
|
|
257
|
-
),
|
|
258
|
-
),
|
|
259
|
-
],
|
|
260
|
-
)
|
|
314
|
+
if single_app_and_package:
|
|
315
|
+
# Add the --app-entity-id and --package-entity-id options to the command
|
|
316
|
+
return _options_decorator_factory(
|
|
317
|
+
wrapper, additional_options=APP_AND_PACKAGE_OPTIONS
|
|
318
|
+
)
|
|
319
|
+
return wrapper
|
|
261
320
|
|
|
262
321
|
return decorator
|
|
@@ -20,21 +20,17 @@ from typing import Optional
|
|
|
20
20
|
import typer
|
|
21
21
|
from click import MissingParameter
|
|
22
22
|
from snowflake.cli._plugins.nativeapp.common_flags import ForceOption, InteractiveOption
|
|
23
|
-
from snowflake.cli._plugins.nativeapp.
|
|
24
|
-
|
|
25
|
-
nativeapp_definition_v2_to_v1,
|
|
26
|
-
)
|
|
27
|
-
from snowflake.cli._plugins.nativeapp.version.version_processor import (
|
|
28
|
-
NativeAppVersionCreateProcessor,
|
|
29
|
-
NativeAppVersionDropProcessor,
|
|
23
|
+
from snowflake.cli._plugins.nativeapp.v2_conversions.compat import (
|
|
24
|
+
force_project_definition_v2,
|
|
30
25
|
)
|
|
26
|
+
from snowflake.cli._plugins.workspace.manager import WorkspaceManager
|
|
31
27
|
from snowflake.cli.api.cli_global_context import get_cli_context
|
|
32
28
|
from snowflake.cli.api.commands.decorators import (
|
|
33
29
|
with_project_definition,
|
|
34
30
|
)
|
|
35
31
|
from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
|
|
32
|
+
from snowflake.cli.api.entities.common import EntityActions
|
|
36
33
|
from snowflake.cli.api.output.types import CommandResult, MessageResult, QueryResult
|
|
37
|
-
from snowflake.cli.api.project.project_verification import assert_project_type
|
|
38
34
|
|
|
39
35
|
app = SnowTyperFactory(
|
|
40
36
|
name="version",
|
|
@@ -46,7 +42,7 @@ log = logging.getLogger(__name__)
|
|
|
46
42
|
|
|
47
43
|
@app.command(requires_connection=True)
|
|
48
44
|
@with_project_definition()
|
|
49
|
-
@
|
|
45
|
+
@force_project_definition_v2()
|
|
50
46
|
def create(
|
|
51
47
|
version: Optional[str] = typer.Argument(
|
|
52
48
|
None,
|
|
@@ -71,18 +67,18 @@ def create(
|
|
|
71
67
|
"""
|
|
72
68
|
Adds a new patch to the provided version defined in your application package. If the version does not exist, creates a version with patch 0.
|
|
73
69
|
"""
|
|
74
|
-
|
|
75
|
-
assert_project_type("native_app")
|
|
76
|
-
|
|
77
70
|
if version is None and patch is not None:
|
|
78
71
|
raise MissingParameter("Cannot provide a patch without version!")
|
|
79
72
|
|
|
80
73
|
cli_context = get_cli_context()
|
|
81
|
-
|
|
82
|
-
project_definition=cli_context.project_definition
|
|
74
|
+
ws = WorkspaceManager(
|
|
75
|
+
project_definition=cli_context.project_definition,
|
|
83
76
|
project_root=cli_context.project_root,
|
|
84
77
|
)
|
|
85
|
-
|
|
78
|
+
package_id = options["package_entity_id"]
|
|
79
|
+
ws.perform_action(
|
|
80
|
+
package_id,
|
|
81
|
+
EntityActions.VERSION_CREATE,
|
|
86
82
|
version=version,
|
|
87
83
|
patch=patch,
|
|
88
84
|
force=force,
|
|
@@ -94,28 +90,29 @@ def create(
|
|
|
94
90
|
|
|
95
91
|
@app.command("list", requires_connection=True)
|
|
96
92
|
@with_project_definition()
|
|
97
|
-
@
|
|
93
|
+
@force_project_definition_v2()
|
|
98
94
|
def version_list(
|
|
99
95
|
**options,
|
|
100
96
|
) -> CommandResult:
|
|
101
97
|
"""
|
|
102
98
|
Lists all versions defined in an application package.
|
|
103
99
|
"""
|
|
104
|
-
|
|
105
|
-
assert_project_type("native_app")
|
|
106
|
-
|
|
107
100
|
cli_context = get_cli_context()
|
|
108
|
-
|
|
109
|
-
project_definition=cli_context.project_definition
|
|
101
|
+
ws = WorkspaceManager(
|
|
102
|
+
project_definition=cli_context.project_definition,
|
|
110
103
|
project_root=cli_context.project_root,
|
|
111
104
|
)
|
|
112
|
-
|
|
105
|
+
package_id = options["package_entity_id"]
|
|
106
|
+
cursor = ws.perform_action(
|
|
107
|
+
package_id,
|
|
108
|
+
EntityActions.VERSION_LIST,
|
|
109
|
+
)
|
|
113
110
|
return QueryResult(cursor)
|
|
114
111
|
|
|
115
112
|
|
|
116
113
|
@app.command(requires_connection=True)
|
|
117
114
|
@with_project_definition()
|
|
118
|
-
@
|
|
115
|
+
@force_project_definition_v2()
|
|
119
116
|
def drop(
|
|
120
117
|
version: Optional[str] = typer.Argument(
|
|
121
118
|
None,
|
|
@@ -129,13 +126,17 @@ def drop(
|
|
|
129
126
|
Drops a version defined in your application package. Versions can either be passed in as an argument to the command or read from the `manifest.yml` file.
|
|
130
127
|
Dropping patches is not allowed.
|
|
131
128
|
"""
|
|
132
|
-
|
|
133
|
-
assert_project_type("native_app")
|
|
134
|
-
|
|
135
129
|
cli_context = get_cli_context()
|
|
136
|
-
|
|
137
|
-
project_definition=cli_context.project_definition
|
|
130
|
+
ws = WorkspaceManager(
|
|
131
|
+
project_definition=cli_context.project_definition,
|
|
138
132
|
project_root=cli_context.project_root,
|
|
139
133
|
)
|
|
140
|
-
|
|
134
|
+
package_id = options["package_entity_id"]
|
|
135
|
+
ws.perform_action(
|
|
136
|
+
package_id,
|
|
137
|
+
EntityActions.VERSION_DROP,
|
|
138
|
+
version=version,
|
|
139
|
+
interactive=interactive,
|
|
140
|
+
force=force,
|
|
141
|
+
)
|
|
141
142
|
return MessageResult(f"Version drop is now complete.")
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
17
|
from pathlib import Path
|
|
18
|
-
from typing import
|
|
18
|
+
from typing import Optional
|
|
19
19
|
|
|
20
20
|
from snowflake.cli._plugins.nativeapp.entities.application_package import (
|
|
21
21
|
ApplicationPackageEntity,
|
|
@@ -24,52 +24,10 @@ from snowflake.cli._plugins.nativeapp.manager import (
|
|
|
24
24
|
NativeAppCommandProcessor,
|
|
25
25
|
NativeAppManager,
|
|
26
26
|
)
|
|
27
|
-
from snowflake.cli._plugins.nativeapp.run_processor import NativeAppRunProcessor
|
|
28
27
|
from snowflake.cli.api.console import cli_console as cc
|
|
29
28
|
from snowflake.cli.api.project.schemas.v1.native_app.native_app import NativeApp
|
|
30
29
|
|
|
31
30
|
|
|
32
|
-
class NativeAppVersionCreateProcessor(NativeAppRunProcessor):
|
|
33
|
-
def __init__(self, project_definition: Dict, project_root: Path):
|
|
34
|
-
super().__init__(project_definition, project_root)
|
|
35
|
-
|
|
36
|
-
def process(
|
|
37
|
-
self,
|
|
38
|
-
version: Optional[str],
|
|
39
|
-
patch: Optional[int],
|
|
40
|
-
force: bool,
|
|
41
|
-
interactive: bool,
|
|
42
|
-
skip_git_check: bool,
|
|
43
|
-
*args,
|
|
44
|
-
**kwargs,
|
|
45
|
-
):
|
|
46
|
-
return ApplicationPackageEntity.version_create(
|
|
47
|
-
console=cc,
|
|
48
|
-
project_root=self.project_root,
|
|
49
|
-
deploy_root=self.deploy_root,
|
|
50
|
-
bundle_root=self.bundle_root,
|
|
51
|
-
generated_root=self.generated_root,
|
|
52
|
-
artifacts=self.artifacts,
|
|
53
|
-
package_name=self.package_name,
|
|
54
|
-
package_role=self.package_role,
|
|
55
|
-
package_distribution=self.package_distribution,
|
|
56
|
-
prune=True,
|
|
57
|
-
recursive=True,
|
|
58
|
-
paths=None,
|
|
59
|
-
print_diff=True,
|
|
60
|
-
validate=True,
|
|
61
|
-
stage_fqn=self.stage_fqn,
|
|
62
|
-
package_warehouse=self.package_warehouse,
|
|
63
|
-
post_deploy_hooks=self.package_post_deploy_hooks,
|
|
64
|
-
package_scripts=self.package_scripts,
|
|
65
|
-
version=version,
|
|
66
|
-
patch=patch,
|
|
67
|
-
force=force,
|
|
68
|
-
interactive=interactive,
|
|
69
|
-
skip_git_check=skip_git_check,
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
|
|
73
31
|
class NativeAppVersionDropProcessor(NativeAppManager, NativeAppCommandProcessor):
|
|
74
32
|
def __init__(self, project_definition: NativeApp, project_root: Path):
|
|
75
33
|
super().__init__(project_definition, project_root)
|
|
@@ -158,10 +158,8 @@ def _check_if_replace_is_required(
|
|
|
158
158
|
)
|
|
159
159
|
return True
|
|
160
160
|
|
|
161
|
-
if (
|
|
162
|
-
resource_json["
|
|
163
|
-
or _sql_to_python_return_type_mapper(resource_json["returns"]).lower()
|
|
164
|
-
!= entity.returns.lower()
|
|
161
|
+
if resource_json["handler"].lower() != entity.handler.lower() or not same_type(
|
|
162
|
+
resource_json["returns"], entity.returns
|
|
165
163
|
):
|
|
166
164
|
log.info(
|
|
167
165
|
"Return type or handler types do not match. Replacing the %s.", object_type
|
|
@@ -216,24 +214,68 @@ def _snowflake_dependencies_differ(
|
|
|
216
214
|
return _standardize(old_dependencies) != _standardize(new_dependencies)
|
|
217
215
|
|
|
218
216
|
|
|
219
|
-
def
|
|
220
|
-
|
|
221
|
-
Some of the Python data types get converted to SQL types, when function/procedure is created.
|
|
222
|
-
So, to properly compare types, we use mapping based on:
|
|
223
|
-
https://docs.snowflake.com/en/developer-guide/udf-stored-procedure-data-type-mapping#sql-python-data-type-mappings
|
|
217
|
+
def same_type(sf_type: str, local_type: str) -> bool:
|
|
218
|
+
sf_type, local_type = sf_type.upper(), local_type.upper()
|
|
224
219
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
220
|
+
# 1. Types are equal out of the box
|
|
221
|
+
if sf_type == local_type:
|
|
222
|
+
return True
|
|
223
|
+
|
|
224
|
+
# 2. Local type is alias for Snowflake type
|
|
225
|
+
local_type = user_to_sql_type_mapper(local_type).upper()
|
|
226
|
+
if sf_type == local_type:
|
|
227
|
+
return True
|
|
228
|
+
|
|
229
|
+
# 3. Local type is a subset of Snowflake type, e.g. VARCHAR(N) == VARCHAR
|
|
230
|
+
# We solved for local VARCHAR(N) in point 1 & 2 as those are explicit types
|
|
231
|
+
if sf_type.startswith(local_type):
|
|
232
|
+
return True
|
|
233
|
+
|
|
234
|
+
# 4. Snowflake types is subset of local type
|
|
235
|
+
if local_type.startswith(sf_type):
|
|
236
|
+
return True
|
|
237
|
+
|
|
238
|
+
return False
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def user_to_sql_type_mapper(user_provided_type: str) -> str:
|
|
229
242
|
mapping = {
|
|
230
|
-
"
|
|
231
|
-
"
|
|
232
|
-
"
|
|
233
|
-
|
|
243
|
+
("VARCHAR", "(16777216)"): ("CHAR", "TEXT", "STRING"),
|
|
244
|
+
("BINARY", "(8388608)"): ("BINARY", "VARBINARY"),
|
|
245
|
+
("NUMBER", "(38,0)"): (
|
|
246
|
+
"NUMBER",
|
|
247
|
+
"DECIMAL",
|
|
248
|
+
"INT",
|
|
249
|
+
"INTEGER",
|
|
250
|
+
"BIGINT",
|
|
251
|
+
"SMALLINT",
|
|
252
|
+
"TINYINT",
|
|
253
|
+
"BYTEINT",
|
|
254
|
+
),
|
|
255
|
+
("FLOAT", ""): (
|
|
256
|
+
"FLOAT",
|
|
257
|
+
"DOUBLE",
|
|
258
|
+
"DOUBLE PRECISION",
|
|
259
|
+
"REAL",
|
|
260
|
+
"FLOAT",
|
|
261
|
+
"FLOAT4",
|
|
262
|
+
"FLOAT8",
|
|
263
|
+
),
|
|
264
|
+
("TIMESTAMP_NTZ", ""): ("TIMESTAMP_NTZ", "TIMESTAMPNTZ", "DATETIME"),
|
|
265
|
+
("TIMESTAMP_LTZ", ""): ("TIMESTAMP_LTZ", "TIMESTAMPLTZ"),
|
|
266
|
+
("TIMESTAMP_TZ", ""): ("TIMESTAMP_TZ", "TIMESTAMPTZ"),
|
|
234
267
|
}
|
|
235
268
|
|
|
236
|
-
|
|
269
|
+
user_provided_type = user_provided_type.upper()
|
|
270
|
+
for (cast_type, default), matching_types in mapping.items():
|
|
271
|
+
for type_ in matching_types:
|
|
272
|
+
if user_provided_type == type_:
|
|
273
|
+
# TEXT -> VARCHAR(16777216)
|
|
274
|
+
return cast_type + default
|
|
275
|
+
if user_provided_type.startswith(type_):
|
|
276
|
+
# TEXT(30) -> VARCHAR(30)
|
|
277
|
+
return user_provided_type.replace(type_, cast_type + default)
|
|
278
|
+
return user_provided_type
|
|
237
279
|
|
|
238
280
|
|
|
239
281
|
def _compare_imports(
|
|
@@ -169,7 +169,7 @@ class AnacondaPackagesManager(SqlExecutionMixin):
|
|
|
169
169
|
|
|
170
170
|
def find_packages_available_in_snowflake_anaconda(self) -> AnacondaPackages:
|
|
171
171
|
"""
|
|
172
|
-
Finds
|
|
172
|
+
Finds Python packages available in Snowflake to use in functions and stored procedures.
|
|
173
173
|
It tries to get the list of packages using SQL query
|
|
174
174
|
but if the try fails then the fallback is to parse JSON containing info about Snowflake's Anaconda channel.
|
|
175
175
|
"""
|
|
@@ -178,7 +178,7 @@ class AnacondaPackagesManager(SqlExecutionMixin):
|
|
|
178
178
|
|
|
179
179
|
def _query_snowflake_for_available_packages(self) -> dict[str, AvailablePackage]:
|
|
180
180
|
cursor = self._execute_query(
|
|
181
|
-
"select package_name, version from information_schema.packages where language = 'python'",
|
|
181
|
+
"select package_name, version from snowflake.information_schema.packages where language = 'python'",
|
|
182
182
|
cursor_class=DictCursor,
|
|
183
183
|
)
|
|
184
184
|
if cursor.rowcount is None or cursor.rowcount == 0:
|
|
@@ -39,6 +39,7 @@ from snowflake.cli.api.identifiers import FQN
|
|
|
39
39
|
from snowflake.cli.api.output.types import (
|
|
40
40
|
CollectionResult,
|
|
41
41
|
MessageResult,
|
|
42
|
+
QueryResult,
|
|
42
43
|
SingleQueryResult,
|
|
43
44
|
)
|
|
44
45
|
from snowflake.cli.api.project.util import is_valid_object_name
|
|
@@ -99,44 +100,10 @@ def list_images(
|
|
|
99
100
|
**options,
|
|
100
101
|
) -> CollectionResult:
|
|
101
102
|
"""Lists images in the given repository."""
|
|
102
|
-
|
|
103
|
-
database = repository_manager.get_database()
|
|
104
|
-
schema = repository_manager.get_schema()
|
|
105
|
-
url = repository_manager.get_repository_url(name.identifier)
|
|
106
|
-
api_url = repository_manager.get_repository_api_url(url)
|
|
107
|
-
bearer_login = RegistryManager().login_to_registry(api_url)
|
|
108
|
-
repos = []
|
|
109
|
-
query: Optional[str] = f"{api_url}/_catalog?n=10"
|
|
110
|
-
|
|
111
|
-
while query:
|
|
112
|
-
# Make paginated catalog requests
|
|
113
|
-
response = requests.get(
|
|
114
|
-
query, headers={"Authorization": f"Bearer {bearer_login}"}
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
if response.status_code != 200:
|
|
118
|
-
raise ClickException(f"Call to the registry failed {response.text}")
|
|
119
|
-
|
|
120
|
-
data = json.loads(response.text)
|
|
121
|
-
if "repositories" in data:
|
|
122
|
-
repos.extend(data["repositories"])
|
|
123
|
-
|
|
124
|
-
if "Link" in response.headers:
|
|
125
|
-
# There are more results
|
|
126
|
-
query = f"{api_url}/_catalog?n=10&last={repos[-1]}"
|
|
127
|
-
else:
|
|
128
|
-
query = None
|
|
129
|
-
|
|
130
|
-
images = []
|
|
131
|
-
for repo in repos:
|
|
132
|
-
prefix = f"/{database}/{schema}/{name}/"
|
|
133
|
-
repo = repo.replace("baserepo/", prefix)
|
|
134
|
-
images.append({"image": repo})
|
|
135
|
-
|
|
136
|
-
return CollectionResult(images)
|
|
103
|
+
return QueryResult(ImageRepositoryManager().list_images(name.identifier))
|
|
137
104
|
|
|
138
105
|
|
|
139
|
-
@app.command("list-tags", requires_connection=True)
|
|
106
|
+
@app.command("list-tags", requires_connection=True, deprecated=True)
|
|
140
107
|
def list_tags(
|
|
141
108
|
name: FQN = REPO_NAME_ARGUMENT,
|
|
142
109
|
image_name: str = typer.Option(
|
|
@@ -149,7 +116,7 @@ def list_tags(
|
|
|
149
116
|
),
|
|
150
117
|
**options,
|
|
151
118
|
) -> CollectionResult:
|
|
152
|
-
"""Lists tags for the given image in a repository."""
|
|
119
|
+
"""Lists tags for the given image in a repository. This command is deprecated and will be removed in a future release. Use `list-images` instead."""
|
|
153
120
|
|
|
154
121
|
repository_manager = ImageRepositoryManager()
|
|
155
122
|
url = repository_manager.get_repository_url(name.identifier)
|
|
@@ -18,6 +18,7 @@ from snowflake.cli._plugins.spcs.common import handle_object_already_exists
|
|
|
18
18
|
from snowflake.cli.api.constants import ObjectType
|
|
19
19
|
from snowflake.cli.api.identifiers import FQN
|
|
20
20
|
from snowflake.cli.api.sql_execution import SqlExecutionMixin
|
|
21
|
+
from snowflake.connector.cursor import SnowflakeCursor
|
|
21
22
|
from snowflake.connector.errors import ProgrammingError
|
|
22
23
|
|
|
23
24
|
|
|
@@ -32,7 +33,6 @@ class ImageRepositoryManager(SqlExecutionMixin):
|
|
|
32
33
|
return self._conn.role
|
|
33
34
|
|
|
34
35
|
def get_repository_url(self, repo_name: str, with_scheme: bool = True):
|
|
35
|
-
|
|
36
36
|
repo_row = self.show_specific_object(
|
|
37
37
|
"image repositories", repo_name, check_schema=True
|
|
38
38
|
)
|
|
@@ -82,3 +82,6 @@ class ImageRepositoryManager(SqlExecutionMixin):
|
|
|
82
82
|
handle_object_already_exists(
|
|
83
83
|
e, ObjectType.IMAGE_REPOSITORY, name, replace_available=True
|
|
84
84
|
)
|
|
85
|
+
|
|
86
|
+
def list_images(self, repo_name: str) -> SnowflakeCursor:
|
|
87
|
+
return self._execute_query(f"show images in image repository {repo_name}")
|