snowflake-cli-labs 2.6.1__py3-none-any.whl → 2.7.0rc1__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 (84) 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/errno.py +27 -0
  9. snowflake/cli/api/feature_flags.py +1 -0
  10. snowflake/cli/api/identifiers.py +20 -3
  11. snowflake/cli/api/output/types.py +9 -0
  12. snowflake/cli/api/project/definition_manager.py +2 -2
  13. snowflake/cli/api/project/project_verification.py +23 -0
  14. snowflake/cli/api/project/schemas/entities/application_entity.py +50 -0
  15. snowflake/cli/api/project/schemas/entities/application_package_entity.py +63 -0
  16. snowflake/cli/api/project/schemas/entities/common.py +85 -0
  17. snowflake/cli/api/project/schemas/entities/entities.py +30 -0
  18. snowflake/cli/api/project/schemas/project_definition.py +114 -22
  19. snowflake/cli/api/project/schemas/streamlit/streamlit.py +5 -4
  20. snowflake/cli/api/project/schemas/template.py +77 -0
  21. snowflake/cli/{plugins/nativeapp/errno.py → api/rendering/__init__.py} +0 -2
  22. snowflake/cli/api/{utils/rendering.py → rendering/jinja.py} +3 -48
  23. snowflake/cli/api/rendering/project_definition_templates.py +39 -0
  24. snowflake/cli/api/rendering/project_templates.py +97 -0
  25. snowflake/cli/api/rendering/sql_templates.py +56 -0
  26. snowflake/cli/api/sql_execution.py +40 -1
  27. snowflake/cli/api/utils/definition_rendering.py +8 -5
  28. snowflake/cli/app/commands_registration/builtin_plugins.py +4 -0
  29. snowflake/cli/app/dev/docs/project_definition_docs_generator.py +2 -2
  30. snowflake/cli/app/loggers.py +3 -1
  31. snowflake/cli/app/printing.py +17 -7
  32. snowflake/cli/app/snow_connector.py +9 -1
  33. snowflake/cli/app/telemetry.py +41 -2
  34. snowflake/cli/plugins/connection/commands.py +4 -3
  35. snowflake/cli/plugins/connection/util.py +73 -18
  36. snowflake/cli/plugins/cortex/commands.py +2 -1
  37. snowflake/cli/plugins/git/commands.py +20 -4
  38. snowflake/cli/plugins/git/manager.py +44 -20
  39. snowflake/cli/plugins/init/__init__.py +13 -0
  40. snowflake/cli/plugins/init/commands.py +242 -0
  41. snowflake/cli/plugins/init/plugin_spec.py +30 -0
  42. snowflake/cli/plugins/nativeapp/codegen/artifact_processor.py +40 -0
  43. snowflake/cli/plugins/nativeapp/codegen/compiler.py +57 -27
  44. snowflake/cli/plugins/nativeapp/codegen/sandbox.py +99 -10
  45. snowflake/cli/plugins/nativeapp/codegen/setup/native_app_setup_processor.py +172 -0
  46. snowflake/cli/plugins/nativeapp/codegen/setup/setup_driver.py.source +56 -0
  47. snowflake/cli/plugins/nativeapp/codegen/snowpark/python_processor.py +21 -21
  48. snowflake/cli/plugins/nativeapp/commands.py +69 -6
  49. snowflake/cli/plugins/nativeapp/constants.py +0 -6
  50. snowflake/cli/plugins/nativeapp/exceptions.py +37 -12
  51. snowflake/cli/plugins/nativeapp/init.py +1 -1
  52. snowflake/cli/plugins/nativeapp/manager.py +114 -39
  53. snowflake/cli/plugins/nativeapp/project_model.py +8 -4
  54. snowflake/cli/plugins/nativeapp/run_processor.py +117 -102
  55. snowflake/cli/plugins/nativeapp/teardown_processor.py +7 -2
  56. snowflake/cli/plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +146 -0
  57. snowflake/cli/plugins/nativeapp/version/commands.py +19 -3
  58. snowflake/cli/plugins/nativeapp/version/version_processor.py +11 -3
  59. snowflake/cli/plugins/snowpark/commands.py +34 -26
  60. snowflake/cli/plugins/snowpark/common.py +88 -27
  61. snowflake/cli/plugins/snowpark/manager.py +16 -5
  62. snowflake/cli/plugins/snowpark/models.py +6 -0
  63. snowflake/cli/plugins/sql/commands.py +3 -5
  64. snowflake/cli/plugins/sql/manager.py +1 -1
  65. snowflake/cli/plugins/stage/commands.py +2 -2
  66. snowflake/cli/plugins/stage/diff.py +4 -2
  67. snowflake/cli/plugins/stage/manager.py +290 -86
  68. snowflake/cli/plugins/streamlit/commands.py +20 -6
  69. snowflake/cli/plugins/streamlit/manager.py +29 -27
  70. snowflake/cli/plugins/workspace/__init__.py +13 -0
  71. snowflake/cli/plugins/workspace/commands.py +35 -0
  72. snowflake/cli/plugins/workspace/plugin_spec.py +30 -0
  73. snowflake/cli/templates/default_snowpark/app/__init__.py +0 -13
  74. snowflake/cli/templates/default_snowpark/app/common.py +0 -15
  75. snowflake/cli/templates/default_snowpark/app/functions.py +0 -14
  76. snowflake/cli/templates/default_snowpark/app/procedures.py +0 -14
  77. snowflake/cli/templates/default_streamlit/common/hello.py +0 -15
  78. snowflake/cli/templates/default_streamlit/pages/my_page.py +0 -14
  79. snowflake/cli/templates/default_streamlit/streamlit_app.py +0 -14
  80. {snowflake_cli_labs-2.6.1.dist-info → snowflake_cli_labs-2.7.0rc1.dist-info}/METADATA +7 -6
  81. {snowflake_cli_labs-2.6.1.dist-info → snowflake_cli_labs-2.7.0rc1.dist-info}/RECORD +84 -64
  82. {snowflake_cli_labs-2.6.1.dist-info → snowflake_cli_labs-2.7.0rc1.dist-info}/WHEEL +0 -0
  83. {snowflake_cli_labs-2.6.1.dist-info → snowflake_cli_labs-2.7.0rc1.dist-info}/entry_points.txt +0 -0
  84. {snowflake_cli_labs-2.6.1.dist-info → snowflake_cli_labs-2.7.0rc1.dist-info}/licenses/LICENSE +0 -0
