meerschaum 3.0.0rc4__py3-none-any.whl → 3.0.0rc8__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 +435 -0
- meerschaum/_internal/docs/index.py +1 -2
- meerschaum/_internal/entry.py +44 -8
- meerschaum/_internal/shell/Shell.py +115 -24
- meerschaum/_internal/shell/__init__.py +4 -1
- meerschaum/_internal/static.py +4 -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 +2 -2
- meerschaum/api/_oauth2.py +47 -4
- meerschaum/api/dash/callbacks/dashboard.py +29 -0
- meerschaum/api/dash/callbacks/jobs.py +3 -2
- meerschaum/api/dash/callbacks/login.py +10 -1
- meerschaum/api/dash/callbacks/register.py +9 -2
- meerschaum/api/dash/pages/login.py +2 -2
- meerschaum/api/dash/pipes.py +72 -36
- 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 +32 -12
- 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 +93 -79
- meerschaum/connectors/sql/_users.py +8 -1
- meerschaum/connectors/valkey/_ValkeyConnector.py +3 -3
- meerschaum/connectors/valkey/_pipes.py +7 -5
- meerschaum/core/Pipe/__init__.py +45 -71
- 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 +146 -36
- meerschaum/jobs/systemd.py +7 -2
- meerschaum/plugins/__init__.py +277 -81
- meerschaum/utils/daemon/Daemon.py +197 -42
- 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/_shell.py +33 -9
- meerschaum/utils/misc.py +22 -38
- meerschaum/utils/packages/__init__.py +15 -13
- meerschaum/utils/packages/_packages.py +1 -0
- meerschaum/utils/pipes.py +33 -5
- meerschaum/utils/process.py +1 -1
- meerschaum/utils/prompt.py +172 -143
- 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.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/METADATA +3 -1
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/RECORD +116 -110
- meerschaum/config/_environment.py +0 -145
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/WHEEL +0 -0
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/entry_points.txt +0 -0
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/licenses/LICENSE +0 -0
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/licenses/NOTICE +0 -0
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/top_level.txt +0 -0
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/zip-safe +0 -0
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,25 @@ 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
|
+
from meerschaum._internal.static import STATIC_CONFIG
|
78
|
+
|
79
|
+
original_kwargs = {
|
80
|
+
'question': question,
|
81
|
+
'icon': icon,
|
82
|
+
'default': default,
|
83
|
+
'default_editable': default_editable,
|
84
|
+
'detect_password': detect_password,
|
85
|
+
'is_password': is_password,
|
86
|
+
'wrap_lines': wrap_lines,
|
87
|
+
'noask': noask,
|
88
|
+
'silent': silent,
|
89
|
+
**kw
|
90
|
+
}
|
91
|
+
|
72
92
|
noask = check_noask(noask)
|
73
|
-
|
74
|
-
prompt_toolkit = attempt_import('prompt_toolkit')
|
93
|
+
prompt_toolkit = attempt_import('prompt_toolkit')
|
75
94
|
question_config = get_config('formatting', 'question', patch=True)
|
76
95
|
|
77
96
|
### if a default is provided, append it to the question.
|
@@ -103,27 +122,50 @@ def prompt(
|
|
103
122
|
question += first_line
|
104
123
|
if len(other_lines) > 0:
|
105
124
|
question += '\n' + other_lines
|
106
|
-
|
125
|
+
|
126
|
+
if not remove_ansi(question).endswith(' '):
|
127
|
+
question += ' '
|
128
|
+
|
129
|
+
prompt_kwargs = {
|
130
|
+
'message': prompt_toolkit.formatted_text.ANSI(question) if not silent else '',
|
131
|
+
'wrap_lines': wrap_lines,
|
132
|
+
'default': default_editable or '',
|
133
|
+
**filter_keywords(prompt_toolkit.prompt, **kw)
|
134
|
+
}
|
135
|
+
|
136
|
+
printed_question = False
|
107
137
|
|
108
138
|
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
|
-
)
|
139
|
+
answer = prompt_toolkit.prompt(**prompt_kwargs) if not noask else ''
|
140
|
+
printed_question = True
|
117
141
|
else:
|
118
|
-
|
142
|
+
import json
|
143
|
+
daemon = get_current_daemon()
|
144
|
+
print(STATIC_CONFIG['jobs']['flush_token'], end='', flush=True)
|
145
|
+
wrote_file = False
|
146
|
+
try:
|
147
|
+
with open(daemon.prompt_kwargs_file_path, 'w+', encoding='utf-8') as f:
|
148
|
+
json.dump(original_kwargs, f, separators=(',', ':'))
|
149
|
+
wrote_file = True
|
150
|
+
except Exception:
|
151
|
+
pass
|
152
|
+
|
153
|
+
if not silent and not wrote_file:
|
154
|
+
print(question, end='', flush=True)
|
155
|
+
print(STATIC_CONFIG['jobs']['flush_token'], end='', flush=True)
|
156
|
+
printed_question = True
|
157
|
+
|
119
158
|
try:
|
120
159
|
answer = input() if not noask else ''
|
121
160
|
except EOFError:
|
122
161
|
answer = ''
|
123
|
-
|
162
|
+
|
163
|
+
if noask and not silent and not printed_question:
|
124
164
|
print(question)
|
165
|
+
|
125
166
|
if answer == '' and default is not None:
|
126
167
|
return default_answer
|
168
|
+
|
127
169
|
return answer
|
128
170
|
|
129
171
|
|
@@ -207,7 +249,7 @@ def yes_no(
|
|
207
249
|
|
208
250
|
def choose(
|
209
251
|
question: str,
|
210
|
-
choices: List[
|
252
|
+
choices: Union[List[str], List[Tuple[str, str]]],
|
211
253
|
default: Union[str, List[str], None] = None,
|
212
254
|
numeric: bool = True,
|
213
255
|
multiple: bool = False,
|
@@ -270,8 +312,9 @@ def choose(
|
|
270
312
|
noask = check_noask(noask)
|
271
313
|
|
272
314
|
### Handle empty choices.
|
273
|
-
if
|
274
|
-
|
315
|
+
if not choices:
|
316
|
+
if warn:
|
317
|
+
_warn(f"No available choices. Returning default value '{default}'.", stacklevel=3)
|
275
318
|
return default
|
276
319
|
|
277
320
|
### If the default case is to include multiple answers, allow for multiple inputs.
|
@@ -279,18 +322,25 @@ def choose(
|
|
279
322
|
multiple = True
|
280
323
|
|
281
324
|
choices_indices = {}
|
282
|
-
for i, c in enumerate(choices):
|
325
|
+
for i, c in enumerate(choices, start=1):
|
283
326
|
if isinstance(c, tuple):
|
284
327
|
i, c = c
|
285
328
|
choices_indices[i] = c
|
286
329
|
|
330
|
+
choices_values_indices = {v: k for k, v in choices_indices.items()}
|
331
|
+
ordered_keys = list(choices_indices.keys())
|
332
|
+
numeric_map = {str(i): key for i, key in enumerate(ordered_keys, 1)}
|
333
|
+
|
287
334
|
def _enforce_default(d):
|
288
|
-
if d is
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
335
|
+
if d is None:
|
336
|
+
return True
|
337
|
+
if d not in choices_values_indices and d not in choices_indices:
|
338
|
+
if warn:
|
339
|
+
_warn(
|
340
|
+
f"Default choice '{d}' is not contained in the choices. "
|
341
|
+
+ "Setting numeric = False.",
|
342
|
+
stacklevel=3
|
343
|
+
)
|
294
344
|
return False
|
295
345
|
return True
|
296
346
|
|
@@ -300,163 +350,141 @@ def choose(
|
|
300
350
|
numeric = False
|
301
351
|
break
|
302
352
|
|
303
|
-
|
304
|
-
|
353
|
+
_choices = (
|
354
|
+
[str(k) for k in choices_indices] if numeric
|
355
|
+
else list(choices_indices.values())
|
356
|
+
)
|
305
357
|
if multiple:
|
306
358
|
question += f"\n Enter your choices, separated by '{delimiter}'.\n"
|
307
359
|
|
308
360
|
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
|
-
|
361
|
+
if multiple and not numeric:
|
362
|
+
delim_replacement = '_' if delimiter != '_' else '-'
|
335
363
|
### Check if the choices have the delimiter.
|
336
364
|
for i, c in choices_indices.items():
|
337
|
-
if delimiter in c
|
365
|
+
if delimiter not in c:
|
366
|
+
continue
|
367
|
+
if warn:
|
338
368
|
_warn(
|
339
369
|
f"The delimiter '{delimiter}' is contained within choice '{c}'.\n"
|
340
370
|
+ f"Replacing the string '{delimiter}' with '{delim_replacement}' in "
|
341
371
|
+ "the choice for correctly parsing input (will be replaced upon returning the prompt).",
|
342
|
-
stacklevel
|
372
|
+
stacklevel=3,
|
343
373
|
)
|
344
|
-
|
345
|
-
|
346
|
-
altered_indices[i] = new_c
|
347
|
-
for i, new_c in altered_indices.items():
|
374
|
+
new_c = c.replace(delimiter, delim_replacement)
|
375
|
+
altered_choices[new_c] = c
|
348
376
|
choices_indices[i] = new_c
|
349
|
-
default = delimiter.join(default) if isinstance(default, list) else default
|
350
377
|
|
351
378
|
question_options = []
|
379
|
+
default_tuple = None
|
352
380
|
if numeric:
|
353
|
-
|
354
|
-
_default = ''
|
381
|
+
_default_prompt_str = ''
|
355
382
|
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'
|
383
|
+
default_list = default if isinstance(default, list) else [default]
|
384
|
+
if multiple and isinstance(default, str):
|
385
|
+
default_list = default.split(delimiter)
|
386
|
+
|
387
|
+
_default_indices = []
|
388
|
+
for d in default_list:
|
389
|
+
key = None
|
390
|
+
if d in choices_values_indices: # is a value
|
391
|
+
key = choices_values_indices[d]
|
392
|
+
elif d in choices_indices: # is an index
|
393
|
+
key = d
|
394
|
+
|
395
|
+
if key in ordered_keys:
|
396
|
+
_default_indices.append(str(ordered_keys.index(key) + 1))
|
397
|
+
|
398
|
+
_default_prompt_str = delimiter.join(_default_indices)
|
399
|
+
|
374
400
|
choices_digits = len(str(len(choices)))
|
375
|
-
for
|
401
|
+
for choice_ix, c in enumerate(choices_indices.values(), start=1):
|
376
402
|
question_options.append(
|
377
|
-
f" {
|
378
|
-
+ (" " * (choices_digits - len(str(
|
403
|
+
f" {choice_ix}. "
|
404
|
+
+ (" " * (choices_digits - len(str(choice_ix))))
|
379
405
|
+ f"{c}\n"
|
380
406
|
)
|
381
|
-
default_tuple = (
|
407
|
+
default_tuple = (_default_prompt_str, default) if default is not None else None
|
382
408
|
else:
|
383
409
|
default_tuple = default
|
384
|
-
# question += '\n'
|
385
410
|
for c in choices_indices.values():
|
386
|
-
question_options.append(f"{c}\n")
|
411
|
+
question_options.append(f" • {c}\n")
|
387
412
|
|
388
413
|
if 'completer' not in kw:
|
389
|
-
WordCompleter = attempt_import('prompt_toolkit.completion').WordCompleter
|
390
|
-
kw['completer'] = WordCompleter(
|
414
|
+
WordCompleter = attempt_import('prompt_toolkit.completion', lazy=False).WordCompleter
|
415
|
+
kw['completer'] = WordCompleter(
|
416
|
+
[str(v) for v in choices_indices.values()] + [str(i) for i in choices_indices],
|
417
|
+
sentence=True,
|
418
|
+
)
|
391
419
|
|
392
|
-
|
393
|
-
while not
|
420
|
+
answers = []
|
421
|
+
while not answers:
|
394
422
|
print_options(question_options, header='')
|
395
423
|
answer = prompt(
|
396
424
|
question,
|
397
|
-
icon
|
398
|
-
default
|
399
|
-
noask
|
425
|
+
icon=icon,
|
426
|
+
default=default_tuple,
|
427
|
+
noask=noask,
|
400
428
|
**kw
|
401
429
|
)
|
430
|
+
if not answer and default is not None:
|
431
|
+
answer = default if isinstance(default, str) else delimiter.join(default)
|
432
|
+
|
402
433
|
if not answer:
|
434
|
+
if warn:
|
435
|
+
_warn("Please pick a valid choice.", stack=False)
|
403
436
|
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
|
-
|
437
|
+
|
438
|
+
_answers = [answer] if not multiple else [a.strip() for a in answer.split(delimiter)]
|
439
|
+
_answers = [a for a in _answers if a]
|
440
|
+
|
441
|
+
if numeric:
|
442
|
+
_raw_answers = list(_answers)
|
443
|
+
_answers = []
|
444
|
+
for _a in _raw_answers:
|
445
|
+
if _a in choices_values_indices:
|
446
|
+
_answers.append(str(choices_values_indices[_a]))
|
447
|
+
elif _a in numeric_map:
|
448
|
+
_answers.append(str(numeric_map[_a]))
|
449
|
+
else:
|
450
|
+
_answers.append(_a)
|
451
|
+
|
452
|
+
_processed_answers = [altered_choices.get(a, a) for a in _answers]
|
453
|
+
|
454
|
+
valid_answers = []
|
455
|
+
for a in _processed_answers:
|
456
|
+
if a in _choices:
|
457
|
+
valid_answers.append(a)
|
458
|
+
|
459
|
+
if len(valid_answers) != len(_processed_answers):
|
460
|
+
if warn:
|
461
|
+
_warn("Please pick a valid choice.", stack=False)
|
462
|
+
continue
|
463
|
+
answers = valid_answers
|
464
|
+
|
465
|
+
def get_key(key_str):
|
466
|
+
try:
|
467
|
+
return int(key_str)
|
468
|
+
except (ValueError, TypeError):
|
469
|
+
return key_str
|
434
470
|
|
435
471
|
if not multiple:
|
472
|
+
answer = answers[0]
|
436
473
|
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)
|
474
|
+
return choices_values_indices.get(answer, answer) if as_indices else answer
|
475
|
+
|
476
|
+
key = get_key(answer)
|
477
|
+
return key if as_indices else choices_indices[key]
|
445
478
|
|
446
479
|
if not numeric:
|
447
|
-
return answers
|
480
|
+
return [choices_values_indices.get(a, a) for a in answers] if as_indices else answers
|
448
481
|
|
449
|
-
|
482
|
+
final_answers = []
|
450
483
|
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
|
484
|
+
key = get_key(a)
|
485
|
+
final_answers.append(key if as_indices else choices_indices[key])
|
486
|
+
return final_answers
|
487
|
+
|
460
488
|
|
461
489
|
|
462
490
|
def get_password(
|
@@ -570,6 +598,7 @@ def check_noask(noask: bool = False) -> bool:
|
|
570
598
|
NOASK = STATIC_CONFIG['environment']['noask']
|
571
599
|
if noask:
|
572
600
|
return True
|
601
|
+
|
573
602
|
return (
|
574
603
|
os.environ.get(NOASK, 'false').lower()
|
575
604
|
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
|
[
|
meerschaum/utils/threading.py
CHANGED
@@ -10,6 +10,9 @@ from __future__ import annotations
|
|
10
10
|
from meerschaum.utils.typing import Optional
|
11
11
|
|
12
12
|
import threading
|
13
|
+
import ctypes
|
14
|
+
import signal
|
15
|
+
|
13
16
|
Lock = threading.Lock
|
14
17
|
RLock = threading.RLock
|
15
18
|
Event = threading.Event
|
@@ -54,6 +57,45 @@ class Thread(threading.Thread):
|
|
54
57
|
"""Set the return to the result of the target."""
|
55
58
|
self._return = self._target(*self._args, **self._kwargs)
|
56
59
|
|
60
|
+
def send_signal(self, signalnum):
|
61
|
+
"""
|
62
|
+
Send a signal to the thread.
|
63
|
+
"""
|
64
|
+
if not self.is_alive():
|
65
|
+
return
|
66
|
+
|
67
|
+
if signalnum == signal.SIGINT:
|
68
|
+
self.raise_exception(KeyboardInterrupt())
|
69
|
+
elif signalnum == signal.SIGTERM:
|
70
|
+
self.raise_exception(SystemExit())
|
71
|
+
else:
|
72
|
+
signal.pthread_kill(self.ident, signalnum)
|
73
|
+
|
74
|
+
def raise_exception(self, exc: BaseException):
|
75
|
+
"""
|
76
|
+
Raise an exception in the thread.
|
77
|
+
|
78
|
+
This uses a CPython-specific implementation and is not guaranteed to be stable.
|
79
|
+
It may also be deprecated in future Python versions.
|
80
|
+
"""
|
81
|
+
if not self.is_alive():
|
82
|
+
return
|
83
|
+
|
84
|
+
if not hasattr(ctypes.pythonapi, 'PyThreadState_SetAsyncExc'):
|
85
|
+
return
|
86
|
+
|
87
|
+
exc_class = exc if isinstance(exc, type) else type(exc)
|
88
|
+
|
89
|
+
ident = self.ident
|
90
|
+
if ident is None:
|
91
|
+
return
|
92
|
+
|
93
|
+
ret = ctypes.pythonapi.PyThreadState_SetAsyncExc(
|
94
|
+
ctypes.c_ulong(ident),
|
95
|
+
ctypes.py_object(exc_class)
|
96
|
+
)
|
97
|
+
if ret > 1:
|
98
|
+
ctypes.pythonapi.PyThreadState_SetAsyncExc(ident, 0)
|
57
99
|
|
58
100
|
class Worker(threading.Thread):
|
59
101
|
"""Wrapper for `threading.Thread` for working with `queue.Queue` objects."""
|
@@ -8,6 +8,7 @@ Manage virtual environments.
|
|
8
8
|
|
9
9
|
from __future__ import annotations
|
10
10
|
|
11
|
+
import sys
|
11
12
|
import pathlib
|
12
13
|
|
13
14
|
from meerschaum.utils.typing import Optional, Union, Dict, List, Tuple
|
@@ -659,6 +660,7 @@ def venv_exec(
|
|
659
660
|
cmd_list,
|
660
661
|
stdout=stdout,
|
661
662
|
stderr=stderr,
|
663
|
+
stdin=sys.stdin,
|
662
664
|
env=env,
|
663
665
|
**group_kwargs
|
664
666
|
)
|
meerschaum/utils/warnings.py
CHANGED
@@ -100,11 +100,11 @@ def warn(*args, stacklevel=2, stack=True, color: bool = True, **kw) -> None:
|
|
100
100
|
|
101
101
|
|
102
102
|
def exception_with_traceback(
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
103
|
+
message: str,
|
104
|
+
exception_class = Exception,
|
105
|
+
stacklevel = 1,
|
106
|
+
tb_type = 'single'
|
107
|
+
):
|
108
108
|
"""Traceback construction help found here:
|
109
109
|
https://stackoverflow.com/questions/27138440/how-to-create-a-traceback-object
|
110
110
|
"""
|
@@ -165,12 +165,16 @@ def error(
|
|
165
165
|
Raise an exception with supressed traceback.
|
166
166
|
"""
|
167
167
|
from meerschaum.utils.formatting import (
|
168
|
-
CHARSET,
|
168
|
+
CHARSET,
|
169
|
+
ANSI,
|
170
|
+
get_console,
|
171
|
+
fill_ansi,
|
172
|
+
highlight_pipes,
|
173
|
+
_init,
|
169
174
|
)
|
170
175
|
from meerschaum.utils.packages import import_rich, attempt_import
|
171
176
|
from meerschaum.config import get_config
|
172
|
-
|
173
|
-
rich = import_rich()
|
177
|
+
_ = import_rich()
|
174
178
|
rich_traceback = attempt_import('rich.traceback')
|
175
179
|
error_config = get_config('formatting', 'errors', patch=True)
|
176
180
|
message = ' ' + error_config[CHARSET]['icon'] + ' ' + str(message)
|
@@ -186,23 +190,25 @@ def error(
|
|
186
190
|
exception_class, exception, exception.__traceback__
|
187
191
|
)
|
188
192
|
rtb = rich_traceback.Traceback(trace)
|
189
|
-
except Exception
|
193
|
+
except Exception:
|
190
194
|
trace, rtb = None, None
|
191
195
|
if trace is None or rtb is None:
|
192
196
|
nopretty = True
|
193
197
|
if not nopretty and stack:
|
194
198
|
if get_console() is not None:
|
195
199
|
get_console().print(rtb)
|
196
|
-
frame = sys._getframe(len(inspect.stack()) - 1)
|
197
200
|
if raise_:
|
198
201
|
raise color_exception
|
199
202
|
|
200
203
|
|
201
204
|
def info(message: str, icon: bool = True, **kw):
|
202
205
|
"""Print an informative message."""
|
203
|
-
from meerschaum.utils.packages import import_rich, attempt_import
|
204
206
|
from meerschaum.utils.formatting import (
|
205
|
-
CHARSET,
|
207
|
+
CHARSET,
|
208
|
+
ANSI,
|
209
|
+
highlight_pipes,
|
210
|
+
fill_ansi,
|
211
|
+
_init,
|
206
212
|
)
|
207
213
|
from meerschaum.config import get_config
|
208
214
|
info_config = get_config('formatting', 'info', patch=True)
|
@@ -215,4 +221,4 @@ def info(message: str, icon: bool = True, **kw):
|
|
215
221
|
message = fill_ansi(lines[0], **info_config['ansi']['rich']) + (
|
216
222
|
'\n' + '\n'.join(lines[1:]) if len(lines) > 1 else ''
|
217
223
|
)
|
218
|
-
print(message)
|
224
|
+
print(message, flush=True)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: meerschaum
|
3
|
-
Version: 3.0.
|
3
|
+
Version: 3.0.0rc8
|
4
4
|
Summary: Sync Time-Series Pipes with Meerschaum
|
5
5
|
Author-email: Bennett Meares <bennett.meares@gmail.com>
|
6
6
|
Maintainer-email: Bennett Meares <bennett.meares@gmail.com>
|
@@ -192,6 +192,7 @@ Requires-Dist: python-multipart>=0.0.20; extra == "api"
|
|
192
192
|
Requires-Dist: httpx>=0.28.1; extra == "api"
|
193
193
|
Requires-Dist: httpcore>=1.0.9; extra == "api"
|
194
194
|
Requires-Dist: valkey>=6.1.0; extra == "api"
|
195
|
+
Requires-Dist: python-jose>=3.5.0; extra == "api"
|
195
196
|
Requires-Dist: numpy>=2.3.1; extra == "api"
|
196
197
|
Requires-Dist: pandas[parquet]>=2.3.1; extra == "api"
|
197
198
|
Requires-Dist: pyarrow>=20.0.0; extra == "api"
|
@@ -337,6 +338,7 @@ Requires-Dist: python-multipart>=0.0.20; extra == "full"
|
|
337
338
|
Requires-Dist: httpx>=0.28.1; extra == "full"
|
338
339
|
Requires-Dist: httpcore>=1.0.9; extra == "full"
|
339
340
|
Requires-Dist: valkey>=6.1.0; extra == "full"
|
341
|
+
Requires-Dist: python-jose>=3.5.0; extra == "full"
|
340
342
|
Dynamic: license-file
|
341
343
|
Dynamic: provides-extra
|
342
344
|
|