meerschaum 3.0.0rc3__py3-none-any.whl → 3.0.0rc7__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.
- meerschaum/_internal/arguments/_parser.py +14 -2
- meerschaum/_internal/cli/__init__.py +6 -0
- meerschaum/_internal/cli/daemons.py +103 -0
- meerschaum/_internal/cli/entry.py +220 -0
- meerschaum/_internal/cli/workers.py +434 -0
- meerschaum/_internal/docs/index.py +1 -2
- meerschaum/_internal/entry.py +44 -8
- meerschaum/_internal/shell/Shell.py +113 -19
- meerschaum/_internal/shell/__init__.py +4 -1
- meerschaum/_internal/static.py +3 -1
- meerschaum/_internal/term/TermPageHandler.py +1 -2
- meerschaum/_internal/term/__init__.py +40 -6
- meerschaum/_internal/term/tools.py +33 -8
- meerschaum/actions/__init__.py +6 -4
- meerschaum/actions/api.py +39 -11
- meerschaum/actions/attach.py +1 -0
- meerschaum/actions/delete.py +4 -2
- meerschaum/actions/edit.py +27 -8
- meerschaum/actions/login.py +8 -8
- meerschaum/actions/register.py +13 -7
- meerschaum/actions/reload.py +22 -5
- meerschaum/actions/restart.py +14 -0
- meerschaum/actions/show.py +69 -4
- meerschaum/actions/start.py +135 -14
- meerschaum/actions/stop.py +36 -3
- meerschaum/actions/sync.py +6 -1
- meerschaum/api/__init__.py +35 -13
- meerschaum/api/_events.py +7 -2
- meerschaum/api/_oauth2.py +47 -4
- meerschaum/api/dash/callbacks/dashboard.py +103 -97
- meerschaum/api/dash/callbacks/jobs.py +3 -2
- meerschaum/api/dash/callbacks/login.py +10 -1
- meerschaum/api/dash/callbacks/pipes.py +136 -57
- meerschaum/api/dash/callbacks/register.py +9 -2
- meerschaum/api/dash/callbacks/tokens.py +2 -1
- meerschaum/api/dash/components.py +6 -7
- meerschaum/api/dash/keys.py +17 -1
- meerschaum/api/dash/pages/login.py +2 -2
- meerschaum/api/dash/pages/pipes.py +14 -4
- meerschaum/api/dash/pipes.py +186 -65
- meerschaum/api/dash/tokens.py +1 -1
- meerschaum/api/dash/webterm.py +14 -6
- meerschaum/api/models/_pipes.py +7 -1
- meerschaum/api/resources/static/js/terminado.js +3 -0
- meerschaum/api/resources/static/js/xterm-addon-unicode11.js +2 -0
- meerschaum/api/resources/templates/termpage.html +1 -0
- meerschaum/api/routes/_jobs.py +23 -11
- meerschaum/api/routes/_login.py +73 -5
- meerschaum/api/routes/_pipes.py +6 -4
- meerschaum/api/routes/_webterm.py +3 -3
- meerschaum/config/__init__.py +60 -13
- meerschaum/config/_default.py +89 -61
- meerschaum/config/_edit.py +10 -8
- meerschaum/config/_formatting.py +2 -0
- meerschaum/config/_patch.py +4 -2
- meerschaum/config/_paths.py +127 -12
- meerschaum/config/_read_config.py +20 -10
- meerschaum/config/_version.py +1 -1
- meerschaum/config/environment.py +262 -0
- meerschaum/config/stack/__init__.py +7 -5
- meerschaum/connectors/_Connector.py +1 -2
- meerschaum/connectors/__init__.py +37 -2
- meerschaum/connectors/api/_APIConnector.py +1 -1
- meerschaum/connectors/api/_jobs.py +11 -0
- meerschaum/connectors/api/_pipes.py +7 -1
- meerschaum/connectors/instance/_plugins.py +9 -1
- meerschaum/connectors/instance/_tokens.py +20 -3
- meerschaum/connectors/instance/_users.py +8 -1
- meerschaum/connectors/parse.py +1 -1
- meerschaum/connectors/sql/_create_engine.py +3 -0
- meerschaum/connectors/sql/_pipes.py +98 -79
- meerschaum/connectors/sql/_users.py +8 -1
- meerschaum/connectors/sql/tables/__init__.py +20 -3
- meerschaum/connectors/valkey/_ValkeyConnector.py +3 -3
- meerschaum/connectors/valkey/_pipes.py +7 -5
- meerschaum/core/Pipe/__init__.py +62 -72
- meerschaum/core/Pipe/_attributes.py +66 -90
- meerschaum/core/Pipe/_cache.py +555 -0
- meerschaum/core/Pipe/_clear.py +0 -11
- meerschaum/core/Pipe/_data.py +0 -50
- meerschaum/core/Pipe/_deduplicate.py +0 -13
- meerschaum/core/Pipe/_delete.py +12 -21
- meerschaum/core/Pipe/_drop.py +11 -23
- meerschaum/core/Pipe/_dtypes.py +1 -1
- meerschaum/core/Pipe/_index.py +8 -14
- meerschaum/core/Pipe/_sync.py +12 -18
- meerschaum/core/Plugin/_Plugin.py +7 -1
- meerschaum/core/Token/_Token.py +1 -1
- meerschaum/core/User/_User.py +1 -2
- meerschaum/jobs/_Executor.py +88 -4
- meerschaum/jobs/_Job.py +135 -35
- meerschaum/jobs/systemd.py +7 -2
- meerschaum/plugins/__init__.py +277 -81
- meerschaum/utils/_get_pipes.py +30 -4
- meerschaum/utils/daemon/Daemon.py +195 -41
- meerschaum/utils/daemon/FileDescriptorInterceptor.py +0 -1
- meerschaum/utils/daemon/RotatingFile.py +63 -36
- meerschaum/utils/daemon/StdinFile.py +53 -13
- meerschaum/utils/daemon/__init__.py +18 -5
- meerschaum/utils/daemon/_names.py +6 -3
- meerschaum/utils/debug.py +34 -4
- meerschaum/utils/dtypes/__init__.py +5 -1
- meerschaum/utils/formatting/__init__.py +4 -1
- meerschaum/utils/formatting/_jobs.py +1 -1
- meerschaum/utils/formatting/_pipes.py +47 -46
- meerschaum/utils/formatting/_pprint.py +1 -0
- meerschaum/utils/formatting/_shell.py +16 -6
- meerschaum/utils/misc.py +18 -38
- meerschaum/utils/packages/__init__.py +15 -13
- meerschaum/utils/packages/_packages.py +1 -0
- meerschaum/utils/pipes.py +39 -7
- meerschaum/utils/process.py +1 -1
- meerschaum/utils/prompt.py +171 -144
- meerschaum/utils/sql.py +12 -2
- meerschaum/utils/threading.py +42 -0
- meerschaum/utils/venv/__init__.py +2 -0
- meerschaum/utils/warnings.py +19 -13
- {meerschaum-3.0.0rc3.dist-info → meerschaum-3.0.0rc7.dist-info}/METADATA +3 -1
- {meerschaum-3.0.0rc3.dist-info → meerschaum-3.0.0rc7.dist-info}/RECORD +125 -119
- meerschaum/config/_environment.py +0 -145
- {meerschaum-3.0.0rc3.dist-info → meerschaum-3.0.0rc7.dist-info}/WHEEL +0 -0
- {meerschaum-3.0.0rc3.dist-info → meerschaum-3.0.0rc7.dist-info}/entry_points.txt +0 -0
- {meerschaum-3.0.0rc3.dist-info → meerschaum-3.0.0rc7.dist-info}/licenses/LICENSE +0 -0
- {meerschaum-3.0.0rc3.dist-info → meerschaum-3.0.0rc7.dist-info}/licenses/NOTICE +0 -0
- {meerschaum-3.0.0rc3.dist-info → meerschaum-3.0.0rc7.dist-info}/top_level.txt +0 -0
- {meerschaum-3.0.0rc3.dist-info → meerschaum-3.0.0rc7.dist-info}/zip-safe +0 -0
@@ -136,17 +136,17 @@ def get_module_path(
|
|
136
136
|
|
137
137
|
|
138
138
|
def manually_import_module(
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
139
|
+
import_name: str,
|
140
|
+
venv: Optional[str] = 'mrsm',
|
141
|
+
check_update: bool = True,
|
142
|
+
check_pypi: bool = False,
|
143
|
+
install: bool = True,
|
144
|
+
split: bool = True,
|
145
|
+
warn: bool = True,
|
146
|
+
color: bool = True,
|
147
|
+
debug: bool = False,
|
148
|
+
use_sys_modules: bool = True,
|
149
|
+
) -> Union['ModuleType', None]:
|
150
150
|
"""
|
151
151
|
Manually import a module from a virtual environment (or the base environment).
|
152
152
|
|
@@ -1179,7 +1179,8 @@ def run_python_package(
|
|
1179
1179
|
Either a return code integer or a `subprocess.Popen` object
|
1180
1180
|
(or `None` if a `KeyboardInterrupt` occurs and as_proc is `True`).
|
1181
1181
|
"""
|
1182
|
-
import sys
|
1182
|
+
import sys
|
1183
|
+
import platform
|
1183
1184
|
import subprocess
|
1184
1185
|
from meerschaum.config._paths import VIRTENV_RESOURCES_PATH
|
1185
1186
|
from meerschaum.utils.process import run_process
|
@@ -1206,7 +1207,7 @@ def run_python_package(
|
|
1206
1207
|
capture_output=capture_output,
|
1207
1208
|
**kw
|
1208
1209
|
)
|
1209
|
-
except Exception
|
1210
|
+
except Exception:
|
1210
1211
|
msg = f"Failed to execute {command}, will try again:\n{traceback.format_exc()}"
|
1211
1212
|
warn(msg, color=False)
|
1212
1213
|
stdout, stderr = (
|
@@ -1218,6 +1219,7 @@ def run_python_package(
|
|
1218
1219
|
command,
|
1219
1220
|
stdout=stdout,
|
1220
1221
|
stderr=stderr,
|
1222
|
+
stdin=sys.stdin,
|
1221
1223
|
env=env_dict,
|
1222
1224
|
)
|
1223
1225
|
to_return = proc if as_proc else proc.wait()
|
@@ -175,6 +175,7 @@ packages['api'] = {
|
|
175
175
|
'httpx' : 'httpx>=0.28.1',
|
176
176
|
'httpcore' : 'httpcore>=1.0.9',
|
177
177
|
'valkey' : 'valkey>=6.1.0',
|
178
|
+
'jose' : 'python-jose>=3.5.0',
|
178
179
|
}
|
179
180
|
packages['api'].update(packages['sql'])
|
180
181
|
packages['api'].update(packages['formatting'])
|
meerschaum/utils/pipes.py
CHANGED
@@ -14,7 +14,7 @@ import ast
|
|
14
14
|
import copy
|
15
15
|
import uuid
|
16
16
|
|
17
|
-
from meerschaum.utils.typing import PipesDict, Optional
|
17
|
+
from meerschaum.utils.typing import PipesDict, Optional
|
18
18
|
import meerschaum as mrsm
|
19
19
|
|
20
20
|
|
@@ -96,9 +96,7 @@ def replace_pipes_syntax(text: str) -> Any:
|
|
96
96
|
Parse a string containing the `{{ Pipe() }}` syntax.
|
97
97
|
"""
|
98
98
|
from meerschaum.utils.warnings import warn
|
99
|
-
from meerschaum.utils.sql import sql_item_name
|
100
99
|
from meerschaum.utils.dtypes import json_serialize_value
|
101
|
-
from meerschaum.utils.misc import parse_arguments_str
|
102
100
|
pattern = r'\{\{\s*(?:mrsm\.)?Pipe\((.*?)\)((?:\.[\w]+|\[[^\]]+\])*)\s*\}\}'
|
103
101
|
|
104
102
|
matches = list(re.finditer(pattern, text))
|
@@ -158,17 +156,20 @@ def replace_pipes_in_dict(
|
|
158
156
|
|
159
157
|
debug: bool, default False
|
160
158
|
Verbosity toggle.
|
161
|
-
|
162
159
|
|
163
160
|
Returns
|
164
161
|
-------
|
165
162
|
A dictionary where every pipe is replaced with the output of a function.
|
166
163
|
|
167
164
|
"""
|
168
|
-
def change_dict(d
|
165
|
+
def change_dict(d: Dict[Any, Any]) -> None:
|
169
166
|
for k, v in d.items():
|
170
167
|
if isinstance(v, dict):
|
171
|
-
change_dict(v
|
168
|
+
change_dict(v)
|
169
|
+
elif isinstance(v, list):
|
170
|
+
d[k] = [func(i) for i in v]
|
171
|
+
elif isinstance(v, tuple):
|
172
|
+
d[k] = tuple([func(i) for i in v])
|
172
173
|
else:
|
173
174
|
d[k] = func(v)
|
174
175
|
|
@@ -177,5 +178,36 @@ def replace_pipes_in_dict(
|
|
177
178
|
pipes = get_pipes(debug=debug, **kw)
|
178
179
|
|
179
180
|
result = copy.deepcopy(pipes)
|
180
|
-
change_dict(result
|
181
|
+
change_dict(result)
|
181
182
|
return result
|
183
|
+
|
184
|
+
|
185
|
+
def is_pipe_registered(
|
186
|
+
pipe: mrsm.Pipe,
|
187
|
+
pipes: PipesDict,
|
188
|
+
debug: bool = False
|
189
|
+
) -> bool:
|
190
|
+
"""
|
191
|
+
Check if a Pipe is inside the pipes dictionary.
|
192
|
+
|
193
|
+
Parameters
|
194
|
+
----------
|
195
|
+
pipe: meerschaum.Pipe
|
196
|
+
The pipe to see if it's in the dictionary.
|
197
|
+
|
198
|
+
pipes: PipesDict
|
199
|
+
The dictionary to search inside.
|
200
|
+
|
201
|
+
debug: bool, default False
|
202
|
+
Verbosity toggle.
|
203
|
+
|
204
|
+
Returns
|
205
|
+
-------
|
206
|
+
A bool indicating whether the pipe is inside the dictionary.
|
207
|
+
"""
|
208
|
+
from meerschaum.utils.debug import dprint
|
209
|
+
ck, mk, lk = pipe.connector_keys, pipe.metric_key, pipe.location_key
|
210
|
+
if debug:
|
211
|
+
dprint(f'{ck}, {mk}, {lk}')
|
212
|
+
dprint(f'{pipe}, {pipes}')
|
213
|
+
return ck in pipes and mk in pipes[ck] and lk in pipes[ck][mk]
|
meerschaum/utils/process.py
CHANGED
meerschaum/utils/prompt.py
CHANGED
@@ -14,7 +14,8 @@ from meerschaum.utils.typing import Any, Union, Optional, Tuple, List
|
|
14
14
|
|
15
15
|
|
16
16
|
def prompt(
|
17
|
-
question: str,
|
17
|
+
question: str = '',
|
18
|
+
*,
|
18
19
|
icon: bool = True,
|
19
20
|
default: Union[str, Tuple[str, str], None] = None,
|
20
21
|
default_editable: Optional[str] = None,
|
@@ -22,6 +23,7 @@ def prompt(
|
|
22
23
|
is_password: bool = False,
|
23
24
|
wrap_lines: bool = True,
|
24
25
|
noask: bool = False,
|
26
|
+
silent: bool = False,
|
25
27
|
**kw: Any
|
26
28
|
) -> str:
|
27
29
|
"""
|
@@ -59,6 +61,9 @@ def prompt(
|
|
59
61
|
noask: bool, default False
|
60
62
|
If `True`, only print the question and return the default answer.
|
61
63
|
|
64
|
+
silent: bool, default False
|
65
|
+
If `True` do not print anything to the screen, but still block for input.
|
66
|
+
|
62
67
|
Returns
|
63
68
|
-------
|
64
69
|
A `str` of the input provided by the user.
|
@@ -67,11 +72,24 @@ def prompt(
|
|
67
72
|
from meerschaum.utils.packages import attempt_import
|
68
73
|
from meerschaum.utils.formatting import ANSI, CHARSET, highlight_pipes, fill_ansi
|
69
74
|
from meerschaum.config import get_config
|
70
|
-
from meerschaum.utils.misc import filter_keywords
|
71
|
-
from meerschaum.utils.daemon import running_in_daemon
|
75
|
+
from meerschaum.utils.misc import filter_keywords, remove_ansi
|
76
|
+
from meerschaum.utils.daemon import running_in_daemon, get_current_daemon
|
77
|
+
|
78
|
+
original_kwargs = {
|
79
|
+
'question': question,
|
80
|
+
'icon': icon,
|
81
|
+
'default': default,
|
82
|
+
'default_editable': default_editable,
|
83
|
+
'detect_password': detect_password,
|
84
|
+
'is_password': is_password,
|
85
|
+
'wrap_lines': wrap_lines,
|
86
|
+
'noask': noask,
|
87
|
+
'silent': silent,
|
88
|
+
**kw
|
89
|
+
}
|
90
|
+
|
72
91
|
noask = check_noask(noask)
|
73
|
-
|
74
|
-
prompt_toolkit = attempt_import('prompt_toolkit')
|
92
|
+
prompt_toolkit = attempt_import('prompt_toolkit')
|
75
93
|
question_config = get_config('formatting', 'question', patch=True)
|
76
94
|
|
77
95
|
### if a default is provided, append it to the question.
|
@@ -103,27 +121,49 @@ def prompt(
|
|
103
121
|
question += first_line
|
104
122
|
if len(other_lines) > 0:
|
105
123
|
question += '\n' + other_lines
|
106
|
-
|
124
|
+
|
125
|
+
if not remove_ansi(question).endswith(' '):
|
126
|
+
question += ' '
|
127
|
+
|
128
|
+
prompt_kwargs = {
|
129
|
+
'message': prompt_toolkit.formatted_text.ANSI(question) if not silent else '',
|
130
|
+
'wrap_lines': wrap_lines,
|
131
|
+
'default': default_editable or '',
|
132
|
+
**filter_keywords(prompt_toolkit.prompt, **kw)
|
133
|
+
}
|
134
|
+
|
135
|
+
printed_question = False
|
107
136
|
|
108
137
|
if not running_in_daemon():
|
109
|
-
answer = (
|
110
|
-
|
111
|
-
prompt_toolkit.formatted_text.ANSI(question),
|
112
|
-
wrap_lines=wrap_lines,
|
113
|
-
default=default_editable or '',
|
114
|
-
**filter_keywords(prompt_toolkit.prompt, **kw)
|
115
|
-
) if not noask else ''
|
116
|
-
)
|
138
|
+
answer = prompt_toolkit.prompt(**prompt_kwargs) if not noask else ''
|
139
|
+
printed_question = True
|
117
140
|
else:
|
118
|
-
|
141
|
+
import json
|
142
|
+
daemon = get_current_daemon()
|
143
|
+
print('', end='', flush=True)
|
144
|
+
wrote_file = False
|
145
|
+
try:
|
146
|
+
with open(daemon.prompt_kwargs_file_path, 'w+', encoding='utf-8') as f:
|
147
|
+
json.dump(original_kwargs, f, separators=(',', ':'))
|
148
|
+
wrote_file = True
|
149
|
+
except Exception:
|
150
|
+
pass
|
151
|
+
|
152
|
+
if not silent and not wrote_file:
|
153
|
+
print(question, end='', flush=True)
|
154
|
+
printed_question = True
|
155
|
+
|
119
156
|
try:
|
120
157
|
answer = input() if not noask else ''
|
121
158
|
except EOFError:
|
122
159
|
answer = ''
|
123
|
-
|
124
|
-
|
160
|
+
|
161
|
+
if noask and not silent and not printed_question:
|
162
|
+
print(question, flush=True)
|
163
|
+
|
125
164
|
if answer == '' and default is not None:
|
126
165
|
return default_answer
|
166
|
+
|
127
167
|
return answer
|
128
168
|
|
129
169
|
|
@@ -207,7 +247,7 @@ def yes_no(
|
|
207
247
|
|
208
248
|
def choose(
|
209
249
|
question: str,
|
210
|
-
choices: List[
|
250
|
+
choices: Union[List[str], List[Tuple[str, str]]],
|
211
251
|
default: Union[str, List[str], None] = None,
|
212
252
|
numeric: bool = True,
|
213
253
|
multiple: bool = False,
|
@@ -270,8 +310,9 @@ def choose(
|
|
270
310
|
noask = check_noask(noask)
|
271
311
|
|
272
312
|
### Handle empty choices.
|
273
|
-
if
|
274
|
-
|
313
|
+
if not choices:
|
314
|
+
if warn:
|
315
|
+
_warn(f"No available choices. Returning default value '{default}'.", stacklevel=3)
|
275
316
|
return default
|
276
317
|
|
277
318
|
### If the default case is to include multiple answers, allow for multiple inputs.
|
@@ -279,18 +320,25 @@ def choose(
|
|
279
320
|
multiple = True
|
280
321
|
|
281
322
|
choices_indices = {}
|
282
|
-
for i, c in enumerate(choices):
|
323
|
+
for i, c in enumerate(choices, start=1):
|
283
324
|
if isinstance(c, tuple):
|
284
325
|
i, c = c
|
285
326
|
choices_indices[i] = c
|
286
327
|
|
328
|
+
choices_values_indices = {v: k for k, v in choices_indices.items()}
|
329
|
+
ordered_keys = list(choices_indices.keys())
|
330
|
+
numeric_map = {str(i): key for i, key in enumerate(ordered_keys, 1)}
|
331
|
+
|
287
332
|
def _enforce_default(d):
|
288
|
-
if d is
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
333
|
+
if d is None:
|
334
|
+
return True
|
335
|
+
if d not in choices_values_indices and d not in choices_indices:
|
336
|
+
if warn:
|
337
|
+
_warn(
|
338
|
+
f"Default choice '{d}' is not contained in the choices. "
|
339
|
+
+ "Setting numeric = False.",
|
340
|
+
stacklevel=3
|
341
|
+
)
|
294
342
|
return False
|
295
343
|
return True
|
296
344
|
|
@@ -300,163 +348,141 @@ def choose(
|
|
300
348
|
numeric = False
|
301
349
|
break
|
302
350
|
|
303
|
-
|
304
|
-
|
351
|
+
_choices = (
|
352
|
+
[str(k) for k in choices_indices] if numeric
|
353
|
+
else list(choices_indices.values())
|
354
|
+
)
|
305
355
|
if multiple:
|
306
356
|
question += f"\n Enter your choices, separated by '{delimiter}'.\n"
|
307
357
|
|
308
358
|
altered_choices = {}
|
309
|
-
|
310
|
-
|
311
|
-
delim_replacement = '_' if delimiter != '_' else '-'
|
312
|
-
can_strip_start_spaces, can_strip_end_spaces = True, True
|
313
|
-
for i, c in choices_indices.items():
|
314
|
-
if isinstance(c, tuple):
|
315
|
-
key, c = c
|
316
|
-
if can_strip_start_spaces and c.startswith(' '):
|
317
|
-
can_strip_start_spaces = False
|
318
|
-
if can_strip_end_spaces and c.endswith(' '):
|
319
|
-
can_strip_end_spaces = False
|
320
|
-
|
321
|
-
if multiple:
|
322
|
-
### Check if the defaults have the delimiter.
|
323
|
-
for i, d in enumerate(default if isinstance(default, list) else [default]):
|
324
|
-
if d is None or delimiter not in d:
|
325
|
-
continue
|
326
|
-
new_d = d.replace(delimiter, delim_replacement)
|
327
|
-
altered_choices[new_d] = d
|
328
|
-
altered_default_indices[i] = new_d
|
329
|
-
for i, new_d in altered_default_indices.items():
|
330
|
-
if not isinstance(default, list):
|
331
|
-
default = new_d
|
332
|
-
break
|
333
|
-
default[i] = new_d
|
334
|
-
|
359
|
+
if multiple and not numeric:
|
360
|
+
delim_replacement = '_' if delimiter != '_' else '-'
|
335
361
|
### Check if the choices have the delimiter.
|
336
362
|
for i, c in choices_indices.items():
|
337
|
-
if delimiter in c
|
363
|
+
if delimiter not in c:
|
364
|
+
continue
|
365
|
+
if warn:
|
338
366
|
_warn(
|
339
367
|
f"The delimiter '{delimiter}' is contained within choice '{c}'.\n"
|
340
368
|
+ f"Replacing the string '{delimiter}' with '{delim_replacement}' in "
|
341
369
|
+ "the choice for correctly parsing input (will be replaced upon returning the prompt).",
|
342
|
-
stacklevel
|
370
|
+
stacklevel=3,
|
343
371
|
)
|
344
|
-
|
345
|
-
|
346
|
-
altered_indices[i] = new_c
|
347
|
-
for i, new_c in altered_indices.items():
|
372
|
+
new_c = c.replace(delimiter, delim_replacement)
|
373
|
+
altered_choices[new_c] = c
|
348
374
|
choices_indices[i] = new_c
|
349
|
-
default = delimiter.join(default) if isinstance(default, list) else default
|
350
375
|
|
351
376
|
question_options = []
|
377
|
+
default_tuple = None
|
352
378
|
if numeric:
|
353
|
-
|
354
|
-
_default = ''
|
379
|
+
_default_prompt_str = ''
|
355
380
|
if default is not None:
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
# question += '\n'
|
381
|
+
default_list = default if isinstance(default, list) else [default]
|
382
|
+
if multiple and isinstance(default, str):
|
383
|
+
default_list = default.split(delimiter)
|
384
|
+
|
385
|
+
_default_indices = []
|
386
|
+
for d in default_list:
|
387
|
+
key = None
|
388
|
+
if d in choices_values_indices: # is a value
|
389
|
+
key = choices_values_indices[d]
|
390
|
+
elif d in choices_indices: # is an index
|
391
|
+
key = d
|
392
|
+
|
393
|
+
if key in ordered_keys:
|
394
|
+
_default_indices.append(str(ordered_keys.index(key) + 1))
|
395
|
+
|
396
|
+
_default_prompt_str = delimiter.join(_default_indices)
|
397
|
+
|
374
398
|
choices_digits = len(str(len(choices)))
|
375
|
-
for
|
399
|
+
for choice_ix, c in enumerate(choices_indices.values(), start=1):
|
376
400
|
question_options.append(
|
377
|
-
f" {
|
378
|
-
+ (" " * (choices_digits - len(str(
|
401
|
+
f" {choice_ix}. "
|
402
|
+
+ (" " * (choices_digits - len(str(choice_ix))))
|
379
403
|
+ f"{c}\n"
|
380
404
|
)
|
381
|
-
default_tuple = (
|
405
|
+
default_tuple = (_default_prompt_str, default) if default is not None else None
|
382
406
|
else:
|
383
407
|
default_tuple = default
|
384
|
-
# question += '\n'
|
385
408
|
for c in choices_indices.values():
|
386
|
-
question_options.append(f"{c}\n")
|
409
|
+
question_options.append(f" • {c}\n")
|
387
410
|
|
388
411
|
if 'completer' not in kw:
|
389
|
-
WordCompleter = attempt_import('prompt_toolkit.completion').WordCompleter
|
390
|
-
kw['completer'] = WordCompleter(
|
412
|
+
WordCompleter = attempt_import('prompt_toolkit.completion', lazy=False).WordCompleter
|
413
|
+
kw['completer'] = WordCompleter(
|
414
|
+
[str(v) for v in choices_indices.values()] + [str(i) for i in choices_indices],
|
415
|
+
sentence=True,
|
416
|
+
)
|
391
417
|
|
392
|
-
|
393
|
-
while not
|
418
|
+
answers = []
|
419
|
+
while not answers:
|
394
420
|
print_options(question_options, header='')
|
395
421
|
answer = prompt(
|
396
422
|
question,
|
397
|
-
icon
|
398
|
-
default
|
399
|
-
noask
|
423
|
+
icon=icon,
|
424
|
+
default=default_tuple,
|
425
|
+
noask=noask,
|
400
426
|
**kw
|
401
427
|
)
|
428
|
+
if not answer and default is not None:
|
429
|
+
answer = default if isinstance(default, str) else delimiter.join(default)
|
430
|
+
|
402
431
|
if not answer:
|
432
|
+
if warn:
|
433
|
+
_warn("Please pick a valid choice.", stack=False)
|
403
434
|
continue
|
404
|
-
|
405
|
-
_answers = [answer] if not multiple else [a for a in answer.split(delimiter)]
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
valid =
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
435
|
+
|
436
|
+
_answers = [answer] if not multiple else [a.strip() for a in answer.split(delimiter)]
|
437
|
+
_answers = [a for a in _answers if a]
|
438
|
+
|
439
|
+
if numeric:
|
440
|
+
_raw_answers = list(_answers)
|
441
|
+
_answers = []
|
442
|
+
for _a in _raw_answers:
|
443
|
+
if _a in choices_values_indices:
|
444
|
+
_answers.append(str(choices_values_indices[_a]))
|
445
|
+
elif _a in numeric_map:
|
446
|
+
_answers.append(str(numeric_map[_a]))
|
447
|
+
else:
|
448
|
+
_answers.append(_a)
|
449
|
+
|
450
|
+
_processed_answers = [altered_choices.get(a, a) for a in _answers]
|
451
|
+
|
452
|
+
valid_answers = []
|
453
|
+
for a in _processed_answers:
|
454
|
+
if a in _choices:
|
455
|
+
valid_answers.append(a)
|
456
|
+
|
457
|
+
if len(valid_answers) != len(_processed_answers):
|
458
|
+
if warn:
|
459
|
+
_warn("Please pick a valid choice.", stack=False)
|
460
|
+
continue
|
461
|
+
answers = valid_answers
|
462
|
+
|
463
|
+
def get_key(key_str):
|
464
|
+
try:
|
465
|
+
return int(key_str)
|
466
|
+
except (ValueError, TypeError):
|
467
|
+
return key_str
|
434
468
|
|
435
469
|
if not multiple:
|
470
|
+
answer = answers[0]
|
436
471
|
if not numeric:
|
437
|
-
return answer
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
return _answer[0]
|
442
|
-
return _answer
|
443
|
-
except Exception:
|
444
|
-
_warn(f"Could not cast answer '{answer}' to an integer.", stacklevel=3)
|
472
|
+
return choices_values_indices.get(answer, answer) if as_indices else answer
|
473
|
+
|
474
|
+
key = get_key(answer)
|
475
|
+
return key if as_indices else choices_indices[key]
|
445
476
|
|
446
477
|
if not numeric:
|
447
|
-
return answers
|
478
|
+
return [choices_values_indices.get(a, a) for a in answers] if as_indices else answers
|
448
479
|
|
449
|
-
|
480
|
+
final_answers = []
|
450
481
|
for a in answers:
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
_answer_to_return = _answer_to_return[0]
|
456
|
-
_answers.append(_answer_to_return)
|
457
|
-
except Exception:
|
458
|
-
_warn(f"Could not cast answer '{a}' to an integer.", stacklevel=3)
|
459
|
-
return _answers
|
482
|
+
key = get_key(a)
|
483
|
+
final_answers.append(key if as_indices else choices_indices[key])
|
484
|
+
return final_answers
|
485
|
+
|
460
486
|
|
461
487
|
|
462
488
|
def get_password(
|
@@ -570,6 +596,7 @@ def check_noask(noask: bool = False) -> bool:
|
|
570
596
|
NOASK = STATIC_CONFIG['environment']['noask']
|
571
597
|
if noask:
|
572
598
|
return True
|
599
|
+
|
573
600
|
return (
|
574
601
|
os.environ.get(NOASK, 'false').lower()
|
575
602
|
in ('1', 'true')
|
meerschaum/utils/sql.py
CHANGED
@@ -1586,6 +1586,8 @@ def get_update_queries(
|
|
1586
1586
|
datetime_col: Optional[str] = None,
|
1587
1587
|
schema: Optional[str] = None,
|
1588
1588
|
patch_schema: Optional[str] = None,
|
1589
|
+
target_cols_types: Optional[Dict[str, str]] = None,
|
1590
|
+
patch_cols_types: Optional[Dict[str, str]] = None,
|
1589
1591
|
identity_insert: bool = False,
|
1590
1592
|
null_indices: bool = True,
|
1591
1593
|
cast_columns: bool = True,
|
@@ -1626,6 +1628,14 @@ def get_update_queries(
|
|
1626
1628
|
If provided, use this schema when quoting the patch table.
|
1627
1629
|
Defaults to `schema`.
|
1628
1630
|
|
1631
|
+
target_cols_types: Optional[Dict[str, Any]], default None
|
1632
|
+
If provided, use these as the columns-types dictionary for the target table.
|
1633
|
+
Default will infer from the database context.
|
1634
|
+
|
1635
|
+
patch_cols_types: Optional[Dict[str, Any]], default None
|
1636
|
+
If provided, use these as the columns-types dictionary for the target table.
|
1637
|
+
Default will infer from the database context.
|
1638
|
+
|
1629
1639
|
identity_insert: bool, default False
|
1630
1640
|
If `True`, include `SET IDENTITY_INSERT` queries before and after the update queries.
|
1631
1641
|
Only applies for MSSQL upserts.
|
@@ -1672,14 +1682,14 @@ def get_update_queries(
|
|
1672
1682
|
flavor=flavor,
|
1673
1683
|
schema=schema,
|
1674
1684
|
debug=debug,
|
1675
|
-
)
|
1685
|
+
) if not target_cols_types else target_cols_types
|
1676
1686
|
patch_table_columns = get_table_cols_types(
|
1677
1687
|
patch,
|
1678
1688
|
connectable,
|
1679
1689
|
flavor=flavor,
|
1680
1690
|
schema=patch_schema,
|
1681
1691
|
debug=debug,
|
1682
|
-
)
|
1692
|
+
) if not patch_cols_types else patch_cols_types
|
1683
1693
|
|
1684
1694
|
patch_cols_str = ', '.join(
|
1685
1695
|
[
|