@@ -20,10 +20,13 @@ import subprocess
20
20
  import sys
21
21
  from enum import Enum
22
22
  from pathlib import Path
23
- from typing import Optional, Sequence, Union
23
+ from typing import Any, Mapping, Optional, Sequence, Union
24
+ from venv import EnvBuilder
24
25
 
25
26
  from click.exceptions import ClickException
26
27
 
28
+ EnvVars = Mapping[str, str] # Only support str -> str for cross-platform compatibility
29
+
27
30
 
28
31
  class SandboxExecutionError(ClickException):
29
32
  """An error occurred while executing a python script."""
@@ -57,6 +60,7 @@ def _execute_python_interpreter(
57
60
  script_source: str,
58
61
  cwd: Optional[Union[str, Path]],
59
62
  timeout: Optional[int],
63
+ env_vars: Optional[EnvVars],
60
64
  ) -> subprocess.CompletedProcess:
61
65
  if not python_executable:
62
66
  raise SandboxExecutionError("No python executable found")
@@ -73,6 +77,7 @@ def _execute_python_interpreter(
73
77
  input=script_source,
74
78
  timeout=timeout,
75
79
  cwd=cwd,
80
+ env=env_vars,
76
81
  )
77
82
 
78
83
 
@@ -81,6 +86,7 @@ def _execute_in_venv(
81
86
  venv_path: Optional[Union[str, Path]] = None,
82
87
  cwd: Optional[Union[str, Path]] = None,
83
88
  timeout: Optional[int] = None,
89
+ env_vars: Optional[EnvVars] = None,
84
90
  ) -> subprocess.CompletedProcess:
85
91
  resolved_venv_path = None
86
92
  if venv_path is None:
@@ -114,7 +120,7 @@ def _execute_in_venv(
114
120
  )
