snowflake-cli 3.11.0__py3-none-any.whl → 3.13.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- snowflake/cli/__about__.py +1 -1
- snowflake/cli/_app/cli_app.py +43 -1
- snowflake/cli/_app/commands_registration/builtin_plugins.py +1 -1
- snowflake/cli/_app/commands_registration/command_plugins_loader.py +14 -1
- snowflake/cli/_app/printing.py +153 -19
- snowflake/cli/_app/telemetry.py +25 -10
- snowflake/cli/_plugins/auth/__init__.py +0 -2
- snowflake/cli/_plugins/connection/commands.py +1 -78
- snowflake/cli/_plugins/dbt/commands.py +44 -19
- snowflake/cli/_plugins/dbt/constants.py +1 -1
- snowflake/cli/_plugins/dbt/manager.py +252 -47
- snowflake/cli/_plugins/dcm/commands.py +65 -90
- snowflake/cli/_plugins/dcm/manager.py +137 -50
- snowflake/cli/_plugins/logs/commands.py +7 -0
- snowflake/cli/_plugins/logs/manager.py +21 -1
- snowflake/cli/_plugins/nativeapp/entities/application_package.py +4 -1
- snowflake/cli/_plugins/nativeapp/sf_sql_facade.py +3 -1
- snowflake/cli/_plugins/object/manager.py +1 -0
- snowflake/cli/_plugins/snowpark/common.py +1 -0
- snowflake/cli/_plugins/snowpark/package/anaconda_packages.py +29 -5
- snowflake/cli/_plugins/snowpark/package_utils.py +44 -3
- snowflake/cli/_plugins/spcs/services/commands.py +19 -1
- snowflake/cli/_plugins/spcs/services/manager.py +17 -4
- snowflake/cli/_plugins/spcs/services/service_entity_model.py +5 -0
- snowflake/cli/_plugins/sql/lexer/types.py +1 -0
- snowflake/cli/_plugins/sql/repl.py +100 -26
- snowflake/cli/_plugins/sql/repl_commands.py +607 -0
- snowflake/cli/_plugins/sql/statement_reader.py +44 -20
- snowflake/cli/_plugins/streamlit/streamlit_entity.py +28 -2
- snowflake/cli/_plugins/streamlit/streamlit_entity_model.py +24 -4
- snowflake/cli/api/artifacts/bundle_map.py +32 -2
- snowflake/cli/api/artifacts/regex_resolver.py +54 -0
- snowflake/cli/api/artifacts/upload.py +5 -1
- snowflake/cli/api/artifacts/utils.py +12 -1
- snowflake/cli/api/cli_global_context.py +7 -0
- snowflake/cli/api/commands/decorators.py +7 -0
- snowflake/cli/api/commands/flags.py +24 -1
- snowflake/cli/api/console/abc.py +13 -2
- snowflake/cli/api/console/console.py +20 -0
- snowflake/cli/api/constants.py +9 -0
- snowflake/cli/api/entities/utils.py +10 -6
- snowflake/cli/api/feature_flags.py +3 -2
- snowflake/cli/api/identifiers.py +18 -1
- snowflake/cli/api/project/schemas/entities/entities.py +0 -6
- snowflake/cli/api/rendering/sql_templates.py +2 -0
- {snowflake_cli-3.11.0.dist-info → snowflake_cli-3.13.0.dist-info}/METADATA +7 -7
- {snowflake_cli-3.11.0.dist-info → snowflake_cli-3.13.0.dist-info}/RECORD +51 -54
- snowflake/cli/_plugins/auth/keypair/__init__.py +0 -0
- snowflake/cli/_plugins/auth/keypair/commands.py +0 -153
- snowflake/cli/_plugins/auth/keypair/manager.py +0 -331
- snowflake/cli/_plugins/dcm/dcm_project_entity_model.py +0 -59
- snowflake/cli/_plugins/sql/snowsql_commands.py +0 -331
- /snowflake/cli/_plugins/auth/{keypair/plugin_spec.py → plugin_spec.py} +0 -0
- {snowflake_cli-3.11.0.dist-info → snowflake_cli-3.13.0.dist-info}/WHEEL +0 -0
- {snowflake_cli-3.11.0.dist-info → snowflake_cli-3.13.0.dist-info}/entry_points.txt +0 -0
- {snowflake_cli-3.11.0.dist-info → snowflake_cli-3.13.0.dist-info}/licenses/LICENSE +0 -0
snowflake/cli/__about__.py
CHANGED
snowflake/cli/_app/cli_app.py
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
|
+
import inspect
|
|
17
18
|
import logging
|
|
18
19
|
import os
|
|
19
20
|
import platform
|
|
@@ -47,6 +48,20 @@ from snowflake.connector.config_manager import CONFIG_MANAGER
|
|
|
47
48
|
|
|
48
49
|
log = logging.getLogger(__name__)
|
|
49
50
|
|
|
51
|
+
INTERNAL_CLI_FLAGS = {
|
|
52
|
+
"custom_help",
|
|
53
|
+
"version",
|
|
54
|
+
"docs",
|
|
55
|
+
"structure",
|
|
56
|
+
"info",
|
|
57
|
+
"configuration_file",
|
|
58
|
+
"pycharm_debug_library_path",
|
|
59
|
+
"pycharm_debug_server_host",
|
|
60
|
+
"pycharm_debug_server_port",
|
|
61
|
+
"disable_external_command_plugins",
|
|
62
|
+
"commands_registration",
|
|
63
|
+
}
|
|
64
|
+
|
|
50
65
|
|
|
51
66
|
def _do_not_execute_on_completion(callback):
|
|
52
67
|
def enriched_callback(value):
|
|
@@ -256,7 +271,6 @@ class CliAppFactory:
|
|
|
256
271
|
"--commands-registration",
|
|
257
272
|
help="Commands registration",
|
|
258
273
|
hidden=True,
|
|
259
|
-
is_eager=True,
|
|
260
274
|
callback=self._commands_registration_callback(),
|
|
261
275
|
),
|
|
262
276
|
) -> None:
|
|
@@ -271,8 +285,36 @@ class CliAppFactory:
|
|
|
271
285
|
pycharm_debug_server_port=pycharm_debug_server_port,
|
|
272
286
|
)
|
|
273
287
|
|
|
288
|
+
self._validate_internal_flags_excluded_from_telemetry(default)
|
|
289
|
+
|
|
274
290
|
self._app = app
|
|
275
291
|
return app
|
|
276
292
|
|
|
293
|
+
@staticmethod
|
|
294
|
+
def _validate_internal_flags_excluded_from_telemetry(callback_function):
|
|
295
|
+
"""
|
|
296
|
+
We have not been interested in collecting telemetry data about root
|
|
297
|
+
command flags (most of which are internal flags). This method validates
|
|
298
|
+
that all new flags should be added to INTERNAL_CLI_FLAGS and thus
|
|
299
|
+
excluded from telemetry as well.
|
|
300
|
+
"""
|
|
301
|
+
sig = inspect.signature(callback_function)
|
|
302
|
+
actual_params = {name for name in sig.parameters.keys() if name != "ctx"}
|
|
303
|
+
if actual_params != INTERNAL_CLI_FLAGS:
|
|
304
|
+
missing = actual_params - INTERNAL_CLI_FLAGS
|
|
305
|
+
extra = INTERNAL_CLI_FLAGS - actual_params
|
|
306
|
+
error_parts = []
|
|
307
|
+
if missing:
|
|
308
|
+
error_parts.append(
|
|
309
|
+
f"Parameters in default() but not in INTERNAL_CLI_FLAGS: {missing}"
|
|
310
|
+
)
|
|
311
|
+
if extra:
|
|
312
|
+
error_parts.append(
|
|
313
|
+
f"Flags in INTERNAL_CLI_FLAGS but not in default(): {extra}"
|
|
314
|
+
)
|
|
315
|
+
raise AssertionError(
|
|
316
|
+
"INTERNAL_CLI_FLAGS mismatch! " + ". ".join(error_parts)
|
|
317
|
+
)
|
|
318
|
+
|
|
277
319
|
def get_click_context(self):
|
|
278
320
|
return self._click_context
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
from snowflake.cli._plugins.auth
|
|
15
|
+
from snowflake.cli._plugins.auth import plugin_spec as auth_plugin_spec
|
|
16
16
|
from snowflake.cli._plugins.connection import plugin_spec as connection_plugin_spec
|
|
17
17
|
from snowflake.cli._plugins.cortex import plugin_spec as cortex_plugin_spec
|
|
18
18
|
from snowflake.cli._plugins.dbt import plugin_spec as dbt_plugin_spec
|
|
@@ -69,7 +69,20 @@ class CommandPluginsLoader:
|
|
|
69
69
|
)
|
|
70
70
|
|
|
71
71
|
def load_all_registered_plugins(self) -> List[LoadedCommandPlugin]:
|
|
72
|
-
|
|
72
|
+
all_plugins = list(self._plugin_manager.list_name_plugin())
|
|
73
|
+
builtin_plugin_names = set(get_builtin_plugin_name_to_plugin_spec().keys())
|
|
74
|
+
|
|
75
|
+
def plugin_sort_key(name_plugin_tuple):
|
|
76
|
+
_plugin_name, _ = name_plugin_tuple
|
|
77
|
+
is_builtin = _plugin_name in builtin_plugin_names
|
|
78
|
+
return (
|
|
79
|
+
not is_builtin,
|
|
80
|
+
_plugin_name,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
sorted_plugins = sorted(all_plugins, key=plugin_sort_key)
|
|
84
|
+
|
|
85
|
+
for plugin_name, plugin in sorted_plugins:
|
|
73
86
|
self._load_plugin(plugin_name, plugin)
|
|
74
87
|
return list(self._loaded_plugins.values())
|
|
75
88
|
|
snowflake/cli/_app/printing.py
CHANGED
|
@@ -22,7 +22,7 @@ from decimal import Decimal
|
|
|
22
22
|
from json import JSONEncoder
|
|
23
23
|
from pathlib import Path
|
|
24
24
|
from textwrap import indent
|
|
25
|
-
from typing import TextIO
|
|
25
|
+
from typing import Any, Dict, TextIO
|
|
26
26
|
|
|
27
27
|
from rich import box, get_console
|
|
28
28
|
from rich import print as rich_print
|
|
@@ -61,13 +61,114 @@ class CustomJSONEncoder(JSONEncoder):
|
|
|
61
61
|
return list(o.result)
|
|
62
62
|
if isinstance(o, (date, datetime, time)):
|
|
63
63
|
return o.isoformat()
|
|
64
|
-
if isinstance(o,
|
|
64
|
+
if isinstance(o, Path):
|
|
65
|
+
return o.as_posix()
|
|
66
|
+
if isinstance(o, Decimal):
|
|
65
67
|
return str(o)
|
|
66
68
|
if isinstance(o, bytearray):
|
|
67
69
|
return o.hex()
|
|
68
70
|
return super().default(o)
|
|
69
71
|
|
|
70
72
|
|
|
73
|
+
class StreamingJSONEncoder(JSONEncoder):
|
|
74
|
+
"""Streaming JSON encoder that doesn't materialize generators into lists"""
|
|
75
|
+
|
|
76
|
+
def default(self, o):
|
|
77
|
+
if isinstance(o, str):
|
|
78
|
+
return sanitize_for_terminal(o)
|
|
79
|
+
if isinstance(o, (ObjectResult, MessageResult)):
|
|
80
|
+
return o.result
|
|
81
|
+
if isinstance(o, (CollectionResult, MultipleResults)):
|
|
82
|
+
raise TypeError(
|
|
83
|
+
f"CollectionResult should be handled by streaming functions, not encoder"
|
|
84
|
+
)
|
|
85
|
+
if isinstance(o, (date, datetime, time)):
|
|
86
|
+
return o.isoformat()
|
|
87
|
+
if isinstance(o, Path):
|
|
88
|
+
return o.as_posix()
|
|
89
|
+
if isinstance(o, Decimal):
|
|
90
|
+
return str(o)
|
|
91
|
+
if isinstance(o, bytearray):
|
|
92
|
+
return o.hex()
|
|
93
|
+
return super().default(o)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _print_json_item_with_array_indentation(item: Any, indent: int):
|
|
97
|
+
"""Print a JSON item with proper indentation for array context"""
|
|
98
|
+
if indent:
|
|
99
|
+
indented_output = json.dumps(item, cls=StreamingJSONEncoder, indent=indent)
|
|
100
|
+
indented_lines = indented_output.split("\n")
|
|
101
|
+
for i, line in enumerate(indented_lines):
|
|
102
|
+
if i == 0:
|
|
103
|
+
print(" " * indent + line, end="")
|
|
104
|
+
else:
|
|
105
|
+
print("\n" + " " * indent + line, end="")
|
|
106
|
+
else:
|
|
107
|
+
json.dump(item, sys.stdout, cls=StreamingJSONEncoder, separators=(",", ":"))
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _stream_collection_as_json(result: CollectionResult, indent: int = 4):
|
|
111
|
+
"""Stream a CollectionResult as a JSON array without loading all data into memory"""
|
|
112
|
+
items = iter(result.result)
|
|
113
|
+
try:
|
|
114
|
+
first_item = next(items)
|
|
115
|
+
except StopIteration:
|
|
116
|
+
print("[]", end="")
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
print("[")
|
|
120
|
+
|
|
121
|
+
_print_json_item_with_array_indentation(first_item, indent)
|
|
122
|
+
|
|
123
|
+
for item in items:
|
|
124
|
+
print(",")
|
|
125
|
+
_print_json_item_with_array_indentation(item, indent)
|
|
126
|
+
|
|
127
|
+
print("\n]", end="")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _stream_collection_as_csv(result: CollectionResult):
|
|
131
|
+
"""Stream a CollectionResult as CSV without loading all data into memory"""
|
|
132
|
+
items = iter(result.result)
|
|
133
|
+
try:
|
|
134
|
+
first_item = next(items)
|
|
135
|
+
except StopIteration:
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
fieldnames = list(first_item.keys())
|
|
139
|
+
if not isinstance(first_item, dict):
|
|
140
|
+
raise TypeError("CSV output requires dictionary items")
|
|
141
|
+
|
|
142
|
+
writer = csv.DictWriter(sys.stdout, fieldnames=fieldnames, lineterminator="\n")
|
|
143
|
+
writer.writeheader()
|
|
144
|
+
_write_csv_row(writer, first_item)
|
|
145
|
+
|
|
146
|
+
for item in items:
|
|
147
|
+
_write_csv_row(writer, item)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _write_csv_row(writer: csv.DictWriter, row_data: Dict[str, Any]):
|
|
151
|
+
"""Write a single CSV row, handling special data types"""
|
|
152
|
+
processed_row = {}
|
|
153
|
+
for key, value in row_data.items():
|
|
154
|
+
if isinstance(value, str):
|
|
155
|
+
processed_row[key] = sanitize_for_terminal(value)
|
|
156
|
+
elif isinstance(value, (date, datetime, time)):
|
|
157
|
+
processed_row[key] = value.isoformat()
|
|
158
|
+
elif isinstance(value, Path):
|
|
159
|
+
processed_row[key] = value.as_posix()
|
|
160
|
+
elif isinstance(value, Decimal):
|
|
161
|
+
processed_row[key] = str(value)
|
|
162
|
+
elif isinstance(value, bytearray):
|
|
163
|
+
processed_row[key] = value.hex()
|
|
164
|
+
elif value is None:
|
|
165
|
+
processed_row[key] = ""
|
|
166
|
+
else:
|
|
167
|
+
processed_row[key] = str(value)
|
|
168
|
+
|
|
169
|
+
writer.writerow(processed_row)
|
|
170
|
+
|
|
171
|
+
|
|
71
172
|
def _get_format_type() -> OutputFormat:
|
|
72
173
|
output_format = get_cli_context().output_format
|
|
73
174
|
if output_format:
|
|
@@ -110,12 +211,13 @@ def is_structured_format(output_format):
|
|
|
110
211
|
def print_structured(
|
|
111
212
|
result: CommandResult, output_format: OutputFormat = OutputFormat.JSON
|
|
112
213
|
):
|
|
113
|
-
"""Handles outputs like json,
|
|
214
|
+
"""Handles outputs like json, csv and other structured and parsable formats with streaming."""
|
|
114
215
|
printed_end_line = False
|
|
216
|
+
|
|
115
217
|
if isinstance(result, MultipleResults):
|
|
116
218
|
if output_format == OutputFormat.CSV:
|
|
117
219
|
for command_result in result.result:
|
|
118
|
-
|
|
220
|
+
_print_csv_result_streaming(command_result)
|
|
119
221
|
print(flush=True)
|
|
120
222
|
printed_end_line = True
|
|
121
223
|
else:
|
|
@@ -125,35 +227,67 @@ def print_structured(
|
|
|
125
227
|
# instead of joining all the values into a JSON array or CSV entry set
|
|
126
228
|
for r in result.result:
|
|
127
229
|
if output_format == OutputFormat.CSV:
|
|
128
|
-
|
|
230
|
+
_print_csv_result_streaming(r)
|
|
129
231
|
else:
|
|
130
|
-
json.dump(r, sys.stdout, cls=
|
|
232
|
+
json.dump(r, sys.stdout, cls=StreamingJSONEncoder)
|
|
131
233
|
print(flush=True)
|
|
132
234
|
printed_end_line = True
|
|
133
235
|
else:
|
|
134
236
|
if output_format == OutputFormat.CSV:
|
|
135
|
-
|
|
237
|
+
_print_csv_result_streaming(result)
|
|
136
238
|
printed_end_line = True
|
|
137
239
|
else:
|
|
138
|
-
|
|
240
|
+
_print_json_result_streaming(result)
|
|
241
|
+
|
|
139
242
|
# Adds empty line at the end
|
|
140
243
|
if not printed_end_line:
|
|
141
244
|
print(flush=True)
|
|
142
245
|
|
|
143
246
|
|
|
144
|
-
def
|
|
145
|
-
|
|
247
|
+
def _print_json_result_streaming(result: CommandResult):
|
|
248
|
+
"""Print a single CommandResult as JSON with streaming support"""
|
|
249
|
+
if isinstance(result, CollectionResult):
|
|
250
|
+
_stream_collection_as_json(result, indent=4)
|
|
251
|
+
elif isinstance(result, (ObjectResult, MessageResult)):
|
|
252
|
+
json.dump(result, sys.stdout, cls=StreamingJSONEncoder, indent=4)
|
|
253
|
+
else:
|
|
254
|
+
json.dump(result, sys.stdout, cls=StreamingJSONEncoder, indent=4)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _print_object_result_as_csv(result: ObjectResult):
|
|
258
|
+
"""Print an ObjectResult as a single-row CSV.
|
|
259
|
+
|
|
260
|
+
Converts the object's key-value pairs into a CSV with headers
|
|
261
|
+
from the keys and a single data row from the values.
|
|
262
|
+
"""
|
|
263
|
+
data = result.result
|
|
146
264
|
if isinstance(data, dict):
|
|
147
|
-
writer = csv.DictWriter(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
elif isinstance(data, list):
|
|
151
|
-
if not data:
|
|
152
|
-
return
|
|
153
|
-
writer = csv.DictWriter(sys.stdout, [*data[0]], lineterminator="\n")
|
|
265
|
+
writer = csv.DictWriter(
|
|
266
|
+
sys.stdout, fieldnames=list(data.keys()), lineterminator="\n"
|
|
267
|
+
)
|
|
154
268
|
writer.writeheader()
|
|
155
|
-
|
|
156
|
-
|
|
269
|
+
_write_csv_row(writer, data)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _print_message_result_as_csv(result: MessageResult):
|
|
273
|
+
"""Print a MessageResult as CSV with a single 'message' column.
|
|
274
|
+
|
|
275
|
+
Creates a simple CSV structure with one column named 'message'
|
|
276
|
+
containing the sanitized message text.
|
|
277
|
+
"""
|
|
278
|
+
writer = csv.DictWriter(sys.stdout, fieldnames=["message"], lineterminator="\n")
|
|
279
|
+
writer.writeheader()
|
|
280
|
+
writer.writerow({"message": sanitize_for_terminal(result.message)})
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _print_csv_result_streaming(result: CommandResult):
|
|
284
|
+
"""Print a single CommandResult as CSV with streaming support"""
|
|
285
|
+
if isinstance(result, CollectionResult):
|
|
286
|
+
_stream_collection_as_csv(result)
|
|
287
|
+
elif isinstance(result, ObjectResult):
|
|
288
|
+
_print_object_result_as_csv(result)
|
|
289
|
+
elif isinstance(result, MessageResult):
|
|
290
|
+
_print_message_result_as_csv(result)
|
|
157
291
|
|
|
158
292
|
|
|
159
293
|
def _stream_json(result):
|
snowflake/cli/_app/telemetry.py
CHANGED
|
@@ -18,11 +18,12 @@ import os
|
|
|
18
18
|
import platform
|
|
19
19
|
import sys
|
|
20
20
|
from enum import Enum, unique
|
|
21
|
-
from typing import Any, Dict, Union
|
|
21
|
+
from typing import Any, Dict, Optional, Union
|
|
22
22
|
|
|
23
23
|
import click
|
|
24
24
|
import typer
|
|
25
25
|
from snowflake.cli import __about__
|
|
26
|
+
from snowflake.cli._app.cli_app import INTERNAL_CLI_FLAGS
|
|
26
27
|
from snowflake.cli._app.constants import PARAM_APPLICATION_NAME
|
|
27
28
|
from snowflake.cli.api.cli_global_context import (
|
|
28
29
|
_CliGlobalContextAccess,
|
|
@@ -38,6 +39,7 @@ from snowflake.connector.telemetry import (
|
|
|
38
39
|
TelemetryField,
|
|
39
40
|
)
|
|
40
41
|
from snowflake.connector.time_util import get_time_millis
|
|
42
|
+
from typer import Context
|
|
41
43
|
|
|
42
44
|
|
|
43
45
|
@unique
|
|
@@ -140,17 +142,30 @@ def _get_command_metrics() -> TelemetryDict:
|
|
|
140
142
|
def _find_command_info() -> TelemetryDict:
|
|
141
143
|
ctx = click.get_current_context()
|
|
142
144
|
command_path = ctx.command_path.split(" ")[1:]
|
|
145
|
+
|
|
146
|
+
command_flags = {}
|
|
147
|
+
format_value = None
|
|
148
|
+
current_ctx: Optional[Context] = ctx
|
|
149
|
+
while current_ctx:
|
|
150
|
+
for flag, flag_value in current_ctx.params.items():
|
|
151
|
+
if (
|
|
152
|
+
flag_value
|
|
153
|
+
and flag not in command_flags
|
|
154
|
+
and flag not in INTERNAL_CLI_FLAGS
|
|
155
|
+
):
|
|
156
|
+
command_flags[flag] = current_ctx.get_parameter_source(flag).name # type: ignore[attr-defined]
|
|
157
|
+
if format_value is None and "format" in current_ctx.params:
|
|
158
|
+
format_value = current_ctx.params["format"]
|
|
159
|
+
current_ctx = current_ctx.parent
|
|
160
|
+
|
|
161
|
+
if format_value is None:
|
|
162
|
+
format_value = OutputFormat.TABLE
|
|
163
|
+
|
|
143
164
|
return {
|
|
144
165
|
CLITelemetryField.COMMAND: command_path,
|
|
145
166
|
CLITelemetryField.COMMAND_GROUP: command_path[0],
|
|
146
|
-
CLITelemetryField.COMMAND_FLAGS:
|
|
147
|
-
|
|
148
|
-
for k, v in ctx.params.items()
|
|
149
|
-
if v # noqa
|
|
150
|
-
},
|
|
151
|
-
CLITelemetryField.COMMAND_OUTPUT_TYPE: ctx.params.get(
|
|
152
|
-
"format", OutputFormat.TABLE
|
|
153
|
-
).value,
|
|
167
|
+
CLITelemetryField.COMMAND_FLAGS: command_flags,
|
|
168
|
+
CLITelemetryField.COMMAND_OUTPUT_TYPE: format_value.value,
|
|
154
169
|
CLITelemetryField.PROJECT_DEFINITION_VERSION: str(_get_definition_version()),
|
|
155
170
|
CLITelemetryField.MODE: _get_cli_running_mode(),
|
|
156
171
|
}
|
|
@@ -227,7 +242,7 @@ class CLITelemetryClient:
|
|
|
227
242
|
**telemetry_payload,
|
|
228
243
|
}
|
|
229
244
|
# To map Enum to string, so we don't have to use .value every time
|
|
230
|
-
return {getattr(k, "value", k): v for k, v in data.items()} # type: ignore[arg-type]
|
|
245
|
+
return {getattr(k, "value", k): v for k, v in data.items()} # type: ignore[arg-type, misc]
|
|
231
246
|
|
|
232
247
|
@property
|
|
233
248
|
def _telemetry(self):
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from snowflake.cli._plugins.auth.keypair.commands import app as keypair_app
|
|
2
1
|
from snowflake.cli._plugins.auth.oidc.commands import (
|
|
3
2
|
app as oidc_app,
|
|
4
3
|
)
|
|
@@ -9,5 +8,4 @@ app = SnowTyperFactory(
|
|
|
9
8
|
help="Manages authentication methods.",
|
|
10
9
|
)
|
|
11
10
|
|
|
12
|
-
app.add_typer(keypair_app)
|
|
13
11
|
app.add_typer(oidc_app)
|
|
@@ -16,9 +16,8 @@ from __future__ import annotations
|
|
|
16
16
|
|
|
17
17
|
import logging
|
|
18
18
|
import os.path
|
|
19
|
-
from copy import deepcopy
|
|
20
19
|
from pathlib import Path
|
|
21
|
-
from typing import
|
|
20
|
+
from typing import Optional
|
|
22
21
|
|
|
23
22
|
import typer
|
|
24
23
|
from click import ( # type: ignore
|
|
@@ -29,14 +28,10 @@ from click import ( # type: ignore
|
|
|
29
28
|
)
|
|
30
29
|
from click.core import ParameterSource # type: ignore
|
|
31
30
|
from snowflake import connector
|
|
32
|
-
from snowflake.cli._app.snow_connector import connect_to_snowflake
|
|
33
|
-
from snowflake.cli._plugins.auth.keypair.commands import KEY_PAIR_DEFAULT_PATH
|
|
34
|
-
from snowflake.cli._plugins.auth.keypair.manager import AuthManager
|
|
35
31
|
from snowflake.cli._plugins.connection.util import (
|
|
36
32
|
strip_if_value_present,
|
|
37
33
|
)
|
|
38
34
|
from snowflake.cli._plugins.object.manager import ObjectManager
|
|
39
|
-
from snowflake.cli.api import exceptions
|
|
40
35
|
from snowflake.cli.api.cli_global_context import get_cli_context
|
|
41
36
|
from snowflake.cli.api.commands.flags import (
|
|
42
37
|
PLAIN_PASSWORD_MSG,
|
|
@@ -69,15 +64,12 @@ from snowflake.cli.api.config import (
|
|
|
69
64
|
)
|
|
70
65
|
from snowflake.cli.api.console import cli_console
|
|
71
66
|
from snowflake.cli.api.constants import ObjectType
|
|
72
|
-
from snowflake.cli.api.feature_flags import FeatureFlag
|
|
73
67
|
from snowflake.cli.api.output.types import (
|
|
74
68
|
CollectionResult,
|
|
75
69
|
CommandResult,
|
|
76
70
|
MessageResult,
|
|
77
71
|
ObjectResult,
|
|
78
72
|
)
|
|
79
|
-
from snowflake.cli.api.secret import SecretType
|
|
80
|
-
from snowflake.cli.api.secure_path import SecurePath
|
|
81
73
|
from snowflake.connector import ProgrammingError
|
|
82
74
|
from snowflake.connector.constants import CONNECTIONS_FILE
|
|
83
75
|
|
|
@@ -302,13 +294,6 @@ def add(
|
|
|
302
294
|
if connection_exists(connection_name):
|
|
303
295
|
raise UsageError(f"Connection {connection_name} already exists")
|
|
304
296
|
|
|
305
|
-
if FeatureFlag.ENABLE_AUTH_KEYPAIR.is_enabled() and not no_interactive:
|
|
306
|
-
connection_options, keypair_error = _extend_add_with_key_pair(
|
|
307
|
-
connection_name, connection_options
|
|
308
|
-
)
|
|
309
|
-
else:
|
|
310
|
-
keypair_error = ""
|
|
311
|
-
|
|
312
297
|
connections_file = add_connection_to_proper_file(
|
|
313
298
|
connection_name,
|
|
314
299
|
ConnectionConfig(**connection_options),
|
|
@@ -316,12 +301,6 @@ def add(
|
|
|
316
301
|
if set_as_default:
|
|
317
302
|
set_config_value(path=["default_connection_name"], value=connection_name)
|
|
318
303
|
|
|
319
|
-
if keypair_error:
|
|
320
|
-
return MessageResult(
|
|
321
|
-
f"Wrote new password-based connection {connection_name} to {connections_file}, "
|
|
322
|
-
f"however there were some issues during key pair setup. Review the following error and check 'snow auth keypair' "
|
|
323
|
-
f"commands to setup key pair authentication:\n * {keypair_error}"
|
|
324
|
-
)
|
|
325
304
|
return MessageResult(
|
|
326
305
|
f"Wrote new connection {connection_name} to {connections_file}"
|
|
327
306
|
)
|
|
@@ -459,59 +438,3 @@ def generate_jwt(
|
|
|
459
438
|
return MessageResult(token)
|
|
460
439
|
except (ValueError, TypeError) as err:
|
|
461
440
|
raise ClickException(str(err))
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
def _extend_add_with_key_pair(
|
|
465
|
-
connection_name: str, connection_options: Dict
|
|
466
|
-
) -> Tuple[Dict, str]:
|
|
467
|
-
if not _should_extend_with_key_pair(connection_options):
|
|
468
|
-
return connection_options, ""
|
|
469
|
-
|
|
470
|
-
configure_key_pair = typer.confirm(
|
|
471
|
-
"Do you want to configure key pair authentication?",
|
|
472
|
-
default=False,
|
|
473
|
-
)
|
|
474
|
-
if not configure_key_pair:
|
|
475
|
-
return connection_options, ""
|
|
476
|
-
|
|
477
|
-
key_length = typer.prompt(
|
|
478
|
-
"Key length",
|
|
479
|
-
default=2048,
|
|
480
|
-
show_default=True,
|
|
481
|
-
)
|
|
482
|
-
|
|
483
|
-
output_path = typer.prompt(
|
|
484
|
-
"Output path",
|
|
485
|
-
default=KEY_PAIR_DEFAULT_PATH,
|
|
486
|
-
show_default=True,
|
|
487
|
-
value_proc=lambda value: SecurePath(value),
|
|
488
|
-
)
|
|
489
|
-
private_key_passphrase = typer.prompt(
|
|
490
|
-
"Private key passphrase",
|
|
491
|
-
default="",
|
|
492
|
-
hide_input=True,
|
|
493
|
-
show_default=False,
|
|
494
|
-
value_proc=lambda value: SecretType(value),
|
|
495
|
-
)
|
|
496
|
-
connection = connect_to_snowflake(temporary_connection=True, **connection_options)
|
|
497
|
-
try:
|
|
498
|
-
connection_options = AuthManager(connection=connection).extend_connection_add(
|
|
499
|
-
connection_name=connection_name,
|
|
500
|
-
connection_options=deepcopy(connection_options),
|
|
501
|
-
key_length=key_length,
|
|
502
|
-
output_path=output_path,
|
|
503
|
-
private_key_passphrase=private_key_passphrase,
|
|
504
|
-
)
|
|
505
|
-
except exceptions.CouldNotSetKeyPairError:
|
|
506
|
-
return connection_options, "The public key is set already."
|
|
507
|
-
except Exception as e:
|
|
508
|
-
return connection_options, str(e)
|
|
509
|
-
return connection_options, ""
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
def _should_extend_with_key_pair(connection_options: Dict) -> bool:
|
|
513
|
-
return (
|
|
514
|
-
connection_options.get("password") is not None
|
|
515
|
-
and connection_options.get("private_key_file") is None
|
|
516
|
-
and connection_options.get("private_key_path") is None
|
|
517
|
-
)
|
|
@@ -19,7 +19,6 @@ from typing import Optional
|
|
|
19
19
|
|
|
20
20
|
import typer
|
|
21
21
|
from click import types
|
|
22
|
-
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
23
22
|
from snowflake.cli._plugins.dbt.constants import (
|
|
24
23
|
DBT_COMMANDS,
|
|
25
24
|
OUTPUT_COLUMN_NAME,
|
|
@@ -31,10 +30,11 @@ from snowflake.cli._plugins.object.command_aliases import add_object_command_ali
|
|
|
31
30
|
from snowflake.cli._plugins.object.commands import scope_option
|
|
32
31
|
from snowflake.cli.api.commands.decorators import global_options_with_connection
|
|
33
32
|
from snowflake.cli.api.commands.flags import identifier_argument, like_option
|
|
33
|
+
from snowflake.cli.api.commands.overrideable_parameter import OverrideableOption
|
|
34
34
|
from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
|
|
35
|
+
from snowflake.cli.api.console.console import cli_console
|
|
35
36
|
from snowflake.cli.api.constants import ObjectType
|
|
36
37
|
from snowflake.cli.api.exceptions import CliError
|
|
37
|
-
from snowflake.cli.api.feature_flags import FeatureFlag
|
|
38
38
|
from snowflake.cli.api.identifiers import FQN
|
|
39
39
|
from snowflake.cli.api.output.types import (
|
|
40
40
|
CommandResult,
|
|
@@ -46,8 +46,6 @@ from snowflake.cli.api.secure_path import SecurePath
|
|
|
46
46
|
app = SnowTyperFactory(
|
|
47
47
|
name="dbt",
|
|
48
48
|
help="Manages dbt on Snowflake projects.",
|
|
49
|
-
is_hidden=FeatureFlag.ENABLE_DBT.is_disabled,
|
|
50
|
-
preview=True,
|
|
51
49
|
)
|
|
52
50
|
log = logging.getLogger(__name__)
|
|
53
51
|
|
|
@@ -59,6 +57,16 @@ DBTNameArgument = identifier_argument(sf_object="DBT Project", example="my_pipel
|
|
|
59
57
|
DBTNameOrCommandArgument = identifier_argument(
|
|
60
58
|
sf_object="DBT Project", example="my_pipeline", click_type=types.StringParamType()
|
|
61
59
|
)
|
|
60
|
+
DefaultTargetOption = OverrideableOption(
|
|
61
|
+
None,
|
|
62
|
+
"--default-target",
|
|
63
|
+
mutually_exclusive=["unset_default_target"],
|
|
64
|
+
)
|
|
65
|
+
UnsetDefaultTargetOption = OverrideableOption(
|
|
66
|
+
False,
|
|
67
|
+
"--unset-default-target",
|
|
68
|
+
mutually_exclusive=["default_target"],
|
|
69
|
+
)
|
|
62
70
|
|
|
63
71
|
add_object_command_aliases(
|
|
64
72
|
app=app,
|
|
@@ -68,7 +76,7 @@ add_object_command_aliases(
|
|
|
68
76
|
help_example='`list --like "my%"` lists all dbt projects that begin with "my"'
|
|
69
77
|
),
|
|
70
78
|
scope_option=scope_option(help_example="`list --in database my_db`"),
|
|
71
|
-
ommit_commands=["
|
|
79
|
+
ommit_commands=["create"],
|
|
72
80
|
)
|
|
73
81
|
|
|
74
82
|
|
|
@@ -92,21 +100,45 @@ def deploy_dbt(
|
|
|
92
100
|
False,
|
|
93
101
|
help="Overwrites conflicting files in the project, if any.",
|
|
94
102
|
),
|
|
103
|
+
default_target: Optional[str] = DefaultTargetOption(
|
|
104
|
+
help="Default target for the dbt project. Mutually exclusive with --unset-default-target.",
|
|
105
|
+
),
|
|
106
|
+
unset_default_target: Optional[bool] = UnsetDefaultTargetOption(
|
|
107
|
+
help="Unset the default target for the dbt project. Mutually exclusive with --default-target.",
|
|
108
|
+
),
|
|
109
|
+
external_access_integrations: Optional[list[str]] = typer.Option(
|
|
110
|
+
None,
|
|
111
|
+
"--external-access-integration",
|
|
112
|
+
show_default=False,
|
|
113
|
+
help="External access integration to be used by the dbt object.",
|
|
114
|
+
),
|
|
115
|
+
install_local_deps: Optional[bool] = typer.Option(
|
|
116
|
+
False,
|
|
117
|
+
"--install-local-deps",
|
|
118
|
+
show_default=False,
|
|
119
|
+
help="Installs local dependencies from project that don't require external access.",
|
|
120
|
+
),
|
|
95
121
|
**options,
|
|
96
122
|
) -> CommandResult:
|
|
97
123
|
"""
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
124
|
+
Upload local dbt project files and create or update a DBT project object on Snowflake.
|
|
125
|
+
|
|
126
|
+
Examples:
|
|
127
|
+
snow dbt deploy PROJECT
|
|
128
|
+
snow dbt deploy PROJECT --source=/Users/jdoe/project --force
|
|
101
129
|
"""
|
|
102
130
|
project_path = SecurePath(source) if source is not None else SecurePath.cwd()
|
|
103
131
|
profiles_dir_path = SecurePath(profiles_dir) if profiles_dir else project_path
|
|
104
132
|
return QueryResult(
|
|
105
133
|
DBTManager().deploy(
|
|
106
134
|
name,
|
|
107
|
-
project_path.resolve(),
|
|
108
|
-
profiles_dir_path.resolve(),
|
|
135
|
+
path=project_path.resolve(),
|
|
136
|
+
profiles_path=profiles_dir_path.resolve(),
|
|
109
137
|
force=force,
|
|
138
|
+
default_target=default_target,
|
|
139
|
+
unset_default_target=unset_default_target,
|
|
140
|
+
external_access_integrations=external_access_integrations,
|
|
141
|
+
install_local_deps=install_local_deps,
|
|
110
142
|
)
|
|
111
143
|
)
|
|
112
144
|
|
|
@@ -116,7 +148,6 @@ dbt_execute_app = SnowTyperFactory(
|
|
|
116
148
|
help="Execute a dbt command on Snowflake. Subcommand name and all "
|
|
117
149
|
"parameters following it will be passed over to dbt.",
|
|
118
150
|
subcommand_metavar="DBT_COMMAND",
|
|
119
|
-
preview=True,
|
|
120
151
|
)
|
|
121
152
|
app.add_typer(dbt_execute_app)
|
|
122
153
|
|
|
@@ -143,7 +174,6 @@ for cmd in DBT_COMMANDS:
|
|
|
143
174
|
context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
|
|
144
175
|
help=f"Execute {cmd} command on Snowflake. Command name and all parameters following it will be passed over to dbt.",
|
|
145
176
|
add_help_option=False,
|
|
146
|
-
preview=True,
|
|
147
177
|
)
|
|
148
178
|
def _dbt_execute(
|
|
149
179
|
ctx: typer.Context,
|
|
@@ -161,13 +191,8 @@ for cmd in DBT_COMMANDS:
|
|
|
161
191
|
f"Command submitted. You can check the result with `snow sql -q \"select execution_status from table(information_schema.query_history_by_user()) where query_id in ('{result.sfqid}');\"`"
|
|
162
192
|
)
|
|
163
193
|
|
|
164
|
-
with
|
|
165
|
-
|
|
166
|
-
TextColumn("[progress.description]{task.description}"),
|
|
167
|
-
transient=True,
|
|
168
|
-
) as progress:
|
|
169
|
-
progress.add_task(description=f"Executing 'dbt {dbt_command}'", total=None)
|
|
170
|
-
|
|
194
|
+
with cli_console.spinner() as spinner:
|
|
195
|
+
spinner.add_task(description=f"Executing 'dbt {dbt_command}'", total=None)
|
|
171
196
|
result = dbt_manager.execute(*execute_args)
|
|
172
197
|
|
|
173
198
|
try:
|