snowflake-cli-labs 3.0.0rc2__py3-none-any.whl → 3.0.0rc4__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 (88) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/_app/commands_registration/builtin_plugins.py +2 -0
  3. snowflake/cli/_app/secret.py +9 -0
  4. snowflake/cli/_app/snow_connector.py +39 -27
  5. snowflake/cli/_app/telemetry.py +28 -0
  6. snowflake/cli/_plugins/connection/commands.py +9 -4
  7. snowflake/cli/_plugins/git/manager.py +53 -7
  8. snowflake/cli/_plugins/helpers/commands.py +61 -0
  9. snowflake/cli/{api/project/schemas/snowpark/__init__.py → _plugins/helpers/plugin_spec.py} +17 -0
  10. snowflake/cli/_plugins/nativeapp/artifacts.py +10 -9
  11. snowflake/cli/_plugins/nativeapp/bundle_context.py +1 -1
  12. snowflake/cli/_plugins/nativeapp/codegen/artifact_processor.py +1 -1
  13. snowflake/cli/_plugins/nativeapp/codegen/compiler.py +6 -1
  14. snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +1 -1
  15. snowflake/cli/_plugins/nativeapp/codegen/snowpark/extension_function_utils.py +1 -1
  16. snowflake/cli/_plugins/nativeapp/codegen/snowpark/models.py +1 -1
  17. snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +5 -1
  18. snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +4 -1
  19. snowflake/cli/_plugins/nativeapp/commands.py +87 -96
  20. snowflake/cli/_plugins/nativeapp/entities/__init__.py +0 -0
  21. snowflake/cli/{api/entities/application_entity.py → _plugins/nativeapp/entities/application.py} +264 -83
  22. snowflake/cli/_plugins/nativeapp/entities/application_package.py +1392 -0
  23. snowflake/cli/_plugins/nativeapp/exceptions.py +0 -9
  24. snowflake/cli/_plugins/nativeapp/manager.py +69 -185
  25. snowflake/cli/_plugins/nativeapp/policy.py +3 -0
  26. snowflake/cli/_plugins/nativeapp/project_model.py +2 -2
  27. snowflake/cli/_plugins/nativeapp/run_processor.py +17 -20
  28. snowflake/cli/_plugins/nativeapp/same_account_install_method.py +0 -4
  29. snowflake/cli/_plugins/nativeapp/teardown_processor.py +4 -6
  30. snowflake/cli/_plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +122 -88
  31. snowflake/cli/_plugins/nativeapp/version/commands.py +7 -39
  32. snowflake/cli/_plugins/nativeapp/version/version_processor.py +46 -312
  33. snowflake/cli/_plugins/object/manager.py +36 -15
  34. snowflake/cli/_plugins/snowpark/commands.py +4 -4
  35. snowflake/cli/_plugins/snowpark/common.py +4 -4
  36. snowflake/cli/{api/entities → _plugins/snowpark}/snowpark_entity.py +2 -2
  37. snowflake/cli/{api/project/schemas/entities/snowpark_entity.py → _plugins/snowpark/snowpark_entity_model.py} +3 -6
  38. snowflake/cli/_plugins/snowpark/snowpark_project_paths.py +1 -1
  39. snowflake/cli/_plugins/stage/manager.py +9 -4
  40. snowflake/cli/_plugins/streamlit/commands.py +15 -3
  41. snowflake/cli/_plugins/streamlit/manager.py +12 -4
  42. snowflake/cli/{api/entities → _plugins/streamlit}/streamlit_entity.py +2 -2
  43. snowflake/cli/{api/project/schemas/entities → _plugins/streamlit}/streamlit_entity_model.py +5 -12
  44. snowflake/cli/_plugins/workspace/commands.py +116 -36
  45. snowflake/cli/_plugins/workspace/plugin_spec.py +1 -1
  46. snowflake/cli/api/cli_global_context.py +7 -0
  47. snowflake/cli/api/commands/decorators.py +14 -0
  48. snowflake/cli/api/commands/flags.py +18 -0
  49. snowflake/cli/api/commands/snow_typer.py +1 -1
  50. snowflake/cli/api/config.py +25 -6
  51. snowflake/cli/api/connections.py +3 -1
  52. snowflake/cli/api/entities/common.py +4 -0
  53. snowflake/cli/api/entities/utils.py +3 -14
  54. snowflake/cli/api/errno.py +1 -0
  55. snowflake/cli/api/identifiers.py +4 -3
  56. snowflake/cli/api/metrics.py +92 -0
  57. snowflake/cli/api/project/definition_conversion.py +61 -18
  58. snowflake/cli/api/project/schemas/entities/common.py +17 -4
  59. snowflake/cli/api/project/schemas/entities/entities.py +11 -10
  60. snowflake/cli/api/project/schemas/project_definition.py +5 -7
  61. snowflake/cli/api/project/schemas/v1/__init__.py +0 -0
  62. snowflake/cli/api/project/schemas/{identifier_model.py → v1/identifier_model.py} +0 -7
  63. snowflake/cli/api/project/schemas/v1/native_app/__init__.py +0 -0
  64. snowflake/cli/api/project/schemas/{native_app → v1/native_app}/native_app.py +4 -4
  65. snowflake/cli/api/project/schemas/v1/snowpark/__init__.py +0 -0
  66. snowflake/cli/api/project/schemas/{snowpark → v1/snowpark}/callable.py +2 -2
  67. snowflake/cli/api/project/schemas/{snowpark → v1/snowpark}/snowpark.py +2 -2
  68. snowflake/cli/api/project/schemas/v1/streamlit/__init__.py +0 -0
  69. snowflake/cli/api/project/schemas/{streamlit → v1/streamlit}/streamlit.py +2 -1
  70. snowflake/cli/api/rendering/sql_templates.py +6 -0
  71. snowflake/cli/api/rest_api.py +11 -5
  72. snowflake/cli/api/sql_execution.py +6 -15
  73. snowflake/cli/api/utils/definition_rendering.py +24 -4
  74. {snowflake_cli_labs-3.0.0rc2.dist-info → snowflake_cli_labs-3.0.0rc4.dist-info}/METADATA +9 -7
  75. {snowflake_cli_labs-3.0.0rc2.dist-info → snowflake_cli_labs-3.0.0rc4.dist-info}/RECORD +83 -79
  76. snowflake/cli/_plugins/nativeapp/init.py +0 -345
  77. snowflake/cli/api/entities/application_package_entity.py +0 -658
  78. snowflake/cli/api/project/schemas/entities/application_entity_model.py +0 -56
  79. snowflake/cli/api/project/schemas/entities/application_package_entity_model.py +0 -94
  80. snowflake/cli/api/project/schemas/streamlit/__init__.py +0 -13
  81. /snowflake/cli/{api/project/schemas/native_app → _plugins/helpers}/__init__.py +0 -0
  82. /snowflake/cli/api/project/schemas/{native_app → v1/native_app}/application.py +0 -0
  83. /snowflake/cli/api/project/schemas/{native_app → v1/native_app}/package.py +0 -0
  84. /snowflake/cli/api/project/schemas/{native_app → v1/native_app}/path_mapping.py +0 -0
  85. /snowflake/cli/api/project/schemas/{snowpark → v1/snowpark}/argument.py +0 -0
  86. {snowflake_cli_labs-3.0.0rc2.dist-info → snowflake_cli_labs-3.0.0rc4.dist-info}/WHEEL +0 -0
  87. {snowflake_cli_labs-3.0.0rc2.dist-info → snowflake_cli_labs-3.0.0rc4.dist-info}/entry_points.txt +0 -0
  88. {snowflake_cli_labs-3.0.0rc2.dist-info → snowflake_cli_labs-3.0.0rc4.dist-info}/licenses/LICENSE +0 -0