115
121
 
116
122
  return _execute_python_interpreter(
117
- python_executable, script_source, timeout=timeout, cwd=cwd
123
+ python_executable, script_source, timeout=timeout, cwd=cwd, env_vars=env_vars
118
124
  )
119
125
 
120
126
 
@@ -123,6 +129,7 @@ def _execute_in_conda_env(
123
129
  env_name: Optional[str] = None,
124
130
  cwd: Optional[Union[str, Path]] = None,
125
131
  timeout: Optional[int] = None,
132
+ env_vars: Optional[EnvVars] = None,
126
133
  ) -> subprocess.CompletedProcess:
127
134
  conda_env = env_name
128
135
  if conda_env is None:
@@ -142,6 +149,7 @@ def _execute_in_conda_env(
142
149
  script_source,
143
150
  timeout=timeout,
144
151
  cwd=cwd,
152
+ env_vars=env_vars,
145
153
  )
146
154
 
147
155
 
@@ -149,13 +157,18 @@ def _execute_with_system_path_python(
149
157
  script_source: str,
150
158
  cwd: Optional[Union[str, Path]] = None,
151
159
  timeout: Optional[int] = None,
160
+ env_vars: Optional[EnvVars] = None,
152
161
  ) -> subprocess.CompletedProcess:
153
162
  python_executable = (
154
163
  shutil.which("python3") or shutil.which("python") or sys.executable
155
164
  )
156
165
 
157
166
  return _execute_python_interpreter(
158
- python_executable, script_source, timeout=timeout, cwd=cwd
167
+ python_executable,
168
+ script_source,
169
+ timeout=timeout,
170
+ cwd=cwd,
171
+ env_vars=env_vars,
159
172
  )
160
173
 
161
174
 
@@ -172,6 +185,7 @@ def execute_script_in_sandbox(
172
185
  env_type: ExecutionEnvironmentType = ExecutionEnvironmentType.AUTO_DETECT,
173
186
  cwd: Optional[Union[str, Path]] = None,
174
187
  timeout: Optional[int] = None,
188
+ env_vars: Optional[EnvVars] = None,
175
189
  **kwargs,
176
190
  ) -> subprocess.CompletedProcess:
177
191
  """
@@ -194,24 +208,99 @@ def execute_script_in_sandbox(
194
208
  """
195
209
  if env_type == ExecutionEnvironmentType.AUTO_DETECT:
196
210
  if _is_venv_active():
197
- return _execute_in_venv(script_source, cwd=cwd, timeout=timeout)
211
+ return _execute_in_venv(
212
+ script_source, cwd=cwd, timeout=timeout, env_vars=env_vars
213
+ )
198
214
  elif _is_conda_active():
199
- return _execute_in_conda_env(script_source, cwd=cwd, timeout=timeout)
215
+ return _execute_in_conda_env(
216
+ script_source, cwd=cwd, timeout=timeout, env_vars=env_vars
217
+ )
200
218
  else:
201
219
  return _execute_with_system_path_python(
202
- script_source, cwd=cwd, timeout=timeout
220
+ script_source, cwd=cwd, timeout=timeout, env_vars=env_vars
203
221
  )
204
222
  elif env_type == ExecutionEnvironmentType.VENV:
205
223
  return _execute_in_venv(
206
- script_source, kwargs.get("path"), cwd=cwd, timeout=timeout
224
+ script_source,
225
+ kwargs.get("path"),
226
+ cwd=cwd,
227
+ timeout=timeout,
228
+ env_vars=env_vars,
207
229
  )
208
230
  elif env_type == ExecutionEnvironmentType.CONDA:
209
231
  return _execute_in_conda_env(
210
- script_source, kwargs.get("name"), cwd=cwd, timeout=timeout
232
+ script_source,
233
+ kwargs.get("name"),
234
+ cwd=cwd,
235
+ timeout=timeout,
236
+ env_vars=env_vars,
211
237
  )
212
238
  elif env_type == ExecutionEnvironmentType.SYSTEM_PATH:
