snowflake-cli-labs 2.6.1__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.
Files changed (86) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/api/cli_global_context.py +9 -0
  3. snowflake/cli/api/commands/decorators.py +9 -4
  4. snowflake/cli/api/commands/execution_metadata.py +40 -0
  5. snowflake/cli/api/commands/flags.py +45 -36
  6. snowflake/cli/api/commands/project_initialisation.py +5 -2
  7. snowflake/cli/api/commands/snow_typer.py +20 -9
  8. snowflake/cli/api/config.py +1 -0
  9. snowflake/cli/api/errno.py +27 -0
  10. snowflake/cli/api/feature_flags.py +5 -0
  11. snowflake/cli/api/identifiers.py +20 -3
  12. snowflake/cli/api/output/types.py +9 -0
  13. snowflake/cli/api/project/definition_manager.py +2 -2
  14. snowflake/cli/api/project/project_verification.py +23 -0
  15. snowflake/cli/api/project/schemas/entities/application_entity.py +50 -0
  16. snowflake/cli/api/project/schemas/entities/application_package_entity.py +63 -0
  17. snowflake/cli/api/project/schemas/entities/common.py +85 -0
  18. snowflake/cli/api/project/schemas/entities/entities.py +30 -0
  19. snowflake/cli/api/project/schemas/project_definition.py +114 -22
  20. snowflake/cli/api/project/schemas/streamlit/streamlit.py +5 -4
  21. snowflake/cli/api/project/schemas/template.py +77 -0
  22. snowflake/cli/{plugins/nativeapp/errno.py → api/rendering/__init__.py} +0 -2
  23. snowflake/cli/api/{utils/rendering.py → rendering/jinja.py} +3 -48
  24. snowflake/cli/api/rendering/project_definition_templates.py +39 -0
  25. snowflake/cli/api/rendering/project_templates.py +97 -0
  26. snowflake/cli/api/rendering/sql_templates.py +56 -0
  27. snowflake/cli/api/sql_execution.py +40 -1
  28. snowflake/cli/api/utils/definition_rendering.py +8 -5
  29. snowflake/cli/app/commands_registration/builtin_plugins.py +4 -0
  30. snowflake/cli/app/dev/docs/project_definition_docs_generator.py +2 -2
  31. snowflake/cli/app/loggers.py +3 -1
  32. snowflake/cli/app/printing.py +17 -7
  33. snowflake/cli/app/snow_connector.py +9 -1
  34. snowflake/cli/app/telemetry.py +41 -2
  35. snowflake/cli/plugins/connection/commands.py +13 -3
  36. snowflake/cli/plugins/connection/util.py +73 -18
  37. snowflake/cli/plugins/cortex/commands.py +2 -1
  38. snowflake/cli/plugins/git/commands.py +20 -4
  39. snowflake/cli/plugins/git/manager.py +44 -20
  40. snowflake/cli/plugins/init/__init__.py +13 -0
  41. snowflake/cli/plugins/init/commands.py +242 -0
  42. snowflake/cli/plugins/init/plugin_spec.py +30 -0
  43. snowflake/cli/plugins/nativeapp/codegen/artifact_processor.py +40 -0
  44. snowflake/cli/plugins/nativeapp/codegen/compiler.py +57 -27
  45. snowflake/cli/plugins/nativeapp/codegen/sandbox.py +99 -10
  46. snowflake/cli/plugins/nativeapp/codegen/setup/native_app_setup_processor.py +172 -0
  47. snowflake/cli/plugins/nativeapp/codegen/setup/setup_driver.py.source +56 -0
  48. snowflake/cli/plugins/nativeapp/codegen/snowpark/python_processor.py +21 -21
  49. snowflake/cli/plugins/nativeapp/commands.py +100 -6
  50. snowflake/cli/plugins/nativeapp/constants.py +0 -6
  51. snowflake/cli/plugins/nativeapp/exceptions.py +37 -12
  52. snowflake/cli/plugins/nativeapp/init.py +1 -1
  53. snowflake/cli/plugins/nativeapp/manager.py +114 -39
  54. snowflake/cli/plugins/nativeapp/project_model.py +8 -4
  55. snowflake/cli/plugins/nativeapp/run_processor.py +117 -102
  56. snowflake/cli/plugins/nativeapp/teardown_processor.py +7 -2
  57. snowflake/cli/plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +146 -0
  58. snowflake/cli/plugins/nativeapp/version/commands.py +19 -3
  59. snowflake/cli/plugins/nativeapp/version/version_processor.py +11 -3
  60. snowflake/cli/plugins/snowpark/commands.py +34 -26
  61. snowflake/cli/plugins/snowpark/common.py +88 -27
  62. snowflake/cli/plugins/snowpark/manager.py +16 -5
  63. snowflake/cli/plugins/snowpark/models.py +6 -0
  64. snowflake/cli/plugins/sql/commands.py +3 -5
  65. snowflake/cli/plugins/sql/manager.py +1 -1
  66. snowflake/cli/plugins/stage/commands.py +2 -2
  67. snowflake/cli/plugins/stage/diff.py +27 -64
  68. snowflake/cli/plugins/stage/manager.py +290 -86
  69. snowflake/cli/plugins/stage/md5.py +160 -0
  70. snowflake/cli/plugins/streamlit/commands.py +20 -6
  71. snowflake/cli/plugins/streamlit/manager.py +46 -32
  72. snowflake/cli/plugins/workspace/__init__.py +13 -0
  73. snowflake/cli/plugins/workspace/commands.py +35 -0
  74. snowflake/cli/plugins/workspace/plugin_spec.py +30 -0
  75. snowflake/cli/templates/default_snowpark/app/__init__.py +0 -13
  76. snowflake/cli/templates/default_snowpark/app/common.py +0 -15
  77. snowflake/cli/templates/default_snowpark/app/functions.py +0 -14
  78. snowflake/cli/templates/default_snowpark/app/procedures.py +0 -14
  79. snowflake/cli/templates/default_streamlit/common/hello.py +0 -15
  80. snowflake/cli/templates/default_streamlit/pages/my_page.py +0 -14
  81. snowflake/cli/templates/default_streamlit/streamlit_app.py +0 -14
  82. {snowflake_cli_labs-2.6.1.dist-info → snowflake_cli_labs-2.7.0.dist-info}/METADATA +7 -6
  83. {snowflake_cli_labs-2.6.1.dist-info → snowflake_cli_labs-2.7.0.dist-info}/RECORD +86 -65
  84. {snowflake_cli_labs-2.6.1.dist-info → snowflake_cli_labs-2.7.0.dist-info}/WHEEL +0 -0
  85. {snowflake_cli_labs-2.6.1.dist-info → snowflake_cli_labs-2.7.0.dist-info}/entry_points.txt +0 -0
  86. {snowflake_cli_labs-2.6.1.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("native_app")
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("native_app")
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("native_app")
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("native_app")
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("native_app")
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("native_app")
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 ApplicationAlreadyExistsError(ClickException):
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'A application object "{name}" not created in development mode using files on a named stage already exists in the account.'
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 MissingPackageScriptError(ClickException):
58
- """A referenced package script was not found."""
60
+ class MissingScriptError(ClickException):
61
+ """A referenced script was not found."""
59
62
 
60
63
  def __init__(self, relpath: str):
61
- super().__init__(f'Package script "{relpath}" does not exist')
64
+ super().__init__(f'Script "{relpath}" does not exist')
62
65
 
63
66
 
64
- class InvalidPackageScriptError(ClickException):
65
- """A referenced package script had syntax error(s)."""
67
+ class InvalidScriptError(ClickException):
68
+ """A referenced script had syntax error(s)."""
66
69
 
67
- def __init__(self, relpath: str, err: jinja2.TemplateError):
68
- super().__init__(f'Package script "{relpath}" is not a valid jinja2 template')
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
- Versions must be dropped first using “snow app version drop”.
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
- InvalidPackageScriptError,
63
- MissingPackageScriptError,
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 == 2043 or err.msg.__contains__(ERROR_MESSAGE_2043):
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 == 606 or err.msg.__contains__(ERROR_MESSAGE_606):
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 err.msg.__contains__("does not exist or not authorized"):
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
- return make_snowsight_url(self._conn, f"/#/apps/application/{name}")
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
- for relpath in self.package_scripts:
543
- try:
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
- try:
559
- if self.package_warehouse:
560
- self._execute_query(f"use warehouse {self.package_warehouse}")
561
-
562
- for i, queries in enumerate(queued_queries):
563
- cc.step(f"Applying package script: {self.package_scripts[i]}")
564
- self._execute_queries(queries)
565
- except ProgrammingError as err:
566
- generic_sql_error_handler(
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 == 2003 and ERROR_MESSAGE_2003 in err.msg:
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
- return cli_context.connection.warehouse
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
- return cli_context.connection.warehouse
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: