snowflake-cli-labs 2.6.0rc0__py3-none-any.whl → 2.7.0rc0__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 +4 -1
- snowflake/cli/api/commands/snow_typer.py +20 -9
- snowflake/cli/api/config.py +3 -0
- snowflake/cli/api/errno.py +27 -0
- snowflake/cli/api/feature_flags.py +1 -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 +4 -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 +69 -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 +4 -2
- snowflake/cli/plugins/stage/manager.py +290 -86
- snowflake/cli/plugins/streamlit/commands.py +20 -6
- snowflake/cli/plugins/streamlit/manager.py +29 -27
- 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.0rc0.dist-info}/METADATA +7 -6
- {snowflake_cli_labs-2.6.0rc0.dist-info → snowflake_cli_labs-2.7.0rc0.dist-info}/RECORD +89 -69
- {snowflake_cli_labs-2.6.0rc0.dist-info → snowflake_cli_labs-2.7.0rc0.dist-info}/WHEEL +0 -0
- {snowflake_cli_labs-2.6.0rc0.dist-info → snowflake_cli_labs-2.7.0rc0.dist-info}/entry_points.txt +0 -0
- {snowflake_cli_labs-2.6.0rc0.dist-info → snowflake_cli_labs-2.7.0rc0.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,6 +59,9 @@ 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
|
|
61
66
|
|
|
62
67
|
app = SnowTyperFactory(
|
|
@@ -145,13 +150,17 @@ def app_list_templates(**options) -> CommandResult:
|
|
|
145
150
|
|
|
146
151
|
|
|
147
152
|
@app.command("bundle")
|
|
148
|
-
@with_project_definition(
|
|
153
|
+
@with_project_definition()
|
|
154
|
+
@nativeapp_definition_v2_to_v1
|
|
149
155
|
def app_bundle(
|
|
150
156
|
**options,
|
|
151
157
|
) -> CommandResult:
|
|
152
158
|
"""
|
|
153
159
|
Prepares a local folder with configured app artifacts.
|
|
154
160
|
"""
|
|
161
|
+
|
|
162
|
+
assert_project_type("native_app")
|
|
163
|
+
|
|
155
164
|
manager = NativeAppManager(
|
|
156
165
|
project_definition=cli_context.project_definition.native_app,
|
|
157
166
|
project_root=cli_context.project_root,
|
|
@@ -161,7 +170,8 @@ def app_bundle(
|
|
|
161
170
|
|
|
162
171
|
|
|
163
172
|
@app.command("run", requires_connection=True)
|
|
164
|
-
@with_project_definition(
|
|
173
|
+
@with_project_definition()
|
|
174
|
+
@nativeapp_definition_v2_to_v1
|
|
165
175
|
def app_run(
|
|
166
176
|
version: Optional[str] = typer.Option(
|
|
167
177
|
None,
|
|
@@ -191,6 +201,8 @@ def app_run(
|
|
|
191
201
|
then creates or upgrades an application object from the application package.
|
|
192
202
|
"""
|
|
193
203
|
|
|
204
|
+
assert_project_type("native_app")
|
|
205
|
+
|
|
194
206
|
is_interactive = False
|
|
195
207
|
if force:
|
|
196
208
|
policy = AllowAlwaysPolicy()
|
|
@@ -221,7 +233,8 @@ def app_run(
|
|
|
221
233
|
|
|
222
234
|
|
|
223
235
|
@app.command("open", requires_connection=True)
|
|
224
|
-
@with_project_definition(
|
|
236
|
+
@with_project_definition()
|
|
237
|
+
@nativeapp_definition_v2_to_v1
|
|
225
238
|
def app_open(
|
|
226
239
|
**options,
|
|
227
240
|
) -> CommandResult:
|
|
@@ -229,6 +242,9 @@ def app_open(
|
|
|
229
242
|
Opens the Snowflake Native App inside of your browser,
|
|
230
243
|
once it has been installed in your account.
|
|
231
244
|
"""
|
|
245
|
+
|
|
246
|
+
assert_project_type("native_app")
|
|
247
|
+
|
|
232
248
|
manager = NativeAppManager(
|
|
233
249
|
project_definition=cli_context.project_definition.native_app,
|
|
234
250
|
project_root=cli_context.project_root,
|
|
@@ -243,7 +259,8 @@ def app_open(
|
|
|
243
259
|
|
|
244
260
|
|
|
245
261
|
@app.command("teardown", requires_connection=True)
|
|
246
|
-
@with_project_definition(
|
|
262
|
+
@with_project_definition()
|
|
263
|
+
@nativeapp_definition_v2_to_v1
|
|
247
264
|
def app_teardown(
|
|
248
265
|
force: Optional[bool] = ForceOption,
|
|
249
266
|
cascade: Optional[bool] = typer.Option(
|
|
@@ -257,6 +274,9 @@ def app_teardown(
|
|
|
257
274
|
"""
|
|
258
275
|
Attempts to drop both the application object and application package as defined in the project definition file.
|
|
259
276
|
"""
|
|
277
|
+
|
|
278
|
+
assert_project_type("native_app")
|
|
279
|
+
|
|
260
280
|
processor = NativeAppTeardownProcessor(
|
|
261
281
|
project_definition=cli_context.project_definition.native_app,
|
|
262
282
|
project_root=cli_context.project_root,
|
|
@@ -266,7 +286,8 @@ def app_teardown(
|
|
|
266
286
|
|
|
267
287
|
|
|
268
288
|
@app.command("deploy", requires_connection=True)
|
|
269
|
-
@with_project_definition(
|
|
289
|
+
@with_project_definition()
|
|
290
|
+
@nativeapp_definition_v2_to_v1
|
|
270
291
|
def app_deploy(
|
|
271
292
|
prune: Optional[bool] = typer.Option(
|
|
272
293
|
default=None,
|
|
@@ -296,6 +317,9 @@ def app_deploy(
|
|
|
296
317
|
Creates an application package in your Snowflake account and syncs the local changes to the stage without creating or updating the application.
|
|
297
318
|
Running this command with no arguments at all, as in `snow app deploy`, is a shorthand for `snow app deploy --prune --recursive`.
|
|
298
319
|
"""
|
|
320
|
+
|
|
321
|
+
assert_project_type("native_app")
|
|
322
|
+
|
|
299
323
|
has_paths = paths is not None and len(paths) > 0
|
|
300
324
|
if prune is None and recursive is None and not has_paths:
|
|
301
325
|
prune = True
|
|
@@ -329,11 +353,15 @@ def app_deploy(
|
|
|
329
353
|
|
|
330
354
|
|
|
331
355
|
@app.command("validate", requires_connection=True)
|
|
332
|
-
@with_project_definition(
|
|
356
|
+
@with_project_definition()
|
|
357
|
+
@nativeapp_definition_v2_to_v1
|
|
333
358
|
def app_validate(**options):
|
|
334
359
|
"""
|
|
335
360
|
Validates a deployed Snowflake Native App's setup script.
|
|
336
361
|
"""
|
|
362
|
+
|
|
363
|
+
assert_project_type("native_app")
|
|
364
|
+
|
|
337
365
|
manager = NativeAppManager(
|
|
338
366
|
project_definition=cli_context.project_definition.native_app,
|
|
339
367
|
project_root=cli_context.project_root,
|
|
@@ -343,3 +371,38 @@ def app_validate(**options):
|
|
|
343
371
|
|
|
344
372
|
manager.validate(use_scratch_stage=True)
|
|
345
373
|
return MessageResult("Snowflake Native App validation succeeded.")
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
@app.command("events", hidden=True, requires_connection=True)
|
|
377
|
+
@with_project_definition()
|
|
378
|
+
@nativeapp_definition_v2_to_v1
|
|
379
|
+
def app_events(**options):
|
|
380
|
+
"""Fetches events for this app from the event table configured in Snowflake."""
|
|
381
|
+
assert_project_type("native_app")
|
|
382
|
+
|
|
383
|
+
manager = NativeAppManager(
|
|
384
|
+
project_definition=cli_context.project_definition.native_app,
|
|
385
|
+
project_root=cli_context.project_root,
|
|
386
|
+
)
|
|
387
|
+
events = manager.get_events()
|
|
388
|
+
if not events:
|
|
389
|
+
return MessageResult("No events found.")
|
|
390
|
+
|
|
391
|
+
def g():
|
|
392
|
+
for event in events:
|
|
393
|
+
yield EventResult(event)
|
|
394
|
+
|
|
395
|
+
return StreamResult(g())
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
class EventResult(ObjectResult, MessageResult):
|
|
399
|
+
"""ObjectResult that renders as a custom string when not printed as JSON."""
|
|
400
|
+
|
|
401
|
+
@property
|
|
402
|
+
def message(self):
|
|
403
|
+
e = self._element
|
|
404
|
+
return f"{e['TIMESTAMP']} {e['VALUE']}"
|
|
405
|
+
|
|
406
|
+
@property
|
|
407
|
+
def result(self):
|
|
408
|
+
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:
|