213
- return _execute_with_system_path_python(script_source, cwd=cwd, timeout=timeout)
239
+ return _execute_with_system_path_python(
240
+ script_source, cwd=cwd, timeout=timeout, env_vars=env_vars
241
+ )
214
242
  else: # ExecutionEnvironmentType.CURRENT
215
243
  return _execute_python_interpreter(
216
- sys.executable, script_source, cwd=cwd, timeout=timeout
244
+ sys.executable, script_source, cwd=cwd, timeout=timeout, env_vars=env_vars
217
245
  )
246
+
247
+
248
+ class SandboxEnvBuilder(EnvBuilder):
249
+ """
250
+ A virtual environment builder that can be used to build an environment suitable for
251
+ executing user-provided python scripts in an isolated sandbox.
252
+ """
253
+
254
+ def __init__(self, path: Path, **kwargs) -> None:
255
+ """
256
+ Creates a new builder with the specified destination path. The path need not
257
+ exist, it will be created when needed (recursively if necessary).
258
+
259
+ Parameters:
260
+ path (Path): The directory in which the sandbox environment will be created.
261
+ """
262
+ super().__init__(**kwargs)
263
+ self.path = path
264
+ self._context: Any = None # cached context
265
+
266
+ def post_setup(self, context) -> None:
267
+ self._context = context
268
+
269
+ def ensure_created(self) -> None:
270
+ """
271
+ Ensures that the sandbox environment has been created and correctly initialized.
272
+ """
273
+ if self.path.exists():
274
+ self._context = self.ensure_directories(self.path)
275
+ else:
276
+ self.path.mkdir(parents=True, exist_ok=True)
277
+ self.create(
278
+ self.path
279
+ ) # will set self._context through the post_setup callback
280
+
281
+ def run_python(self, *args) -> str:
282
+ """
283
+ Executes the python interpreter in the sandboxed environment with the provided arguments.
284
+ This raises a CalledProcessError if the python interpreter was not executed successfully.
285
+
286
+ Returns:
287
+ The output of running the command.
288
+ """
289
+ positional_args = [
290
+ self._context.env_exe,
291
+ "-E", # passing -E ignores all PYTHON* env vars
292
+ *args,
293
+ ]
294
+ kwargs = {
295
+ "cwd": self._context.env_dir,
296
+ "stderr": subprocess.STDOUT,
297
+ }
298
+ env = dict(os.environ)
299
+ env["VIRTUAL_ENV"] = self._context.env_dir
300
+ return subprocess.check_output(positional_args, **kwargs)
301
+
302
+ def pip_install(self, *args: Any) -> None:
303
+ """
304
+ Invokes pip install with the provided arguments.
305
+ """
306
+ self.run_python("-m", "pip", "install", *[str(arg) for arg in args])
@@ -0,0 +1,172 @@
1
+ # Copyright (c) 2024 Snowflake Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ import os.path
19
+ from pathlib import Path
20
+ from typing import List, Optional
21
+
22
+ from click import ClickException
23
+ from snowflake.cli.api.console import cli_console as cc
24
+ from snowflake.cli.api.project.schemas.native_app.path_mapping import (
25
+ PathMapping,
26
+ ProcessorMapping,
27
+ )
28
+ from snowflake.cli.plugins.nativeapp.artifacts import BundleMap, find_setup_script_file
29
+ from snowflake.cli.plugins.nativeapp.codegen.artifact_processor import (
30
+ ArtifactProcessor,
31
+ is_python_file_artifact,
32
+ )
33
+ from snowflake.cli.plugins.nativeapp.codegen.sandbox import (
34
+ ExecutionEnvironmentType,
35
+ SandboxEnvBuilder,
36
+ execute_script_in_sandbox,
37
+ )
38
+ from snowflake.cli.plugins.nativeapp.project_model import NativeAppProjectModel
39
+ from snowflake.cli.plugins.stage.diff import to_stage_path
40
+
41
+ DEFAULT_TIMEOUT = 30
42
+ DRIVER_PATH = Path(__file__).parent / "setup_driver.py.source"
43
+
44
+
45
+ class NativeAppSetupProcessor(ArtifactProcessor):
46
+ def __init__(
47
+ self,
48
+ na_project: NativeAppProjectModel,
49
+ ):
50
+ super().__init__(na_project=na_project)
51
+
52
+ def process(
53
+ self,
54
+ artifact_to_process: PathMapping,
55
+ processor_mapping: Optional[ProcessorMapping],
56
+ **kwargs,
57
+ ) -> None:
58
+ """
59
+ Processes a Python setup script and generates the corresponding SQL commands.
60
+ """
61
+ bundle_map = BundleMap(
62
+ project_root=self._na_project.project_root,
63
+ deploy_root=self._na_project.deploy_root,
64
+ )
65
+ bundle_map.add(artifact_to_process)
66
+
67
+ self._create_or_update_sandbox()
68
+
69
+ cc.phase("Processing Python setup files")
70
+
71
+ files_to_process = []
72
+ for src_file, dest_file in bundle_map.all_mappings(
73
+ absolute=True, expand_directories=True, predicate=is_python_file_artifact
74
+ ):
75
+ cc.message(
76
+ f"Found Python setup file: {src_file.relative_to(self._na_project.project_root)}"
77
+ )
78
+ files_to_process.append(src_file)
79
+
80
+ sql_files_mapping = self._execute_in_sandbox(files_to_process)
81
+ self._generate_setup_sql(sql_files_mapping)
82
+
83
+ def _execute_in_sandbox(self, py_files: List[Path]) -> dict:
84
+ file_count = len(py_files)
85
+ cc.step(f"Processing {file_count} setup file{'s' if file_count > 1 else ''}")
86
+
87
+ env_vars = {
88
+ "_SNOWFLAKE_CLI_PROJECT_PATH": str(self._na_project.project_root),
89
+ "_SNOWFLAKE_CLI_SETUP_FILES": os.pathsep.join(map(str, py_files)),
90
+ "_SNOWFLAKE_CLI_APP_NAME": str(self._na_project.package_name),
91
+ "_SNOWFLAKE_CLI_SQL_DEST_DIR": str(self.generated_root),
92
+ }
93
+
94
+ try:
95
+ result = execute_script_in_sandbox(
96
+ script_source=DRIVER_PATH.read_text(),
97
+ env_type=ExecutionEnvironmentType.VENV,
98
+ cwd=self._na_project.bundle_root,
99
+ timeout=DEFAULT_TIMEOUT,
100
+ path=self.sandbox_root,
101
+ env_vars=env_vars,
102
+ )
103
+ except Exception as e:
104
+ raise ClickException(
105
+ f"Exception while executing python setup script logic: {e}"
106
+ )
107
+
108
+ if result.returncode == 0:
109
+ sql_file_mappings = json.loads(result.stdout)
110
+ return sql_file_mappings
111
+ else:
112
+ raise ClickException(
113
+ f"Failed to execute python setup script logic: {result.stderr}"
114
+ )
115
+
116
+ def _generate_setup_sql(self, sql_file_mappings: dict) -> None:
117
+ if not sql_file_mappings:
118
+ # Nothing to generate
119
+ return
120
+
121
+ generated_root = self.generated_root
122
+ generated_root.mkdir(exist_ok=True, parents=True)
123
+
124
+ cc.step("Patching setup script")
125
+ setup_file_path = find_setup_script_file(
126
+ deploy_root=self._na_project.deploy_root
127
+ )
128
+ with self.edit_file(setup_file_path) as f:
129
+ new_contents = [f.contents]
130
+
131
+ if sql_file_mappings["schemas"]:
132
+ schemas_file = generated_root / sql_file_mappings["schemas"]
133
+ new_contents.insert(
134
+ 0,
135
+ f"EXECUTE IMMEDIATE FROM '/{to_stage_path(schemas_file.relative_to(self._na_project.deploy_root))}';",
136
+ )
137
+
138
+ if sql_file_mappings["compute_pools"]:
139
+ compute_pools_file = generated_root / sql_file_mappings["compute_pools"]
140
+ new_contents.append(
141
+ f"EXECUTE IMMEDIATE FROM '/{to_stage_path(compute_pools_file.relative_to(self._na_project.deploy_root))}';"
142
+ )
143
+
144
+ if sql_file_mappings["services"]:
145
+ services_file = generated_root / sql_file_mappings["services"]
146
+ new_contents.append(
147
+ f"EXECUTE IMMEDIATE FROM '/{to_stage_path(services_file.relative_to(self._na_project.deploy_root))}';"
148
+ )
149
+
150
+ f.edited_contents = "\n".join(new_contents)
151
+
152
+ @property
153
+ def sandbox_root(self):
154
+ return self._na_project.bundle_root / "setup_py_venv"
155
+
156
+ @property
157
+ def generated_root(self):
158
+ return self._na_project.generated_root / "setup_py"
159
+
160
+ def _create_or_update_sandbox(self):
161
+ sandbox_root = self.sandbox_root
162
+ env_builder = SandboxEnvBuilder(sandbox_root, with_pip=True)
163
+ if sandbox_root.exists():
164
+ cc.step("Virtual environment found")
165
+ else:
166
+ cc.step(
167
+ f"Creating virtual environment in {sandbox_root.relative_to(self._na_project.project_root)}"
168
+ )
169
+ env_builder.ensure_created()
170
+
171
+ # Temporarily fetch the library from a location specified via env vars
172
+ env_builder.pip_install(os.environ["SNOWFLAKE_APP_PYTHON_LOC"])
@@ -0,0 +1,56 @@
1
+ # Copyright (c) 2024 Snowflake Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import contextlib
16
+ import os
17
+ import sys
18
+ from pathlib import Path
19
+
20
+ import snowflake.app.context as ctx
21
+ from snowflake.app.sql import SQLGenerator
22
+
23
+ ctx._project_path = os.environ["_SNOWFLAKE_CLI_PROJECT_PATH"]
24
+ ctx._current_app_name = os.environ["_SNOWFLAKE_CLI_APP_NAME"]
25
+ __snowflake_internal_py_files = os.environ["_SNOWFLAKE_CLI_SETUP_FILES"].split(
26
+ os.pathsep
27
+ )
28
+ __snowflake_internal_sql_dest_dir = os.environ["_SNOWFLAKE_CLI_SQL_DEST_DIR"]
29
+
30
+ try:
31
+ import importlib
32
+
33
+ with contextlib.redirect_stdout(None):
34
+ with contextlib.redirect_stderr(None):
35
+ for __snowflake_internal_py_file in __snowflake_internal_py_files:
36
+ __snowflake_internal_spec = importlib.util.spec_from_file_location(
37
+ "<string>", __snowflake_internal_py_file
38
+ )
39
+ __snowflake_internal_module = importlib.util.module_from_spec(
40
+ __snowflake_internal_spec
41
+ )
42
+ __snowflake_internal_spec.loader.exec_module(
43
+ __snowflake_internal_module
44
+ )
45
+ except Exception as exc: # Catch any error
46
+ print("An exception occurred while executing file: ", exc, file=sys.stderr)
47
+ sys.exit(1)
48
+
49
+
50
+ import json
51
+
52
+ output_dir = Path(__snowflake_internal_sql_dest_dir)
53
+ output_dir.mkdir(exist_ok=True, parents=True)
54
+ path_mappings = SQLGenerator(dest_dir=output_dir).generate()
55
+
56
+ print(json.dumps(path_mappings, default=str))
@@ -20,19 +20,21 @@ from pathlib import Path
20
20
  from textwrap import dedent