@@ -14,4 +14,4 @@
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
- VERSION = "3.0.0rc2"
17
+ VERSION = "3.0.0rc4"
@@ -15,6 +15,7 @@
15
15
  from snowflake.cli._plugins.connection import plugin_spec as connection_plugin_spec
16
16
  from snowflake.cli._plugins.cortex import plugin_spec as cortex_plugin_spec
17
17
  from snowflake.cli._plugins.git import plugin_spec as git_plugin_spec
18
+ from snowflake.cli._plugins.helpers import plugin_spec as migrate_plugin_spec
18
19
  from snowflake.cli._plugins.init import plugin_spec as init_plugin_spec
19
20
  from snowflake.cli._plugins.nativeapp import plugin_spec as nativeapp_plugin_spec
20
21
  from snowflake.cli._plugins.notebook import plugin_spec as notebook_plugin_spec
@@ -31,6 +32,7 @@ from snowflake.cli._plugins.workspace import plugin_spec as workspace_plugin_spe
31
32
  def get_builtin_plugin_name_to_plugin_spec():
32
33
  plugin_specs = {
33
34
  "connection": connection_plugin_spec,
35
+ "helpers": migrate_plugin_spec,
34
36
  "spcs": spcs_plugin_spec,
35
37
  "nativeapp": nativeapp_plugin_spec,
36
38
  "object": object_plugin_spec,
@@ -0,0 +1,9 @@
1
+ class SecretType:
2
+ def __init__(self, value):
3
+ self.value = value
4
+
5
+ def __repr__(self):
6
+ return "SecretType(***)"
7
+
8
+ def __str___(self):
9
+ return "***"
@@ -24,6 +24,7 @@ from click.exceptions import ClickException
24
24
  from snowflake.cli._app.constants import (
25
25
  PARAM_APPLICATION_NAME,
26
26
  )
27
+ from snowflake.cli._app.secret import SecretType
27
28
  from snowflake.cli._app.telemetry import command_info
28
29
  from snowflake.cli.api.config import (
29
30
  get_connection_dict,
@@ -205,7 +206,7 @@ def _load_private_key(connection_parameters: Dict, private_key_var_name: str) ->
205
206
  connection_parameters[private_key_var_name]
206
207
  )
207
208
  private_key = _load_pem_to_der(private_key_pem)
208
- connection_parameters["private_key"] = private_key
209
+ connection_parameters["private_key"] = private_key.value
209
210
  del connection_parameters[private_key_var_name]
210
211
  else:
211
212
  raise ClickException(
@@ -217,10 +218,11 @@ def _load_private_key_from_parameters(
217
218
  connection_parameters: Dict, private_key_var_name: str
218
219
  ) -> None:
219
220
  if connection_parameters.get("authenticator") == "SNOWFLAKE_JWT":
220
- private_key_pem = connection_parameters[private_key_var_name]
221
- private_key_pem = private_key_pem.encode("utf-8")
221
+ private_key_pem = _load_pem_from_parameters(
222
+ connection_parameters[private_key_var_name]
223
+ )
222
224
  private_key = _load_pem_to_der(private_key_pem)
223
- connection_parameters["private_key"] = private_key
225
+ connection_parameters["private_key"] = private_key.value
224
226
  del connection_parameters[private_key_var_name]
225
227
  else:
226
228
  raise ClickException(
@@ -236,43 +238,49 @@ def _update_connection_application_name(connection_parameters: Dict):
236
238
  connection_parameters.update(connection_application_params)
237
239
 
238
240
 
239
- def _load_pem_from_file(private_key_file: str) -> bytes:
241
+ def _load_pem_from_file(private_key_file: str) -> SecretType:
240
242
  with SecurePath(private_key_file).open(
241
243
  "rb", read_file_limit_mb=DEFAULT_SIZE_LIMIT_MB
242
244
  ) as f:
243
- private_key_pem = f.read()
245
+ private_key_pem = SecretType(f.read())
244
246
  return private_key_pem
245
247
 
246
248
 
247
- def _load_pem_to_der(private_key_pem: bytes) -> bytes:
249
+ def _load_pem_from_parameters(private_key_raw: str) -> SecretType:
250
+ return SecretType(private_key_raw.encode("utf-8"))
251
+
252
+
253
+ def _load_pem_to_der(private_key_pem: SecretType) -> SecretType:
248
254
  """
249
255
  Given a private key file path (in PEM format), decode key data into DER
250
256
  format
251
257
  """
252
- private_key_passphrase = os.getenv("PRIVATE_KEY_PASSPHRASE", None)
258
+ private_key_passphrase = SecretType(os.getenv("PRIVATE_KEY_PASSPHRASE", None))
253
259
  if (
254
- private_key_pem.startswith(ENCRYPTED_PKCS8_PK_HEADER)
255
- and private_key_passphrase is None
260
+ private_key_pem.value.startswith(ENCRYPTED_PKCS8_PK_HEADER)
261
+ and private_key_passphrase.value is None
256
262
  ):
257
263
  raise ClickException(
258
264
  "Encrypted private key, you must provide the"
259
265
  "passphrase in the environment variable PRIVATE_KEY_PASSPHRASE"
260
266
  )
261
267
 
262
- if not private_key_pem.startswith(
268
+ if not private_key_pem.value.startswith(
263
269
  ENCRYPTED_PKCS8_PK_HEADER
264
- ) and not private_key_pem.startswith(UNENCRYPTED_PKCS8_PK_HEADER):
270
+ ) and not private_key_pem.value.startswith(UNENCRYPTED_PKCS8_PK_HEADER):
265
271
  raise ClickException(
266
272
  "Private key provided is not in PKCS#8 format. Please use correct format."
267
273
  )
268
274
 
269
- if private_key_pem.startswith(UNENCRYPTED_PKCS8_PK_HEADER):
270
- private_key_passphrase = None
275
+ if private_key_pem.value.startswith(UNENCRYPTED_PKCS8_PK_HEADER):
276
+ private_key_passphrase = SecretType(None)
271
277
 
272
278
  return prepare_private_key(private_key_pem, private_key_passphrase)
273
279
 
274
280
 
275
- def prepare_private_key(private_key_pem, private_key_passphrase=None):
281
+ def prepare_private_key(
282
+ private_key_pem: SecretType, private_key_passphrase: SecretType = SecretType(None)
283
+ ):
276
284
  from cryptography.hazmat.backends import default_backend
277
285
  from cryptography.hazmat.primitives.serialization import (
278
286
  Encoding,
@@ -281,17 +289,21 @@ def prepare_private_key(private_key_pem, private_key_passphrase=None):
281
289
  load_pem_private_key,
282
290
  )
283
291
 
284
- private_key = load_pem_private_key(
285
- private_key_pem,
286
- (
287
- str.encode(private_key_passphrase)
288
- if private_key_passphrase is not None
289
- else private_key_passphrase
290
- ),
291
- default_backend(),
292
+ private_key = SecretType(
293
+ load_pem_private_key(
294
+ private_key_pem.value,
295
+ (
296
+ str.encode(private_key_passphrase.value)
297
+ if private_key_passphrase.value is not None
298
+ else private_key_passphrase.value
299
+ ),
300
+ default_backend(),
301
+ )
292
302
  )
293
- return private_key.private_bytes(
294
- encoding=Encoding.DER,
295
- format=PrivateFormat.PKCS8,
296
- encryption_algorithm=NoEncryption(),
303
+ return SecretType(
304
+ private_key.value.private_bytes(
305
+ encoding=Encoding.DER,
306
+ format=PrivateFormat.PKCS8,
307
+ encryption_algorithm=NoEncryption(),
308
+ )
297
309
  )
@@ -37,6 +37,12 @@ from snowflake.connector.telemetry import (
37
37
  from snowflake.connector.time_util import get_time_millis
38
38
 
39
39
 
40
+ @unique
41
+ class CLIInstallationSource(Enum):
42
+ BINARY = "binary"
43
+ PYPI = "pypi"
44
+
45
+
40
46
  @unique
41
47
  class CLITelemetryField(Enum):
42
48
  # Basic information
@@ -44,6 +50,7 @@ class CLITelemetryField(Enum):
44
50
  VERSION_CLI = "version_cli"
45
51
  VERSION_PYTHON = "version_python"
46
52
  VERSION_OS = "version_os"
53
+ INSTALLATION_SOURCE = "installation_source"
47
54
  # Command execution context
48
55
  COMMAND = "command"
49
56
  COMMAND_GROUP = "command_group"
@@ -54,6 +61,8 @@ class CLITelemetryField(Enum):
54
61
  COMMAND_EXECUTION_TIME = "command_execution_time"
55
62
  # Configuration
56
63
  CONFIG_FEATURE_FLAGS = "config_feature_flags"
64
+ # Metrics
65
+ COUNTERS = "counters"
57
66
  # Information
58
67
  EVENT = "event"
59
68
  ERROR_MSG = "error_msg"
@@ -72,6 +81,16 @@ class TelemetryEvent(Enum):
72
81
  TelemetryDict = Dict[Union[CLITelemetryField, TelemetryField], Any]
73
82
 
74
83
 
84
+ def _get_command_metrics() -> TelemetryDict:
85
+ cli_context = get_cli_context()
86
+
87
+ return {
88
+ CLITelemetryField.COUNTERS: {
89
+ **cli_context.metrics.counters,
90
+ }
91
+ }
92
+
93
+
75
94
  def _find_command_info() -> TelemetryDict:
76
95
  ctx = click.get_current_context()
77
96
  command_path = ctx.command_path.split(" ")[1:]
@@ -97,6 +116,12 @@ def _get_definition_version() -> str | None:
97
116
  return None
98
117
 
99
118
 
119
+ def _get_installation_source() -> CLIInstallationSource:
120
+ if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"):
121
+ return CLIInstallationSource.BINARY
122
+ return CLIInstallationSource.PYPI
123
+
124
+
100
125
  def command_info() -> str:
101
126
  info = _find_command_info()
102
127
  command = ".".join(info[CLITelemetryField.COMMAND])
@@ -119,6 +144,7 @@ class CLITelemetryClient:
119
144
  ) -> Dict[str, Any]:
120
145
  data = {
121
146
  CLITelemetryField.SOURCE: PARAM_APPLICATION_NAME,
147
+ CLITelemetryField.INSTALLATION_SOURCE: _get_installation_source().value,
122
148
  CLITelemetryField.VERSION_CLI: VERSION,
123
149
  CLITelemetryField.VERSION_OS: platform.platform(),
124
150
  CLITelemetryField.VERSION_PYTHON: python_version(),
@@ -168,6 +194,7 @@ def log_command_result(execution: ExecutionMetadata):
168
194
  CLITelemetryField.COMMAND_EXECUTION_ID: execution.execution_id,
169
195
  CLITelemetryField.COMMAND_RESULT_STATUS: execution.status.value,
170
196
  CLITelemetryField.COMMAND_EXECUTION_TIME: execution.get_duration(),
197
+ **_get_command_metrics(),
171
198
  }
172
199
  )
173
200
 
@@ -183,6 +210,7 @@ def log_command_execution_error(exception: Exception, execution: ExecutionMetada
183
210
  CLITelemetryField.ERROR_TYPE: exception_type,
184
211
  CLITelemetryField.IS_CLI_EXCEPTION: is_cli_exception,
185
212
  CLITelemetryField.COMMAND_EXECUTION_TIME: execution.get_duration(),
213
+ **_get_command_metrics(),
186
214
  }
187
215
  )
188
216
 
@@ -33,7 +33,7 @@ from snowflake.cli.api.commands.flags import (
33
33
  from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
34
34
  from snowflake.cli.api.config import (
35
35
  ConnectionConfig,
36
- add_connection,
36
+ add_connection_to_proper_file,
37
37
  connection_exists,
38
38
  get_all_connections,
39
39
  get_connection_dict,
@@ -49,7 +49,7 @@ from snowflake.cli.api.output.types import (
49
49
  ObjectResult,
50
50
  )
51
51
  from snowflake.connector import ProgrammingError
52
- from snowflake.connector.config_manager import CONFIG_MANAGER
52
+ from snowflake.connector.constants import CONNECTIONS_FILE
53
53
 
54
54
  app = SnowTyperFactory(
55
55
  name="connection",
@@ -91,6 +91,11 @@ def list_connections(**options) -> CommandResult:
91
91
  }
92
92
  for connection_name, connection_config in connections.items()
93
93
  )
94
+
95
+ if CONNECTIONS_FILE.exists():
96
+ cli_console.warning(
97
+ f"Reading connections from {CONNECTIONS_FILE}. Entries from config.toml are ignored."
98
+ )
94
99
  return CollectionResult(result)
95
100
 
96
101
 
@@ -255,7 +260,7 @@ def add(
255
260
  if connection_exists(connection_name):
256
261
  raise ClickException(f"Connection {connection_name} already exists")
257
262
 
258
- add_connection(
263
+ connections_file = add_connection_to_proper_file(
259
264
  connection_name,
260
265
  ConnectionConfig(
261
266
  account=account,
@@ -279,7 +284,7 @@ def add(
279
284
  )
280
285
 
281
286
  return MessageResult(
282
- f"Wrote new connection {connection_name} to {CONFIG_MANAGER.file_path}"
287
+ f"Wrote new connection {connection_name} to {connections_file}"
283
288
  )
284
289
 
285
290
 
@@ -14,10 +14,11 @@
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
- from pathlib import Path
17
+ from pathlib import Path, PurePosixPath
18
18
  from textwrap import dedent
19
19
  from typing import List
20
20
 
21
+ from click import UsageError
21
22
  from snowflake.cli._plugins.stage.manager import (
22
23
  USER_STAGE_PREFIX,
23
24
  StageManager,
@@ -27,16 +28,22 @@ from snowflake.cli._plugins.stage.manager import (
27
28
  from snowflake.cli.api.identifiers import FQN
28
29
  from snowflake.connector.cursor import SnowflakeCursor
29
30
 
31
+ # Replace magic numbers with constants
32
+ OMIT_FIRST = slice(1, None)
33
+ OMIT_STAGE = slice(3, None)
34
+ OMIT_STAGE_IN_NEW_LIST_FILES = slice(2, None)
35
+ ONLY_STAGE = slice(3)
36
+
30
37
 
31
38
  class GitStagePathParts(StagePathParts):
32
39
  def __init__(self, stage_path: str):
33
40
  self.stage = GitManager.get_stage_from_path(stage_path)
34
- stage_path_parts = Path(stage_path).parts
41
+ stage_path_parts = GitManager.split_git_path(stage_path)
35
42
  git_repo_name = stage_path_parts[0].split(".")[-1]
36
43
  if git_repo_name.startswith("@"):
37
- git_repo_name = git_repo_name[1:]
44
+ git_repo_name = git_repo_name[OMIT_FIRST]
38
45
  self.stage_name = "/".join([git_repo_name, *stage_path_parts[1:3], ""])
39
- self.directory = "/".join(stage_path_parts[3:])
46
+ self.directory = "/".join(stage_path_parts[OMIT_STAGE])
40
47
  self.is_directory = True if stage_path.endswith("/") else False
41
48
 
42
49
  @property
@@ -45,7 +52,12 @@ class GitStagePathParts(StagePathParts):
45
52
 
46
53
  @classmethod
47
54
  def get_directory(cls, stage_path: str) -> str:
48
- return "/".join(Path(stage_path).parts[3:])
55
+ git_path_parts = GitManager.split_git_path(stage_path)
56
+ # New file list does not have a stage name at the beginning
57
+ if stage_path.startswith("/"):
58
+ return "/".join(git_path_parts[OMIT_STAGE_IN_NEW_LIST_FILES])
59
+ else:
60
+ return "/".join(git_path_parts[OMIT_STAGE])
49
61
 
50
62
  @property
51
63
  def full_path(self) -> str:
@@ -53,7 +65,7 @@ class GitStagePathParts(StagePathParts):
53
65
 
54
66
  def replace_stage_prefix(self, file_path: str) -> str:
55
67
  stage = Path(self.stage).parts[0]
56
- file_path_without_prefix = Path(file_path).parts[1:]
68
+ file_path_without_prefix = Path(file_path).parts[OMIT_FIRST]
57
69
  return f"{stage}/{'/'.join(file_path_without_prefix)}"
58
70
 
59
71
  def add_stage_prefix(self, file_path: str) -> str:
@@ -95,7 +107,8 @@ class GitManager(StageManager):
95
107
  Returns stage name from potential path on stage. For example
96
108
  repo/branches/main/foo/bar -> repo/branches/main/
97
109
  """
98
- return f"{'/'.join(Path(path).parts[0:3])}/"
110
+ path_parts = GitManager.split_git_path(path)
111
+ return f"{'/'.join(path_parts[ONLY_STAGE])}/"
99
112
 
100
113
  @staticmethod
101
114
  def _stage_path_part_factory(stage_path: str) -> StagePathParts:
@@ -103,3 +116,36 @@ class GitManager(StageManager):
103
116
  if stage_path.startswith(USER_STAGE_PREFIX):
104
117
  return UserStagePathParts(stage_path)
105
118
  return GitStagePathParts(stage_path)
119
+
120
+ @staticmethod
121
+ def split_git_path(path: str):
122
+ # Check if path contains quotes and split it accordingly
123
+ if '/"' in path and '"/' in path:
124
+ if path.count('"') > 2:
125
+ raise UsageError(
126
+ f'Invalid string {path}, too much " in path, expected 2.'
127
+ )
128
+
129
+ path_parts = path.split('"')
130
+ before_quoted_part = GitManager._split_path_without_empty_parts(
131
+ path_parts[0]
132
+ )
133
+
134
+ if path_parts[2] == "/":
135
+ after_quoted_part = []
136
+ else:
137
+ after_quoted_part = GitManager._split_path_without_empty_parts(
138
+ path_parts[2]
139
+ )
140
+
141
+ return [
142
+ *before_quoted_part,
143
+ f'"{path_parts[1]}"',
144
+ *after_quoted_part,
145
+ ]
146
+ else:
147
+ return GitManager._split_path_without_empty_parts(path)
148
+
149
+ @staticmethod
150
+ def _split_path_without_empty_parts(path: str):
151
+ return [e for e in PurePosixPath(path).parts if e != "/"]
@@ -0,0 +1,61 @@
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 typer
18
+ import yaml
19
+ from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
20
+ from snowflake.cli.api.output.types import MessageResult
21
+ from snowflake.cli.api.project.definition_conversion import (
22
+ convert_project_definition_to_v2,
23
+ )
24
+ from snowflake.cli.api.project.definition_manager import DefinitionManager
25
+ from snowflake.cli.api.secure_path import SecurePath
26
+
27
+ app = SnowTyperFactory(
28
+ name="helpers",
29
+ help="Helper commands.",
30
+ )
31
+
32
+
33
+ @app.command()
34
+ def v1_to_v2(
35
+ accept_templates: bool = typer.Option(
36
+ False, "-t", "--accept-templates", help="Allows the migration of templates."
37
+ ),
38
+ **options,
39
+ ):
40
+ """Migrates the Snowpark, Streamlit, and Native App project definition files from V1 to V2."""
41
+ manager = DefinitionManager()
42
+ pd = manager.unrendered_project_definition
43
+
44
+ if pd.meets_version_requirement("2"):
45
+ return MessageResult("Project definition is already at version 2.")
46
+
47
+ pd_v2 = convert_project_definition_to_v2(
48
+ manager.project_root, pd, accept_templates, manager.template_context
49
+ )
50
+
51
+ SecurePath("snowflake.yml").rename("snowflake_V1.yml")
52
+ with open("snowflake.yml", "w") as file:
53
+ yaml.dump(
54
+ pd_v2.model_dump(
55
+ exclude_unset=True, exclude_none=True, mode="json", by_alias=True
56
+ ),
57
+ file,
58
+ sort_keys=False,
59
+ width=float("inf"), # Don't break lines
60
+ )
61
+ return MessageResult("Project definition migrated to version 2.")
@@ -11,3 +11,20 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
+
15
+ from snowflake.cli._plugins.helpers import commands
16
+ from snowflake.cli.api.plugins.command import (
17
+ SNOWCLI_ROOT_COMMAND_PATH,
18
+ CommandSpec,
19
+ CommandType,
20
+ plugin_hook_impl,
21
+ )
22
+
23
+
24
+ @plugin_hook_impl
25
+ def command_spec():
26
+ return CommandSpec(
27
+ parent_command_path=SNOWCLI_ROOT_COMMAND_PATH,
28
+ command_type=CommandType.COMMAND_GROUP,
29
+ typer_instance=commands.app.create_instance(),
30
+ )
@@ -22,7 +22,8 @@ from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Tupl
22
22
 
23
23
  from click.exceptions import ClickException
24
24
  from snowflake.cli.api.constants import DEFAULT_SIZE_LIMIT_MB
25
- from snowflake.cli.api.project.schemas.native_app.path_mapping import PathMapping
25
+ from snowflake.cli.api.project.schemas.v1.native_app.path_mapping import PathMapping
26
+ from snowflake.cli.api.project.util import to_identifier
26
27
  from snowflake.cli.api.secure_path import SecurePath
27
28
  from yaml import safe_load
28
29
 
@@ -734,23 +735,23 @@ def find_setup_script_file(deploy_root: Path) -> Path:
734
735
 
735
736
  def find_version_info_in_manifest_file(
736
737
  deploy_root: Path,
737
- ) -> Tuple[Optional[str], Optional[str]]:
738
+ ) -> Tuple[Optional[str], Optional[int]]:
738
739
  """
739
740
  Find version and patch, if available, in the manifest.yml file.
740
741
  """
741
- version_field = "version"
742
742
  name_field = "name"
743
743
  patch_field = "patch"
744
744
 
745
745
  manifest_content = find_and_read_manifest_file(deploy_root=deploy_root)
746
746
 
747
747
  version_name: Optional[str] = None
748
- patch_name: Optional[str] = None
748
+ patch_number: Optional[int] = None
749
749
 
750
- if version_field in manifest_content:
751
- version_info = manifest_content[version_field]
752
- version_name = version_info[name_field]
750
+ version_info = manifest_content.get("version", None)
751
+ if version_info:
752
+ if name_field in version_info:
753
+ version_name = to_identifier(str(version_info[name_field]))
753
754
  if patch_field in version_info:
754
- patch_name = version_info[patch_field]
755
+ patch_number = int(version_info[patch_field])
755
756
 
756
- return version_name, patch_name
757
+ return version_name, patch_number
@@ -18,7 +18,7 @@ from typing import (
18
18
  List,
19
19
  )
20
20
 
21
- from snowflake.cli.api.project.schemas.native_app.path_mapping import PathMapping
21
+ from snowflake.cli.api.project.schemas.v1.native_app.path_mapping import PathMapping
22
22
 
23
23
 
24
24
  @dataclass
@@ -20,7 +20,7 @@ from typing import Optional
20
20
 
21
21
  from click import ClickException
22
22
  from snowflake.cli._plugins.nativeapp.bundle_context import BundleContext
23
- from snowflake.cli.api.project.schemas.native_app.path_mapping import (
23
+ from snowflake.cli.api.project.schemas.v1.native_app.path_mapping import (
24
24
  PathMapping,
25
25
  ProcessorMapping,
26
26
  )
@@ -34,8 +34,10 @@ from snowflake.cli._plugins.nativeapp.codegen.templates.templates_processor impo
34
34
  TemplatesProcessor,
35
35
  )
36
36
  from snowflake.cli._plugins.nativeapp.feature_flags import FeatureFlag
37
+ from snowflake.cli.api.cli_global_context import get_cli_context
37
38
  from snowflake.cli.api.console import cli_console as cc
38
- from snowflake.cli.api.project.schemas.native_app.path_mapping import (
39
+ from snowflake.cli.api.metrics import CLICounterField
40
+ from snowflake.cli.api.project.schemas.v1.native_app.path_mapping import (
39
41
  ProcessorMapping,
40
42
  )
41
43
 
@@ -72,6 +74,9 @@ class NativeAppCompiler:
72
74
  Go through every artifact object in the project definition of a native app, and execute processors in order of specification for each of the artifact object.
73
75
  May have side-effects on the filesystem by either directly editing source files or the deploy root.
74
76
  """
77
+ metrics = get_cli_context().metrics
78
+ metrics.set_counter_default(CLICounterField.TEMPLATES_PROCESSOR, 0)
79
+ metrics.set_counter_default(CLICounterField.SNOWPARK_PROCESSOR, 0)
75
80
 
76
81
  if not self._should_invoke_processors():
77
82
  return
@@ -38,7 +38,7 @@ from snowflake.cli._plugins.nativeapp.codegen.sandbox import (
38
38
  )
39
39
  from snowflake.cli._plugins.stage.diff import to_stage_path
40
40
  from snowflake.cli.api.console import cli_console as cc
41
- from snowflake.cli.api.project.schemas.native_app.path_mapping import (
41
+ from snowflake.cli.api.project.schemas.v1.native_app.path_mapping import (
42
42
  PathMapping,
43
43
  ProcessorMapping,
44
44
  )
@@ -27,7 +27,7 @@ from snowflake.cli._plugins.nativeapp.codegen.snowpark.models import (
27
27
  ExtensionFunctionTypeEnum,
28
28
  NativeAppExtensionFunction,
29
29
  )
30
- from snowflake.cli.api.project.schemas.snowpark.argument import Argument
30
+ from snowflake.cli.api.project.schemas.v1.snowpark.argument import Argument
31
31
  from snowflake.cli.api.project.util import (
32
32
  is_valid_identifier,
33
33
  is_valid_string_literal,
@@ -18,8 +18,8 @@ from enum import Enum
18
18
  from typing import List, Optional
19
19
 
20
20
  from pydantic import Field
21
- from snowflake.cli.api.project.schemas.snowpark.callable import _CallableBase
22
21
  from snowflake.cli.api.project.schemas.updatable_model import IdentifierField
22
+ from snowflake.cli.api.project.schemas.v1.snowpark.callable import _CallableBase
23
23
 
24
24
 
25
25
  class ExtensionFunctionTypeEnum(str, Enum):
@@ -48,8 +48,10 @@ from snowflake.cli._plugins.nativeapp.codegen.snowpark.models import (
48
48
  NativeAppExtensionFunction,
49
49
  )
50
50
  from snowflake.cli._plugins.stage.diff import to_stage_path
51
+ from snowflake.cli.api.cli_global_context import get_cli_context
51
52
  from snowflake.cli.api.console import cli_console as cc
52
- from snowflake.cli.api.project.schemas.native_app.path_mapping import (
53
+ from snowflake.cli.api.metrics import CLICounterField
54
+ from snowflake.cli.api.project.schemas.v1.native_app.path_mapping import (
53
55
  PathMapping,
54
56
  ProcessorMapping,
55
57
  )
@@ -176,6 +178,8 @@ class SnowparkAnnotationProcessor(ArtifactProcessor):
176
178
  setup script with generated SQL that registers these functions.
177
179
  """
178
180
 
181
+ get_cli_context().metrics.set_counter(CLICounterField.SNOWPARK_PROCESSOR, 1)
182
+
179
183
  bundle_map = BundleMap(
180
184
  project_root=self._bundle_ctx.project_root,
181
185
  deploy_root=self._bundle_ctx.deploy_root,
@@ -25,7 +25,8 @@ from snowflake.cli._plugins.nativeapp.codegen.artifact_processor import (
25
25
  from snowflake.cli._plugins.nativeapp.exceptions import InvalidTemplateInFileError
26
26
  from snowflake.cli.api.cli_global_context import get_cli_context
27
27
  from snowflake.cli.api.console import cli_console as cc
28
- from snowflake.cli.api.project.schemas.native_app.path_mapping import (
28
+ from snowflake.cli.api.metrics import CLICounterField
29
+ from snowflake.cli.api.project.schemas.v1.native_app.path_mapping import (
29
30
  PathMapping,
30
31
  ProcessorMapping,
31
32
  )
@@ -98,6 +99,8 @@ class TemplatesProcessor(ArtifactProcessor):
98
99
  Process the artifact by executing the template expansion logic on it.
99
100
  """
100
101
 
102
+ get_cli_context().metrics.set_counter(CLICounterField.TEMPLATES_PROCESSOR, 1)
103
+
101
104
  bundle_map = BundleMap(
102
105
  project_root=self._bundle_ctx.project_root,
103
106
  deploy_root=self._bundle_ctx.deploy_root,