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.
- snowflake/cli/__about__.py +1 -1
- snowflake/cli/_app/commands_registration/builtin_plugins.py +2 -0
- snowflake/cli/_app/secret.py +9 -0
- snowflake/cli/_app/snow_connector.py +39 -27
- snowflake/cli/_app/telemetry.py +28 -0
- snowflake/cli/_plugins/connection/commands.py +9 -4
- snowflake/cli/_plugins/git/manager.py +53 -7
- snowflake/cli/_plugins/helpers/commands.py +61 -0
- snowflake/cli/{api/project/schemas/snowpark/__init__.py → _plugins/helpers/plugin_spec.py} +17 -0
- snowflake/cli/_plugins/nativeapp/artifacts.py +10 -9
- snowflake/cli/_plugins/nativeapp/bundle_context.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/artifact_processor.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/compiler.py +6 -1
- snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/extension_function_utils.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/models.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +5 -1
- snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +4 -1
- snowflake/cli/_plugins/nativeapp/commands.py +87 -96
- snowflake/cli/_plugins/nativeapp/entities/__init__.py +0 -0
- snowflake/cli/{api/entities/application_entity.py → _plugins/nativeapp/entities/application.py} +264 -83
- snowflake/cli/_plugins/nativeapp/entities/application_package.py +1392 -0
- snowflake/cli/_plugins/nativeapp/exceptions.py +0 -9
- snowflake/cli/_plugins/nativeapp/manager.py +69 -185
- snowflake/cli/_plugins/nativeapp/policy.py +3 -0
- snowflake/cli/_plugins/nativeapp/project_model.py +2 -2
- snowflake/cli/_plugins/nativeapp/run_processor.py +17 -20
- snowflake/cli/_plugins/nativeapp/same_account_install_method.py +0 -4
- snowflake/cli/_plugins/nativeapp/teardown_processor.py +4 -6
- snowflake/cli/_plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +122 -88
- snowflake/cli/_plugins/nativeapp/version/commands.py +7 -39
- snowflake/cli/_plugins/nativeapp/version/version_processor.py +46 -312
- snowflake/cli/_plugins/object/manager.py +36 -15
- snowflake/cli/_plugins/snowpark/commands.py +4 -4
- snowflake/cli/_plugins/snowpark/common.py +4 -4
- snowflake/cli/{api/entities → _plugins/snowpark}/snowpark_entity.py +2 -2
- snowflake/cli/{api/project/schemas/entities/snowpark_entity.py → _plugins/snowpark/snowpark_entity_model.py} +3 -6
- snowflake/cli/_plugins/snowpark/snowpark_project_paths.py +1 -1
- snowflake/cli/_plugins/stage/manager.py +9 -4
- snowflake/cli/_plugins/streamlit/commands.py +15 -3
- snowflake/cli/_plugins/streamlit/manager.py +12 -4
- snowflake/cli/{api/entities → _plugins/streamlit}/streamlit_entity.py +2 -2
- snowflake/cli/{api/project/schemas/entities → _plugins/streamlit}/streamlit_entity_model.py +5 -12
- snowflake/cli/_plugins/workspace/commands.py +116 -36
- snowflake/cli/_plugins/workspace/plugin_spec.py +1 -1
- snowflake/cli/api/cli_global_context.py +7 -0
- snowflake/cli/api/commands/decorators.py +14 -0
- snowflake/cli/api/commands/flags.py +18 -0
- snowflake/cli/api/commands/snow_typer.py +1 -1
- snowflake/cli/api/config.py +25 -6
- snowflake/cli/api/connections.py +3 -1
- snowflake/cli/api/entities/common.py +4 -0
- snowflake/cli/api/entities/utils.py +3 -14
- snowflake/cli/api/errno.py +1 -0
- snowflake/cli/api/identifiers.py +4 -3
- snowflake/cli/api/metrics.py +92 -0
- snowflake/cli/api/project/definition_conversion.py +61 -18
- snowflake/cli/api/project/schemas/entities/common.py +17 -4
- snowflake/cli/api/project/schemas/entities/entities.py +11 -10
- snowflake/cli/api/project/schemas/project_definition.py +5 -7
- snowflake/cli/api/project/schemas/v1/__init__.py +0 -0
- snowflake/cli/api/project/schemas/{identifier_model.py → v1/identifier_model.py} +0 -7
- snowflake/cli/api/project/schemas/v1/native_app/__init__.py +0 -0
- snowflake/cli/api/project/schemas/{native_app → v1/native_app}/native_app.py +4 -4
- snowflake/cli/api/project/schemas/v1/snowpark/__init__.py +0 -0
- snowflake/cli/api/project/schemas/{snowpark → v1/snowpark}/callable.py +2 -2
- snowflake/cli/api/project/schemas/{snowpark → v1/snowpark}/snowpark.py +2 -2
- snowflake/cli/api/project/schemas/v1/streamlit/__init__.py +0 -0
- snowflake/cli/api/project/schemas/{streamlit → v1/streamlit}/streamlit.py +2 -1
- snowflake/cli/api/rendering/sql_templates.py +6 -0
- snowflake/cli/api/rest_api.py +11 -5
- snowflake/cli/api/sql_execution.py +6 -15
- snowflake/cli/api/utils/definition_rendering.py +24 -4
- {snowflake_cli_labs-3.0.0rc2.dist-info → snowflake_cli_labs-3.0.0rc4.dist-info}/METADATA +9 -7
- {snowflake_cli_labs-3.0.0rc2.dist-info → snowflake_cli_labs-3.0.0rc4.dist-info}/RECORD +83 -79
- snowflake/cli/_plugins/nativeapp/init.py +0 -345
- snowflake/cli/api/entities/application_package_entity.py +0 -658
- snowflake/cli/api/project/schemas/entities/application_entity_model.py +0 -56
- snowflake/cli/api/project/schemas/entities/application_package_entity_model.py +0 -94
- snowflake/cli/api/project/schemas/streamlit/__init__.py +0 -13
- /snowflake/cli/{api/project/schemas/native_app → _plugins/helpers}/__init__.py +0 -0
- /snowflake/cli/api/project/schemas/{native_app → v1/native_app}/application.py +0 -0
- /snowflake/cli/api/project/schemas/{native_app → v1/native_app}/package.py +0 -0
- /snowflake/cli/api/project/schemas/{native_app → v1/native_app}/path_mapping.py +0 -0
- /snowflake/cli/api/project/schemas/{snowpark → v1/snowpark}/argument.py +0 -0
- {snowflake_cli_labs-3.0.0rc2.dist-info → snowflake_cli_labs-3.0.0rc4.dist-info}/WHEEL +0 -0
- {snowflake_cli_labs-3.0.0rc2.dist-info → snowflake_cli_labs-3.0.0rc4.dist-info}/entry_points.txt +0 -0
- {snowflake_cli_labs-3.0.0rc2.dist-info → snowflake_cli_labs-3.0.0rc4.dist-info}/licenses/LICENSE +0 -0
snowflake/cli/__about__.py
CHANGED
|
@@ -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,
|
|
@@ -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 =
|
|
221
|
-
|
|
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) ->
|
|
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
|
|
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(
|
|
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 =
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
303
|
+
return SecretType(
|
|
304
|
+
private_key.value.private_bytes(
|
|
305
|
+
encoding=Encoding.DER,
|
|
306
|
+
format=PrivateFormat.PKCS8,
|
|
307
|
+
encryption_algorithm=NoEncryption(),
|
|
308
|
+
)
|
|
297
309
|
)
|
snowflake/cli/_app/telemetry.py
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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
|
-
|
|
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 {
|
|
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 =
|
|
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[
|
|
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[
|
|
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
|
-
|
|
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[
|
|
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
|
-
|
|
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[
|
|
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
|
-
|
|
748
|
+
patch_number: Optional[int] = None
|
|
749
749
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
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
|
-
|
|
755
|
+
patch_number = int(version_info[patch_field])
|
|
755
756
|
|
|
756
|
-
return version_name,
|
|
757
|
+
return version_name, patch_number
|
|
@@ -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.
|
|
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.
|
|
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.
|
|
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,
|