21
21
  from typing import Any, Dict, List, Optional, Set
22
22
 
23
- from click import ClickException
24
23
  from pydantic import ValidationError
25
24
  from snowflake.cli.api.console import cli_console as cc
26
25
  from snowflake.cli.api.project.schemas.native_app.path_mapping import (
27
26
  PathMapping,
28
27
  ProcessorMapping,
29
28
  )
30
- from snowflake.cli.api.utils.rendering import jinja_render_from_file
29
+ from snowflake.cli.api.rendering.jinja import jinja_render_from_file
31
30
  from snowflake.cli.plugins.nativeapp.artifacts import (
32
31
  BundleMap,
33
32
  find_setup_script_file,
34
33
  )
35
- from snowflake.cli.plugins.nativeapp.codegen.artifact_processor import ArtifactProcessor
34
+ from snowflake.cli.plugins.nativeapp.codegen.artifact_processor import (
35
+ ArtifactProcessor,
36
+ is_python_file_artifact,
37
+ )
36
38
  from snowflake.cli.plugins.nativeapp.codegen.sandbox import (
37
39
  ExecutionEnvironmentType,
38
40
  SandboxExecutionError,
@@ -167,16 +169,6 @@ class SnowparkAnnotationProcessor(ArtifactProcessor):
167
169
  ):
