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.
Files changed (89) 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 +4 -1
  7. snowflake/cli/api/commands/snow_typer.py +20 -9
  8. snowflake/cli/api/config.py +3 -0
  9. snowflake/cli/api/errno.py +27 -0
  10. snowflake/cli/api/feature_flags.py +1 -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/rest_api.py +84 -25
  28. snowflake/cli/api/sql_execution.py +40 -1
  29. snowflake/cli/api/utils/definition_rendering.py +8 -5
  30. snowflake/cli/app/cli_app.py +0 -2
  31. snowflake/cli/app/commands_registration/builtin_plugins.py +4 -0
  32. snowflake/cli/app/dev/docs/project_definition_docs_generator.py +2 -2
  33. snowflake/cli/app/loggers.py +10 -6
  34. snowflake/cli/app/printing.py +17 -7
  35. snowflake/cli/app/snow_connector.py +9 -1
  36. snowflake/cli/app/telemetry.py +41 -2
  37. snowflake/cli/plugins/connection/commands.py +4 -3
  38. snowflake/cli/plugins/connection/util.py +73 -18
  39. snowflake/cli/plugins/cortex/commands.py +2 -1
  40. snowflake/cli/plugins/git/commands.py +20 -4
  41. snowflake/cli/plugins/git/manager.py +44 -20
  42. snowflake/cli/plugins/init/__init__.py +13 -0
  43. snowflake/cli/plugins/init/commands.py +242 -0
  44. snowflake/cli/plugins/init/plugin_spec.py +30 -0
  45. snowflake/cli/plugins/nativeapp/codegen/artifact_processor.py +40 -0
  46. snowflake/cli/plugins/nativeapp/codegen/compiler.py +57 -27
  47. snowflake/cli/plugins/nativeapp/codegen/sandbox.py +99 -10
  48. snowflake/cli/plugins/nativeapp/codegen/setup/native_app_setup_processor.py +172 -0
  49. snowflake/cli/plugins/nativeapp/codegen/setup/setup_driver.py.source +56 -0
  50. snowflake/cli/plugins/nativeapp/codegen/snowpark/python_processor.py +21 -21
  51. snowflake/cli/plugins/nativeapp/commands.py +69 -6
  52. snowflake/cli/plugins/nativeapp/constants.py +0 -6
  53. snowflake/cli/plugins/nativeapp/exceptions.py +37 -12
  54. snowflake/cli/plugins/nativeapp/init.py +1 -1
  55. snowflake/cli/plugins/nativeapp/manager.py +114 -39
  56. snowflake/cli/plugins/nativeapp/project_model.py +8 -4
  57. snowflake/cli/plugins/nativeapp/run_processor.py +117 -102
  58. snowflake/cli/plugins/nativeapp/teardown_processor.py +7 -2
  59. snowflake/cli/plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +146 -0
  60. snowflake/cli/plugins/nativeapp/version/commands.py +19 -3
  61. snowflake/cli/plugins/nativeapp/version/version_processor.py +11 -3
  62. snowflake/cli/plugins/object/commands.py +1 -1
  63. snowflake/cli/plugins/object/manager.py +2 -15
  64. snowflake/cli/plugins/snowpark/commands.py +34 -26
  65. snowflake/cli/plugins/snowpark/common.py +88 -27
  66. snowflake/cli/plugins/snowpark/manager.py +16 -5
  67. snowflake/cli/plugins/snowpark/models.py +6 -0
  68. snowflake/cli/plugins/sql/commands.py +3 -5
  69. snowflake/cli/plugins/sql/manager.py +1 -1
  70. snowflake/cli/plugins/stage/commands.py +2 -2
  71. snowflake/cli/plugins/stage/diff.py +4 -2
  72. snowflake/cli/plugins/stage/manager.py +290 -86
  73. snowflake/cli/plugins/streamlit/commands.py +20 -6
  74. snowflake/cli/plugins/streamlit/manager.py +29 -27
  75. snowflake/cli/plugins/workspace/__init__.py +13 -0
  76. snowflake/cli/plugins/workspace/commands.py +35 -0
  77. snowflake/cli/plugins/workspace/plugin_spec.py +30 -0
  78. snowflake/cli/templates/default_snowpark/app/__init__.py +0 -13
  79. snowflake/cli/templates/default_snowpark/app/common.py +0 -15
  80. snowflake/cli/templates/default_snowpark/app/functions.py +0 -14
  81. snowflake/cli/templates/default_snowpark/app/procedures.py +0 -14
  82. snowflake/cli/templates/default_streamlit/common/hello.py +0 -15
  83. snowflake/cli/templates/default_streamlit/pages/my_page.py +0 -14
  84. snowflake/cli/templates/default_streamlit/streamlit_app.py +0 -14
  85. {snowflake_cli_labs-2.6.0rc0.dist-info → snowflake_cli_labs-2.7.0rc0.dist-info}/METADATA +7 -6
  86. {snowflake_cli_labs-2.6.0rc0.dist-info → snowflake_cli_labs-2.7.0rc0.dist-info}/RECORD +89 -69
  87. {snowflake_cli_labs-2.6.0rc0.dist-info → snowflake_cli_labs-2.7.0rc0.dist-info}/WHEEL +0 -0
  88. {snowflake_cli_labs-2.6.0rc0.dist-info → snowflake_cli_labs-2.7.0rc0.dist-info}/entry_points.txt +0 -0
  89. {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("native_app")
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("native_app")
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("native_app")
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("native_app")
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("native_app")
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("native_app")
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 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: