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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from contextlib import contextmanager
|
|
1
2
|
from logging import getLogger
|
|
2
3
|
from typing import Iterable
|
|
3
4
|
|
|
@@ -10,6 +11,7 @@ from prompt_toolkit.lexers import PygmentsLexer
|
|
|
10
11
|
from snowflake.cli._app.printing import print_result
|
|
11
12
|
from snowflake.cli._plugins.sql.lexer import CliLexer, cli_completer
|
|
12
13
|
from snowflake.cli._plugins.sql.manager import SqlManager
|
|
14
|
+
from snowflake.cli._plugins.sql.repl_commands import detect_command
|
|
13
15
|
from snowflake.cli.api.cli_global_context import get_cli_context_manager
|
|
14
16
|
from snowflake.cli.api.console import cli_console
|
|
15
17
|
from snowflake.cli.api.output.types import MultipleResults, QueryResult
|
|
@@ -28,6 +30,21 @@ EXIT_KEYWORDS = ("exit", "quit")
|
|
|
28
30
|
log.debug("setting history file to: %s", HISTORY_FILE.as_posix())
|
|
29
31
|
|
|
30
32
|
|
|
33
|
+
@contextmanager
|
|
34
|
+
def repl_context(repl_instance):
|
|
35
|
+
"""Context manager for REPL execution that handles CLI context registration."""
|
|
36
|
+
context_manager = get_cli_context_manager()
|
|
37
|
+
context_manager.is_repl = True
|
|
38
|
+
context_manager.repl_instance = repl_instance
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
yield
|
|
42
|
+
finally:
|
|
43
|
+
# Clean up REPL context
|
|
44
|
+
context_manager.is_repl = False
|
|
45
|
+
context_manager.repl_instance = None
|
|
46
|
+
|
|
47
|
+
|
|
31
48
|
class Repl:
|
|
32
49
|
"""Basic REPL implementation for the Snowflake CLI."""
|
|
33
50
|
|
|
@@ -45,7 +62,6 @@ class Repl:
|
|
|
45
62
|
`retain_comments` how to handle comments in queries
|
|
46
63
|
"""
|
|
47
64
|
super().__init__()
|
|
48
|
-
setattr(get_cli_context_manager(), "is_repl", True)
|
|
49
65
|
self._data = data or {}
|
|
50
66
|
self._retain_comments = retain_comments
|
|
51
67
|
self._template_syntax_config = template_syntax_config
|
|
@@ -56,6 +72,7 @@ class Repl:
|
|
|
56
72
|
self._yes_no_keybindings = self._setup_yn_key_bindings()
|
|
57
73
|
self._sql_manager = sql_manager
|
|
58
74
|
self.session = PromptSession(history=self._history)
|
|
75
|
+
self._next_input: str | None = None
|
|
59
76
|
|
|
60
77
|
def _setup_key_bindings(self) -> KeyBindings:
|
|
61
78
|
"""Key bindings for repl. Helps detecting ; at end of buffer."""
|
|
@@ -65,22 +82,52 @@ class Repl:
|
|
|
65
82
|
def not_searching():
|
|
66
83
|
return not is_searching()
|
|
67
84
|
|
|
85
|
+
@kb.add(Keys.BracketedPaste)
|
|
86
|
+
def _(event):
|
|
87
|
+
"""Handle bracketed paste - normalize line endings and strip trailing whitespace."""
|
|
88
|
+
pasted_data = event.data
|
|
89
|
+
# Normalize line endings: \r\n -> \n, \r -> \n
|
|
90
|
+
normalized_data = pasted_data.replace("\r\n", "\n").replace("\r", "\n")
|
|
91
|
+
# Strip trailing whitespace
|
|
92
|
+
cleaned_data = normalized_data.rstrip()
|
|
93
|
+
buffer = event.app.current_buffer
|
|
94
|
+
buffer.insert_text(cleaned_data)
|
|
95
|
+
log.debug(
|
|
96
|
+
"handled paste operation, normalized line endings and stripped trailing whitespace"
|
|
97
|
+
)
|
|
98
|
+
|
|
68
99
|
@kb.add(Keys.Enter, filter=not_searching)
|
|
69
100
|
def _(event):
|
|
70
|
-
"""Handle Enter key press.
|
|
101
|
+
"""Handle Enter key press with intelligent execution logic.
|
|
102
|
+
|
|
103
|
+
Execution priority:
|
|
104
|
+
1. Exit keywords (exit, quit) - execute immediately
|
|
105
|
+
2. REPL commands (starting with !) - execute immediately
|
|
106
|
+
3. SQL with trailing semicolon - execute immediately
|
|
107
|
+
4. All other input - add new line for multi-line editing
|
|
108
|
+
"""
|
|
71
109
|
buffer = event.app.current_buffer
|
|
72
|
-
|
|
110
|
+
buffer_text = buffer.text
|
|
111
|
+
stripped_text = buffer_text.strip()
|
|
73
112
|
|
|
74
|
-
if
|
|
113
|
+
if stripped_text:
|
|
75
114
|
log.debug("evaluating repl input")
|
|
76
115
|
cursor_position = buffer.cursor_position
|
|
77
|
-
ends_with_semicolon =
|
|
116
|
+
ends_with_semicolon = stripped_text.endswith(";")
|
|
117
|
+
is_command = detect_command(stripped_text) is not None
|
|
78
118
|
|
|
79
|
-
|
|
80
|
-
|
|
119
|
+
meaningful_content_end = len(buffer_text.rstrip())
|
|
120
|
+
cursor_at_meaningful_end = cursor_position >= meaningful_content_end
|
|
121
|
+
|
|
122
|
+
if stripped_text.lower() in EXIT_KEYWORDS:
|
|
123
|
+
log.debug("exit keyword detected %r", stripped_text)
|
|
124
|
+
buffer.validate_and_handle()
|
|
125
|
+
|
|
126
|
+
elif is_command:
|
|
127
|
+
log.debug("command detected, submitting input")
|
|
81
128
|
buffer.validate_and_handle()
|
|
82
129
|
|
|
83
|
-
elif ends_with_semicolon and
|
|
130
|
+
elif ends_with_semicolon and cursor_at_meaningful_end:
|
|
84
131
|
log.debug("semicolon detected, submitting input")
|
|
85
132
|
buffer.validate_and_handle()
|
|
86
133
|
|
|
@@ -118,16 +165,27 @@ class Repl:
|
|
|
118
165
|
|
|
119
166
|
return kb
|
|
120
167
|
|
|
121
|
-
def
|
|
122
|
-
"""Regular repl prompt.
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
168
|
+
def repl_prompt(self, msg: str = " > ") -> str:
|
|
169
|
+
"""Regular repl prompt with support for pre-filled input.
|
|
170
|
+
|
|
171
|
+
Checks for queued input from commands like !edit and uses it as
|
|
172
|
+
default text in the prompt. The queued input is cleared after use.
|
|
173
|
+
"""
|
|
174
|
+
default_text = self._next_input
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
return self.session.prompt(
|
|
178
|
+
msg,
|
|
179
|
+
lexer=self._lexer,
|
|
180
|
+
completer=self._completer,
|
|
181
|
+
multiline=True,
|
|
182
|
+
wrap_lines=True,
|
|
183
|
+
key_bindings=self._repl_key_bindings,
|
|
184
|
+
default=default_text or "",
|
|
185
|
+
)
|
|
186
|
+
finally:
|
|
187
|
+
if self._next_input == default_text:
|
|
188
|
+
self._next_input = None
|
|
131
189
|
|
|
132
190
|
def yn_prompt(self, msg: str) -> str:
|
|
133
191
|
"""Yes/No prompt."""
|
|
@@ -142,7 +200,7 @@ class Repl:
|
|
|
142
200
|
|
|
143
201
|
@property
|
|
144
202
|
def _welcome_banner(self) -> str:
|
|
145
|
-
return
|
|
203
|
+
return "Welcome to Snowflake-CLI REPL\nType 'exit' or 'quit' to leave"
|
|
146
204
|
|
|
147
205
|
def _initialize_connection(self):
|
|
148
206
|
"""Early connection for possible fast fail."""
|
|
@@ -163,12 +221,13 @@ class Repl:
|
|
|
163
221
|
return cursors
|
|
164
222
|
|
|
165
223
|
def run(self):
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
224
|
+
with repl_context(self):
|
|
225
|
+
try:
|
|
226
|
+
cli_console.panel(self._welcome_banner)
|
|
227
|
+
self._initialize_connection()
|
|
228
|
+
self._repl_loop()
|
|
229
|
+
except (KeyboardInterrupt, EOFError):
|
|
230
|
+
cli_console.message("\n[bold orange_red1]Leaving REPL, bye ...")
|
|
172
231
|
|
|
173
232
|
def _repl_loop(self):
|
|
174
233
|
"""Main REPL loop. Handles input and query execution.
|
|
@@ -178,7 +237,7 @@ class Repl:
|
|
|
178
237
|
"""
|
|
179
238
|
while True:
|
|
180
239
|
try:
|
|
181
|
-
user_input = self.
|
|
240
|
+
user_input = self.repl_prompt().strip()
|
|
182
241
|
|
|
183
242
|
if not user_input:
|
|
184
243
|
continue
|
|
@@ -210,6 +269,21 @@ class Repl:
|
|
|
210
269
|
except Exception as e:
|
|
211
270
|
cli_console.warning(f"\nError occurred: {e}")
|
|
212
271
|
|
|
272
|
+
def set_next_input(self, text: str) -> None:
|
|
273
|
+
"""Set the text that will be used as the next REPL input."""
|
|
274
|
+
self._next_input = text
|
|
275
|
+
log.debug("Next input has been set")
|
|
276
|
+
|
|
277
|
+
@property
|
|
278
|
+
def next_input(self) -> str | None:
|
|
279
|
+
"""Get the next input text that will be used in the prompt."""
|
|
280
|
+
return self._next_input
|
|
281
|
+
|
|
282
|
+
@property
|
|
283
|
+
def history(self) -> FileHistory:
|
|
284
|
+
"""Get the FileHistory instance used by the REPL."""
|
|
285
|
+
return self._history
|
|
286
|
+
|
|
213
287
|
def ask_yn(self, question: str) -> bool:
|
|
214
288
|
"""Asks user a Yes/No question."""
|
|
215
289
|
try:
|