168
170
  super().__init__(na_project=na_project)
169
171
 
170
- assert self._na_project.bundle_root.is_absolute()
171
- assert self._na_project.deploy_root.is_absolute()
172
- assert self._na_project.generated_root.is_absolute()
173
- assert self._na_project.project_root.is_absolute()
174
-
175
- if self._na_project.generated_root.exists():
176
- raise ClickException(
177
- f"Path {self._na_project.generated_root} already exists. Please choose a different name for your generated directory in the project definition file."
178
- )
179
-
180
172
  def process(
181
173
  self,
182
174
  artifact_to_process: PathMapping,
@@ -200,7 +192,9 @@ class SnowparkAnnotationProcessor(ArtifactProcessor):
200
192
 
201
193
  collected_output = []
202
194
  collected_sql_files: List[Path] = []
203
- for py_file, extension_fns in collected_extension_functions_by_path.items():
195
+ for py_file, extension_fns in sorted(
196
+ collected_extension_functions_by_path.items()
197
+ ):
204
198
  sql_file = self.generate_new_sql_file_name(
205
199
  py_file=py_file,
206
200
  )
@@ -236,9 +230,13 @@ class SnowparkAnnotationProcessor(ArtifactProcessor):
236
230
  edit_setup_script_with_exec_imm_sql(
237
231
  collected_sql_files=collected_sql_files,
238
232
  deploy_root=bundle_map.deploy_root(),
239
- generated_root=self._na_project.generated_root,
233
+ generated_root=self._generated_root,
240
234
  )
241
235
 
236
+ @property
237
+ def _generated_root(self):
238
+ return self._na_project.generated_root / "snowpark"
239
+
242
240
  def _normalize_imports(
243
241
  self,
244
242
  extension_fn: NativeAppExtensionFunction,
@@ -326,8 +324,12 @@ class SnowparkAnnotationProcessor(ArtifactProcessor):
326
324
  Path, List[NativeAppExtensionFunction]
327
325
  ] = {}
328
326
 
329
- for src_file, dest_file in bundle_map.all_mappings(
330
- absolute=True, expand_directories=True, predicate=_is_python_file_artifact
327
+ for src_file, dest_file in sorted(
328
+ bundle_map.all_mappings(
329
+ absolute=True,
330
+ expand_directories=True,
331
+ predicate=is_python_file_artifact,
332
+ )
331
333
  ):
332
334
  cc.step(
333
335
  "Processing Snowpark annotations from {}".format(
@@ -368,9 +370,7 @@ class SnowparkAnnotationProcessor(ArtifactProcessor):
368
370
  Generates a SQL filename for the generated root from the python file, and creates its parent directories.
369
371
  """
370
372
  relative_py_file = py_file.relative_to(self._na_project.deploy_root)
371
- sql_file = Path(
372
- self._na_project.generated_root, relative_py_file.with_suffix(".sql")
373
- )
373
+ sql_file = Path(self._generated_root, relative_py_file.with_suffix(".sql"))
374
374
  if sql_file.exists():
375
375
  cc.warning(
376
376
  f"""\
@@ -488,7 +488,7 @@ def edit_setup_script_with_exec_imm_sql(
488
488
  Adds an 'execute immediate' to setup script for every SQL file in the map
489
489
  """
490
490
  # Create a __generated.sql in the __generated folder
491
- generated_file_path = Path(generated_root, f"{generated_root.stem}.sql")
491
+ generated_file_path = Path(generated_root, f"__generated.sql")
492
492
  generated_file_path.parent.mkdir(exist_ok=True, parents=True)
493
493
 
494
494
  if generated_file_path.exists():
@@ -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