snowflake-cli-labs 2.6.0rc0__py3-none-any.whl → 2.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- snowflake/cli/__about__.py +1 -1
- snowflake/cli/api/cli_global_context.py +9 -0
- snowflake/cli/api/commands/decorators.py +9 -4
- snowflake/cli/api/commands/execution_metadata.py +40 -0
- snowflake/cli/api/commands/flags.py +45 -36
- snowflake/cli/api/commands/project_initialisation.py +5 -2
- snowflake/cli/api/commands/snow_typer.py +20 -9
- snowflake/cli/api/config.py +4 -0
- snowflake/cli/api/errno.py +27 -0
- snowflake/cli/api/feature_flags.py +5 -0
- snowflake/cli/api/identifiers.py +20 -3
- snowflake/cli/api/output/types.py +9 -0
- snowflake/cli/api/project/definition_manager.py +2 -2
- snowflake/cli/api/project/project_verification.py +23 -0
- snowflake/cli/api/project/schemas/entities/application_entity.py +50 -0
- snowflake/cli/api/project/schemas/entities/application_package_entity.py +63 -0
- snowflake/cli/api/project/schemas/entities/common.py +85 -0
- snowflake/cli/api/project/schemas/entities/entities.py +30 -0
- snowflake/cli/api/project/schemas/project_definition.py +114 -22
- snowflake/cli/api/project/schemas/streamlit/streamlit.py +5 -4
- snowflake/cli/api/project/schemas/template.py +77 -0
- snowflake/cli/{plugins/nativeapp/errno.py → api/rendering/__init__.py} +0 -2
- snowflake/cli/api/{utils/rendering.py → rendering/jinja.py} +3 -48
- snowflake/cli/api/rendering/project_definition_templates.py +39 -0
- snowflake/cli/api/rendering/project_templates.py +97 -0
- snowflake/cli/api/rendering/sql_templates.py +56 -0
- snowflake/cli/api/rest_api.py +84 -25
- snowflake/cli/api/sql_execution.py +40 -1
- snowflake/cli/api/utils/definition_rendering.py +8 -5
- snowflake/cli/app/cli_app.py +0 -2
- snowflake/cli/app/commands_registration/builtin_plugins.py +4 -0
- snowflake/cli/app/dev/docs/project_definition_docs_generator.py +2 -2
- snowflake/cli/app/loggers.py +10 -6
- snowflake/cli/app/printing.py +17 -7
- snowflake/cli/app/snow_connector.py +9 -1
- snowflake/cli/app/telemetry.py +41 -2
- snowflake/cli/plugins/connection/commands.py +13 -3
- snowflake/cli/plugins/connection/util.py +73 -18
- snowflake/cli/plugins/cortex/commands.py +2 -1
- snowflake/cli/plugins/git/commands.py +20 -4
- snowflake/cli/plugins/git/manager.py +44 -20
- snowflake/cli/plugins/init/__init__.py +13 -0
- snowflake/cli/plugins/init/commands.py +242 -0
- snowflake/cli/plugins/init/plugin_spec.py +30 -0
- snowflake/cli/plugins/nativeapp/codegen/artifact_processor.py +40 -0
- snowflake/cli/plugins/nativeapp/codegen/compiler.py +57 -27
- snowflake/cli/plugins/nativeapp/codegen/sandbox.py +99 -10
- snowflake/cli/plugins/nativeapp/codegen/setup/native_app_setup_processor.py +172 -0
- snowflake/cli/plugins/nativeapp/codegen/setup/setup_driver.py.source +56 -0
- snowflake/cli/plugins/nativeapp/codegen/snowpark/python_processor.py +21 -21
- snowflake/cli/plugins/nativeapp/commands.py +100 -6
- snowflake/cli/plugins/nativeapp/constants.py +0 -6
- snowflake/cli/plugins/nativeapp/exceptions.py +37 -12
- snowflake/cli/plugins/nativeapp/init.py +1 -1
- snowflake/cli/plugins/nativeapp/manager.py +114 -39
- snowflake/cli/plugins/nativeapp/project_model.py +8 -4
- snowflake/cli/plugins/nativeapp/run_processor.py +117 -102
- snowflake/cli/plugins/nativeapp/teardown_processor.py +7 -2
- snowflake/cli/plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +146 -0
- snowflake/cli/plugins/nativeapp/version/commands.py +19 -3
- snowflake/cli/plugins/nativeapp/version/version_processor.py +11 -3
- snowflake/cli/plugins/object/commands.py +1 -1
- snowflake/cli/plugins/object/manager.py +2 -15
- snowflake/cli/plugins/snowpark/commands.py +34 -26
- snowflake/cli/plugins/snowpark/common.py +88 -27
- snowflake/cli/plugins/snowpark/manager.py +16 -5
- snowflake/cli/plugins/snowpark/models.py +6 -0
- snowflake/cli/plugins/sql/commands.py +3 -5
- snowflake/cli/plugins/sql/manager.py +1 -1
- snowflake/cli/plugins/stage/commands.py +2 -2
- snowflake/cli/plugins/stage/diff.py +27 -64
- snowflake/cli/plugins/stage/manager.py +290 -86
- snowflake/cli/plugins/stage/md5.py +160 -0
- snowflake/cli/plugins/streamlit/commands.py +20 -6
- snowflake/cli/plugins/streamlit/manager.py +46 -32
- snowflake/cli/plugins/workspace/__init__.py +13 -0
- snowflake/cli/plugins/workspace/commands.py +35 -0
- snowflake/cli/plugins/workspace/plugin_spec.py +30 -0
- snowflake/cli/templates/default_snowpark/app/__init__.py +0 -13
- snowflake/cli/templates/default_snowpark/app/common.py +0 -15
- snowflake/cli/templates/default_snowpark/app/functions.py +0 -14
- snowflake/cli/templates/default_snowpark/app/procedures.py +0 -14
- snowflake/cli/templates/default_streamlit/common/hello.py +0 -15
- snowflake/cli/templates/default_streamlit/pages/my_page.py +0 -14
- snowflake/cli/templates/default_streamlit/streamlit_app.py +0 -14
- {snowflake_cli_labs-2.6.0rc0.dist-info → snowflake_cli_labs-2.7.0.dist-info}/METADATA +7 -6
- {snowflake_cli_labs-2.6.0rc0.dist-info → snowflake_cli_labs-2.7.0.dist-info}/RECORD +90 -69
- {snowflake_cli_labs-2.6.0rc0.dist-info → snowflake_cli_labs-2.7.0.dist-info}/WHEEL +0 -0
- {snowflake_cli_labs-2.6.0rc0.dist-info → snowflake_cli_labs-2.7.0.dist-info}/entry_points.txt +0 -0
- {snowflake_cli_labs-2.6.0rc0.dist-info → snowflake_cli_labs-2.7.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -32,7 +32,9 @@ from snowflake.cli.api.output.types import (
|
|
|
32
32
|
CommandResult,
|
|
33
33
|
MessageResult,
|
|
34
34
|
ObjectResult,
|
|
35
|
+
StreamResult,
|
|
35
36
|
)
|
|
37
|
+
from snowflake.cli.api.project.project_verification import assert_project_type
|
|
36
38
|
from snowflake.cli.api.secure_path import SecurePath
|
|
37
39
|
from snowflake.cli.plugins.nativeapp.common_flags import (
|
|
38
40
|
ForceOption,
|
|
@@ -57,7 +59,15 @@ from snowflake.cli.plugins.nativeapp.utils import (
|
|
|
57
59
|
get_first_paragraph_from_markdown_file,
|
|
58
60
|
shallow_git_clone,
|
|
59
61
|
)
|
|
62
|
+
from snowflake.cli.plugins.nativeapp.v2_conversions.v2_to_v1_decorator import (
|
|
63
|
+
nativeapp_definition_v2_to_v1,
|
|
64
|
+
)
|
|
60
65
|
from snowflake.cli.plugins.nativeapp.version.commands import app as versions_app
|
|
66
|
+
from snowflake.cli.plugins.stage.diff import (
|
|
67
|
+
DiffResult,
|
|
68
|
+
compute_stage_diff,
|
|
69
|
+
print_diff_to_console,
|
|
70
|
+
)
|
|
61
71
|
|
|
62
72
|
app = SnowTyperFactory(
|
|
63
73
|
name="app",
|
|
@@ -145,13 +155,17 @@ def app_list_templates(**options) -> CommandResult:
|
|
|
145
155
|
|
|
146
156
|
|
|
147
157
|
@app.command("bundle")
|
|
148
|
-
@with_project_definition(
|
|
158
|
+
@with_project_definition()
|
|
159
|
+
@nativeapp_definition_v2_to_v1
|
|
149
160
|
def app_bundle(
|
|
150
161
|
**options,
|
|
151
162
|
) -> CommandResult:
|
|
152
163
|
"""
|
|
153
164
|
Prepares a local folder with configured app artifacts.
|
|
154
165
|
"""
|
|
166
|
+
|
|
167
|
+
assert_project_type("native_app")
|
|
168
|
+
|
|
155
169
|
manager = NativeAppManager(
|
|
156
170
|
project_definition=cli_context.project_definition.native_app,
|
|
157
171
|
project_root=cli_context.project_root,
|
|
@@ -160,8 +174,35 @@ def app_bundle(
|
|
|
160
174
|
return MessageResult(f"Bundle generated at {manager.deploy_root}")
|
|
161
175
|
|
|
162
176
|
|
|
177
|
+
@app.command("diff", requires_connection=True, hidden=True)
|
|
178
|
+
@with_project_definition()
|
|
179
|
+
@nativeapp_definition_v2_to_v1
|
|
180
|
+
def app_diff(
|
|
181
|
+
**options,
|
|
182
|
+
) -> CommandResult:
|
|
183
|
+
"""
|
|
184
|
+
Performs a diff between the app's source stage and the local deploy root.
|
|
185
|
+
"""
|
|
186
|
+
assert_project_type("native_app")
|
|
187
|
+
|
|
188
|
+
manager = NativeAppManager(
|
|
189
|
+
project_definition=cli_context.project_definition.native_app,
|
|
190
|
+
project_root=cli_context.project_root,
|
|
191
|
+
)
|
|
192
|
+
bundle_map = manager.build_bundle()
|
|
193
|
+
diff: DiffResult = compute_stage_diff(
|
|
194
|
+
local_root=Path(manager.deploy_root), stage_fqn=manager.stage_fqn
|
|
195
|
+
)
|
|
196
|
+
if cli_context.output_format == OutputFormat.JSON:
|
|
197
|
+
return ObjectResult(diff.to_dict())
|
|
198
|
+
else:
|
|
199
|
+
print_diff_to_console(diff, bundle_map)
|
|
200
|
+
return None # don't print any output
|
|
201
|
+
|
|
202
|
+
|
|
163
203
|
@app.command("run", requires_connection=True)
|
|
164
|
-
@with_project_definition(
|
|
204
|
+
@with_project_definition()
|
|
205
|
+
@nativeapp_definition_v2_to_v1
|
|
165
206
|
def app_run(
|
|
166
207
|
version: Optional[str] = typer.Option(
|
|
167
208
|
None,
|
|
@@ -191,6 +232,8 @@ def app_run(
|
|
|
191
232
|
then creates or upgrades an application object from the application package.
|
|
192
233
|
"""
|
|
193
234
|
|
|
235
|
+
assert_project_type("native_app")
|
|
236
|
+
|
|
194
237
|
is_interactive = False
|
|
195
238
|
if force:
|
|
196
239
|
policy = AllowAlwaysPolicy()
|
|
@@ -221,7 +264,8 @@ def app_run(
|
|
|
221
264
|
|
|
222
265
|
|
|
223
266
|
@app.command("open", requires_connection=True)
|
|
224
|
-
@with_project_definition(
|
|
267
|
+
@with_project_definition()
|
|
268
|
+
@nativeapp_definition_v2_to_v1
|
|
225
269
|
def app_open(
|
|
226
270
|
**options,
|
|
227
271
|
) -> CommandResult:
|
|
@@ -229,6 +273,9 @@ def app_open(
|
|
|
229
273
|
Opens the Snowflake Native App inside of your browser,
|
|
230
274
|
once it has been installed in your account.
|
|
231
275
|
"""
|
|
276
|
+
|
|
277
|
+
assert_project_type("native_app")
|
|
278
|
+
|
|
232
279
|
manager = NativeAppManager(
|
|
233
280
|
project_definition=cli_context.project_definition.native_app,
|
|
234
281
|
project_root=cli_context.project_root,
|
|
@@ -243,7 +290,8 @@ def app_open(
|
|
|
243
290
|
|
|
244
291
|
|
|
245
292
|
@app.command("teardown", requires_connection=True)
|
|
246
|
-
@with_project_definition(
|
|
293
|
+
@with_project_definition()
|
|
294
|
+
@nativeapp_definition_v2_to_v1
|
|
247
295
|
def app_teardown(
|
|
248
296
|
force: Optional[bool] = ForceOption,
|
|
249
297
|
cascade: Optional[bool] = typer.Option(
|
|
@@ -257,6 +305,9 @@ def app_teardown(
|
|
|
257
305
|
"""
|
|
258
306
|
Attempts to drop both the application object and application package as defined in the project definition file.
|
|
259
307
|
"""
|
|
308
|
+
|
|
309
|
+
assert_project_type("native_app")
|
|
310
|
+
|
|
260
311
|
processor = NativeAppTeardownProcessor(
|
|
261
312
|
project_definition=cli_context.project_definition.native_app,
|
|
262
313
|
project_root=cli_context.project_root,
|
|
@@ -266,7 +317,8 @@ def app_teardown(
|
|
|
266
317
|
|
|
267
318
|
|
|
268
319
|
@app.command("deploy", requires_connection=True)
|
|
269
|
-
@with_project_definition(
|
|
320
|
+
@with_project_definition()
|
|
321
|
+
@nativeapp_definition_v2_to_v1
|
|
270
322
|
def app_deploy(
|
|
271
323
|
prune: Optional[bool] = typer.Option(
|
|
272
324
|
default=None,
|
|
@@ -296,6 +348,9 @@ def app_deploy(
|
|
|
296
348
|
Creates an application package in your Snowflake account and syncs the local changes to the stage without creating or updating the application.
|
|
297
349
|
Running this command with no arguments at all, as in `snow app deploy`, is a shorthand for `snow app deploy --prune --recursive`.
|
|
298
350
|
"""
|
|
351
|
+
|
|
352
|
+
assert_project_type("native_app")
|
|
353
|
+
|
|
299
354
|
has_paths = paths is not None and len(paths) > 0
|
|
300
355
|
if prune is None and recursive is None and not has_paths:
|
|
301
356
|
prune = True
|
|
@@ -329,11 +384,15 @@ def app_deploy(
|
|
|
329
384
|
|
|
330
385
|
|
|
331
386
|
@app.command("validate", requires_connection=True)
|
|
332
|
-
@with_project_definition(
|
|
387
|
+
@with_project_definition()
|
|
388
|
+
@nativeapp_definition_v2_to_v1
|
|
333
389
|
def app_validate(**options):
|
|
334
390
|
"""
|
|
335
391
|
Validates a deployed Snowflake Native App's setup script.
|
|
336
392
|
"""
|
|
393
|
+
|
|
394
|
+
assert_project_type("native_app")
|
|
395
|
+
|
|
337
396
|
manager = NativeAppManager(
|
|
338
397
|
project_definition=cli_context.project_definition.native_app,
|
|
339
398
|
project_root=cli_context.project_root,
|
|
@@ -343,3 +402,38 @@ def app_validate(**options):
|
|
|
343
402
|
|
|
344
403
|
manager.validate(use_scratch_stage=True)
|
|
345
404
|
return MessageResult("Snowflake Native App validation succeeded.")
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
@app.command("events", hidden=True, requires_connection=True)
|
|
408
|
+
@with_project_definition()
|
|
409
|
+
@nativeapp_definition_v2_to_v1
|
|
410
|
+
def app_events(**options):
|
|
411
|
+
"""Fetches events for this app from the event table configured in Snowflake."""
|
|
412
|
+
assert_project_type("native_app")
|
|
413
|
+
|
|
414
|
+
manager = NativeAppManager(
|
|
415
|
+
project_definition=cli_context.project_definition.native_app,
|
|
416
|
+
project_root=cli_context.project_root,
|
|
417
|
+
)
|
|
418
|
+
events = manager.get_events()
|
|
419
|
+
if not events:
|
|
420
|
+
return MessageResult("No events found.")
|
|
421
|
+
|
|
422
|
+
def g():
|
|
423
|
+
for event in events:
|
|
424
|
+
yield EventResult(event)
|
|
425
|
+
|
|
426
|
+
return StreamResult(g())
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
class EventResult(ObjectResult, MessageResult):
|
|
430
|
+
"""ObjectResult that renders as a custom string when not printed as JSON."""
|
|
431
|
+
|
|
432
|
+
@property
|
|
433
|
+
def message(self):
|
|
434
|
+
e = self._element
|
|
435
|
+
return f"{e['TIMESTAMP']} {e['VALUE']}"
|
|
436
|
+
|
|
437
|
+
@property
|
|
438
|
+
def result(self):
|
|
439
|
+
return self._element
|
|
@@ -25,9 +25,3 @@ PATCH_COL = "patch"
|
|
|
25
25
|
|
|
26
26
|
INTERNAL_DISTRIBUTION = "internal"
|
|
27
27
|
EXTERNAL_DISTRIBUTION = "external"
|
|
28
|
-
|
|
29
|
-
ERROR_MESSAGE_2003 = "does not exist or not authorized"
|
|
30
|
-
ERROR_MESSAGE_2043 = "Object does not exist, or operation cannot be performed."
|
|
31
|
-
ERROR_MESSAGE_606 = "No active warehouse selected in the current session."
|
|
32
|
-
ERROR_MESSAGE_093079 = "Application is no longer available for use"
|
|
33
|
-
ERROR_MESSAGE_093128 = "The application owns one or more objects within the account"
|
|
@@ -12,7 +12,10 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
15
17
|
from textwrap import dedent
|
|
18
|
+
from typing import Optional
|
|
16
19
|
|
|
17
20
|
import jinja2
|
|
18
21
|
from click.exceptions import ClickException
|
|
@@ -36,12 +39,12 @@ class ApplicationPackageDoesNotExistError(ClickException):
|
|
|
36
39
|
)
|
|
37
40
|
|
|
38
41
|
|
|
39
|
-
class
|
|
42
|
+
class ApplicationCreatedExternallyError(ClickException):
|
|
40
43
|
"""An application object not created by Snowflake CLI exists with the same name."""
|
|
41
44
|
|
|
42
45
|
def __init__(self, name: str):
|
|
43
46
|
super().__init__(
|
|
44
|
-
f'
|
|
47
|
+
f'An application object "{name}" not created by Snowflake CLI already exists in the account.'
|
|
45
48
|
)
|
|
46
49
|
|
|
47
50
|
|
|
@@ -54,18 +57,23 @@ class UnexpectedOwnerError(ClickException):
|
|
|
54
57
|
)
|
|
55
58
|
|
|
56
59
|
|
|
57
|
-
class
|
|
58
|
-
"""A referenced
|
|
60
|
+
class MissingScriptError(ClickException):
|
|
61
|
+
"""A referenced script was not found."""
|
|
59
62
|
|
|
60
63
|
def __init__(self, relpath: str):
|
|
61
|
-
super().__init__(f'
|
|
64
|
+
super().__init__(f'Script "{relpath}" does not exist')
|
|
62
65
|
|
|
63
66
|
|
|
64
|
-
class
|
|
65
|
-
"""A referenced
|
|
67
|
+
class InvalidScriptError(ClickException):
|
|
68
|
+
"""A referenced script had syntax error(s)."""
|
|
66
69
|
|
|
67
|
-
def __init__(
|
|
68
|
-
|
|
70
|
+
def __init__(
|
|
71
|
+
self, relpath: str, err: jinja2.TemplateError, lineno: Optional[int] = None
|
|
72
|
+
):
|
|
73
|
+
lineno_str = f":{lineno}" if lineno is not None else ""
|
|
74
|
+
super().__init__(
|
|
75
|
+
f'Script "{relpath}{lineno_str}" does not contain a valid template: {err.message}'
|
|
76
|
+
)
|
|
69
77
|
self.err = err
|
|
70
78
|
|
|
71
79
|
|
|
@@ -79,14 +87,14 @@ class MissingSchemaError(ClickException):
|
|
|
79
87
|
class CouldNotDropApplicationPackageWithVersions(ClickException):
|
|
80
88
|
"""Application package could not be dropped as it has versions associated with it."""
|
|
81
89
|
|
|
82
|
-
def __init__(self):
|
|
90
|
+
def __init__(self, additional_msg: str = ""):
|
|
83
91
|
super().__init__(
|
|
84
92
|
dedent(
|
|
85
93
|
f"""
|
|
86
94
|
{self.__doc__}
|
|
87
|
-
|
|
95
|
+
{additional_msg}
|
|
88
96
|
"""
|
|
89
|
-
)
|
|
97
|
+
).strip()
|
|
90
98
|
)
|
|
91
99
|
|
|
92
100
|
|
|
@@ -95,3 +103,20 @@ class SetupScriptFailedValidation(ClickException):
|
|
|
95
103
|
|
|
96
104
|
def __init__(self):
|
|
97
105
|
super().__init__(self.__doc__)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class NoEventTableForAccount(ClickException):
|
|
109
|
+
"""No event table was found for this Snowflake account."""
|
|
110
|
+
|
|
111
|
+
INSTRUCTIONS = dedent(
|
|
112
|
+
"""\
|
|
113
|
+
Ask your Snowflake administrator to set up an event table for your account by following the docs at
|
|
114
|
+
https://docs.snowflake.com/en/developer-guide/logging-tracing/event-table-setting-up.
|
|
115
|
+
|
|
116
|
+
If your account is configured to send events to an organization event account, create a new
|
|
117
|
+
connection to this account using `snow connection add` and re-run this command using the new connection.
|
|
118
|
+
More information on event accounts is available at https://docs.snowflake.com/en/developer-guide/native-apps/setting-up-logging-and-events#configure-an-account-to-store-shared-events."""
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
def __init__(self):
|
|
122
|
+
super().__init__(f"{self.__doc__}\n\n{self.INSTRUCTIONS}")
|
|
@@ -28,8 +28,8 @@ from snowflake.cli.api.project.util import (
|
|
|
28
28
|
is_valid_unquoted_identifier,
|
|
29
29
|
to_identifier,
|
|
30
30
|
)
|
|
31
|
+
from snowflake.cli.api.rendering.jinja import jinja_render_from_file
|
|
31
32
|
from snowflake.cli.api.secure_path import SecurePath
|
|
32
|
-
from snowflake.cli.api.utils.rendering import jinja_render_from_file
|
|
33
33
|
from yaml import dump, safe_dump, safe_load
|
|
34
34
|
|
|
35
35
|
log = logging.getLogger(__name__)
|
|
@@ -17,14 +17,20 @@ from __future__ import annotations
|
|
|
17
17
|
import json
|
|
18
18
|
import os
|
|
19
19
|
from abc import ABC, abstractmethod
|
|
20
|
+
from contextlib import contextmanager
|
|
20
21
|
from functools import cached_property
|
|
21
22
|
from pathlib import Path
|
|
22
23
|
from textwrap import dedent
|
|
23
|
-
from typing import List, Optional, TypedDict
|
|
24
|
+
from typing import Any, List, NoReturn, Optional, TypedDict
|
|
24
25
|
|
|
25
26
|
import jinja2
|
|
26
27
|
from click import ClickException
|
|
27
28
|
from snowflake.cli.api.console import cli_console as cc
|
|
29
|
+
from snowflake.cli.api.errno import (
|
|
30
|
+
DOES_NOT_EXIST_OR_CANNOT_BE_PERFORMED,
|
|
31
|
+
DOES_NOT_EXIST_OR_NOT_AUTHORIZED,
|
|
32
|
+
NO_WAREHOUSE_SELECTED_IN_SESSION,
|
|
33
|
+
)
|
|
28
34
|
from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
|
|
29
35
|
from snowflake.cli.api.project.schemas.native_app.application import (
|
|
30
36
|
ApplicationPostDeployHook,
|
|
@@ -48,9 +54,6 @@ from snowflake.cli.plugins.nativeapp.codegen.compiler import (
|
|
|
48
54
|
from snowflake.cli.plugins.nativeapp.constants import (
|
|
49
55
|
ALLOWED_SPECIAL_COMMENTS,
|
|
50
56
|
COMMENT_COL,
|
|
51
|
-
ERROR_MESSAGE_606,
|
|
52
|
-
ERROR_MESSAGE_2003,
|
|
53
|
-
ERROR_MESSAGE_2043,
|
|
54
57
|
INTERNAL_DISTRIBUTION,
|
|
55
58
|
NAME_COL,
|
|
56
59
|
OWNER_COL,
|
|
@@ -59,8 +62,9 @@ from snowflake.cli.plugins.nativeapp.constants import (
|
|
|
59
62
|
from snowflake.cli.plugins.nativeapp.exceptions import (
|
|
60
63
|
ApplicationPackageAlreadyExistsError,
|
|
61
64
|
ApplicationPackageDoesNotExistError,
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
InvalidScriptError,
|
|
66
|
+
MissingScriptError,
|
|
67
|
+
NoEventTableForAccount,
|
|
64
68
|
SetupScriptFailedValidation,
|
|
65
69
|
UnexpectedOwnerError,
|
|
66
70
|
)
|
|
@@ -78,16 +82,16 @@ from snowflake.cli.plugins.stage.diff import (
|
|
|
78
82
|
to_stage_path,
|
|
79
83
|
)
|
|
80
84
|
from snowflake.cli.plugins.stage.manager import StageManager
|
|
81
|
-
from snowflake.connector import ProgrammingError
|
|
85
|
+
from snowflake.connector import DictCursor, ProgrammingError
|
|
82
86
|
|
|
83
87
|
ApplicationOwnedObject = TypedDict("ApplicationOwnedObject", {"name": str, "type": str})
|
|
84
88
|
|
|
85
89
|
|
|
86
90
|
def generic_sql_error_handler(
|
|
87
91
|
err: ProgrammingError, role: Optional[str] = None, warehouse: Optional[str] = None
|
|
88
|
-
):
|
|
92
|
+
) -> NoReturn:
|
|
89
93
|
# Potential refactor: If moving away from Python 3.8 and 3.9 to >= 3.10, use match ... case
|
|
90
|
-
if err.errno ==
|
|
94
|
+
if err.errno == DOES_NOT_EXIST_OR_CANNOT_BE_PERFORMED:
|
|
91
95
|
raise ProgrammingError(
|
|
92
96
|
msg=dedent(
|
|
93
97
|
f"""\
|
|
@@ -98,7 +102,7 @@ def generic_sql_error_handler(
|
|
|
98
102
|
),
|
|
99
103
|
errno=err.errno,
|
|
100
104
|
)
|
|
101
|
-
elif err.errno ==
|
|
105
|
+
elif err.errno == NO_WAREHOUSE_SELECTED_IN_SESSION:
|
|
102
106
|
raise ProgrammingError(
|
|
103
107
|
msg=dedent(
|
|
104
108
|
f"""\
|
|
@@ -108,7 +112,7 @@ def generic_sql_error_handler(
|
|
|
108
112
|
),
|
|
109
113
|
errno=err.errno,
|
|
110
114
|
)
|
|
111
|
-
elif
|
|
115
|
+
elif "does not exist or not authorized" in err.msg:
|
|
112
116
|
raise ProgrammingError(
|
|
113
117
|
msg=dedent(
|
|
114
118
|
f"""\
|
|
@@ -216,10 +220,40 @@ class NativeAppManager(SqlExecutionMixin):
|
|
|
216
220
|
def package_warehouse(self) -> Optional[str]:
|
|
217
221
|
return self.na_project.package_warehouse
|
|
218
222
|
|
|
223
|
+
@contextmanager
|
|
224
|
+
def use_package_warehouse(self):
|
|
225
|
+
if self.package_warehouse:
|
|
226
|
+
with self.use_warehouse(self.package_warehouse):
|
|
227
|
+
yield
|
|
228
|
+
else:
|
|
229
|
+
raise ClickException(
|
|
230
|
+
dedent(
|
|
231
|
+
f"""\
|
|
232
|
+
Application package warehouse cannot be empty.
|
|
233
|
+
Please provide a value for it in your connection information or your project definition file.
|
|
234
|
+
"""
|
|
235
|
+
)
|
|
236
|
+
)
|
|
237
|
+
|
|
219
238
|
@property
|
|
220
239
|
def application_warehouse(self) -> Optional[str]:
|
|
221
240
|
return self.na_project.application_warehouse
|
|
222
241
|
|
|
242
|
+
@contextmanager
|
|
243
|
+
def use_application_warehouse(self):
|
|
244
|
+
if self.application_warehouse:
|
|
245
|
+
with self.use_warehouse(self.application_warehouse):
|
|
246
|
+
yield
|
|
247
|
+
else:
|
|
248
|
+
raise ClickException(
|
|
249
|
+
dedent(
|
|
250
|
+
f"""\
|
|
251
|
+
Application warehouse cannot be empty.
|
|
252
|
+
Please provide a value for it in your connection information or your project definition file.
|
|
253
|
+
"""
|
|
254
|
+
)
|
|
255
|
+
)
|
|
256
|
+
|
|
223
257
|
@property
|
|
224
258
|
def project_identifier(self) -> str:
|
|
225
259
|
return self.na_project.project_identifier
|
|
@@ -280,6 +314,12 @@ class NativeAppManager(SqlExecutionMixin):
|
|
|
280
314
|
)
|
|
281
315
|
)
|
|
282
316
|
|
|
317
|
+
@cached_property
|
|
318
|
+
def account_event_table(self) -> str:
|
|
319
|
+
query = "show parameters like 'event_table' in account"
|
|
320
|
+
results = self._execute_query(query, cursor_class=DictCursor)
|
|
321
|
+
return next((r["value"] for r in results if r["key"] == "EVENT_TABLE"), "")
|
|
322
|
+
|
|
283
323
|
def verify_project_distribution(
|
|
284
324
|
self, expected_distribution: Optional[str] = None
|
|
285
325
|
) -> bool:
|
|
@@ -482,7 +522,8 @@ class NativeAppManager(SqlExecutionMixin):
|
|
|
482
522
|
def get_snowsight_url(self) -> str:
|
|
483
523
|
"""Returns the URL that can be used to visit this app via Snowsight."""
|
|
484
524
|
name = identifier_for_url(self.app_name)
|
|
485
|
-
|
|
525
|
+
with self.use_application_warehouse():
|
|
526
|
+
return make_snowsight_url(self._conn, f"/#/apps/application/{name}")
|
|
486
527
|
|
|
487
528
|
def create_app_package(self) -> None:
|
|
488
529
|
"""
|
|
@@ -527,6 +568,36 @@ class NativeAppManager(SqlExecutionMixin):
|
|
|
527
568
|
)
|
|
528
569
|
)
|
|
529
570
|
|
|
571
|
+
def _expand_script_templates(
|
|
572
|
+
self, env: jinja2.Environment, jinja_context: dict[str, Any], scripts: List[str]
|
|
573
|
+
) -> List[str]:
|
|
574
|
+
"""
|
|
575
|
+
Input:
|
|
576
|
+
- env: Jinja2 environment
|
|
577
|
+
- jinja_context: a dictionary with the jinja context
|
|
578
|
+
- scripts: list of scripts that need to be expanded with Jinja
|
|
579
|
+
Returns:
|
|
580
|
+
- List of expanded scripts content.
|
|
581
|
+
Size of the return list is the same as the size of the input scripts list.
|
|
582
|
+
"""
|
|
583
|
+
scripts_contents = []
|
|
584
|
+
for relpath in scripts:
|
|
585
|
+
try:
|
|
586
|
+
template = env.get_template(relpath)
|
|
587
|
+
result = template.render(**jinja_context)
|
|
588
|
+
scripts_contents.append(result)
|
|
589
|
+
|
|
590
|
+
except jinja2.TemplateNotFound as e:
|
|
591
|
+
raise MissingScriptError(e.name) from e
|
|
592
|
+
|
|
593
|
+
except jinja2.TemplateSyntaxError as e:
|
|
594
|
+
raise InvalidScriptError(e.name, e, e.lineno) from e
|
|
595
|
+
|
|
596
|
+
except jinja2.UndefinedError as e:
|
|
597
|
+
raise InvalidScriptError(relpath, e) from e
|
|
598
|
+
|
|
599
|
+
return scripts_contents
|
|
600
|
+
|
|
530
601
|
def _apply_package_scripts(self) -> None:
|
|
531
602
|
"""
|
|
532
603
|
Assuming the application package exists and we are using the correct role,
|
|
@@ -538,34 +609,20 @@ class NativeAppManager(SqlExecutionMixin):
|
|
|
538
609
|
undefined=jinja2.StrictUndefined,
|
|
539
610
|
)
|
|
540
611
|
|
|
541
|
-
queued_queries =
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
template = env.get_template(relpath)
|
|
545
|
-
result = template.render(dict(package_name=self.package_name))
|
|
546
|
-
queued_queries.append(result)
|
|
547
|
-
|
|
548
|
-
except jinja2.TemplateNotFound as e:
|
|
549
|
-
raise MissingPackageScriptError(e.name)
|
|
550
|
-
|
|
551
|
-
except jinja2.TemplateSyntaxError as e:
|
|
552
|
-
raise InvalidPackageScriptError(e.name, e)
|
|
553
|
-
|
|
554
|
-
except jinja2.UndefinedError as e:
|
|
555
|
-
raise InvalidPackageScriptError(relpath, e)
|
|
612
|
+
queued_queries = self._expand_script_templates(
|
|
613
|
+
env, dict(package_name=self.package_name), self.package_scripts
|
|
614
|
+
)
|
|
556
615
|
|
|
557
616
|
# once we're sure all the templates expanded correctly, execute all of them
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
err, role=self.package_role, warehouse=self.package_warehouse
|
|
568
|
-
)
|
|
617
|
+
with self.use_package_warehouse():
|
|
618
|
+
try:
|
|
619
|
+
for i, queries in enumerate(queued_queries):
|
|
620
|
+
cc.step(f"Applying package script: {self.package_scripts[i]}")
|
|
621
|
+
self._execute_queries(queries)
|
|
622
|
+
except ProgrammingError as err:
|
|
623
|
+
generic_sql_error_handler(
|
|
624
|
+
err, role=self.package_role, warehouse=self.package_warehouse
|
|
625
|
+
)
|
|
569
626
|
|
|
570
627
|
def deploy(
|
|
571
628
|
self,
|
|
@@ -642,7 +699,7 @@ class NativeAppManager(SqlExecutionMixin):
|
|
|
642
699
|
f"call system$validate_native_app_setup('{prefixed_stage_fqn}')"
|
|
643
700
|
)
|
|
644
701
|
except ProgrammingError as err:
|
|
645
|
-
if err.errno ==
|
|
702
|
+
if err.errno == DOES_NOT_EXIST_OR_NOT_AUTHORIZED:
|
|
646
703
|
raise ApplicationPackageDoesNotExistError(self.package_name)
|
|
647
704
|
generic_sql_error_handler(err)
|
|
648
705
|
else:
|
|
@@ -657,6 +714,24 @@ class NativeAppManager(SqlExecutionMixin):
|
|
|
657
714
|
f"drop stage if exists {self.scratch_stage_fqn}"
|
|
658
715
|
)
|
|
659
716
|
|
|
717
|
+
def get_events(self) -> list[dict]:
|
|
718
|
+
if not self.account_event_table:
|
|
719
|
+
raise NoEventTableForAccount()
|
|
720
|
+
|
|
721
|
+
# resource_attributes:"snow.database.name" uses the unquoted/uppercase app name
|
|
722
|
+
app_name = unquote_identifier(self.app_name)
|
|
723
|
+
query = dedent(
|
|
724
|
+
f"""\
|
|
725
|
+
select timestamp, value::varchar value
|
|
726
|
+
from {self.account_event_table}
|
|
727
|
+
where resource_attributes:"snow.database.name" = '{app_name}'
|
|
728
|
+
order by timestamp asc;"""
|
|
729
|
+
)
|
|
730
|
+
try:
|
|
731
|
+
return self._execute_query(query, cursor_class=DictCursor).fetchall()
|
|
732
|
+
except ProgrammingError as err:
|
|
733
|
+
generic_sql_error_handler(err)
|
|
734
|
+
|
|
660
735
|
|
|
661
736
|
def _validation_item_to_str(item: dict[str, str | int]):
|
|
662
737
|
s = item["message"]
|
|
@@ -105,16 +105,20 @@ class NativeAppProjectModel:
|
|
|
105
105
|
@cached_property
|
|
106
106
|
def package_warehouse(self) -> Optional[str]:
|
|
107
107
|
if self.definition.package and self.definition.package.warehouse:
|
|
108
|
-
return self.definition.package.warehouse
|
|
108
|
+
return to_identifier(self.definition.package.warehouse)
|
|
109
109
|
else:
|
|
110
|
-
|
|
110
|
+
if cli_context.connection.warehouse:
|
|
111
|
+
return to_identifier(cli_context.connection.warehouse)
|
|
112
|
+
return None
|
|
111
113
|
|
|
112
114
|
@cached_property
|
|
113
115
|
def application_warehouse(self) -> Optional[str]:
|
|
114
116
|
if self.definition.application and self.definition.application.warehouse:
|
|
115
|
-
return self.definition.application.warehouse
|
|
117
|
+
return to_identifier(self.definition.application.warehouse)
|
|
116
118
|
else:
|
|
117
|
-
|
|
119
|
+
if cli_context.connection.warehouse:
|
|
120
|
+
return to_identifier(cli_context.connection.warehouse)
|
|
121
|
+
return None
|
|
118
122
|
|
|
119
123
|
@cached_property
|
|
120
124
|
def project_identifier(self) -> str:
|