meerschaum 2.2.3__py3-none-any.whl → 2.2.5.dev0__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/entry.py +2 -4
- meerschaum/actions/__init__.py +5 -1
- meerschaum/actions/backup.py +43 -0
- meerschaum/actions/bootstrap.py +246 -0
- meerschaum/actions/delete.py +62 -0
- meerschaum/actions/python.py +45 -14
- meerschaum/actions/show.py +26 -0
- meerschaum/api/_oauth2.py +17 -0
- meerschaum/api/routes/_login.py +23 -7
- meerschaum/config/_edit.py +1 -1
- meerschaum/config/_paths.py +3 -0
- meerschaum/config/_version.py +1 -1
- meerschaum/config/stack/__init__.py +3 -1
- meerschaum/utils/daemon/Daemon.py +14 -3
- meerschaum/utils/daemon/FileDescriptorInterceptor.py +21 -14
- meerschaum/utils/daemon/RotatingFile.py +21 -18
- meerschaum/utils/packages/_packages.py +1 -0
- meerschaum/utils/prompt.py +53 -17
- meerschaum/utils/yaml.py +32 -1
- {meerschaum-2.2.3.dist-info → meerschaum-2.2.5.dev0.dist-info}/METADATA +5 -1
- {meerschaum-2.2.3.dist-info → meerschaum-2.2.5.dev0.dist-info}/RECORD +27 -26
- {meerschaum-2.2.3.dist-info → meerschaum-2.2.5.dev0.dist-info}/WHEEL +1 -1
- {meerschaum-2.2.3.dist-info → meerschaum-2.2.5.dev0.dist-info}/LICENSE +0 -0
- {meerschaum-2.2.3.dist-info → meerschaum-2.2.5.dev0.dist-info}/NOTICE +0 -0
- {meerschaum-2.2.3.dist-info → meerschaum-2.2.5.dev0.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.2.3.dist-info → meerschaum-2.2.5.dev0.dist-info}/top_level.txt +0 -0
- {meerschaum-2.2.3.dist-info → meerschaum-2.2.5.dev0.dist-info}/zip-safe +0 -0
meerschaum/_internal/entry.py
CHANGED
@@ -47,9 +47,6 @@ def entry(sysargs: Optional[List[str]] = None) -> SuccessTuple:
|
|
47
47
|
)
|
48
48
|
)
|
49
49
|
|
50
|
-
# if args.get('schedule', None):
|
51
|
-
# from meerschaum.utils.schedule import schedule_function
|
52
|
-
# return schedule_function(entry_with_args, **args)
|
53
50
|
return entry_with_args(**args)
|
54
51
|
|
55
52
|
|
@@ -122,11 +119,12 @@ def entry_with_args(
|
|
122
119
|
def _do_action_wrapper(action_function, plugin_name, **kw):
|
123
120
|
from meerschaum.plugins import Plugin
|
124
121
|
from meerschaum.utils.venv import Venv, active_venvs, deactivate_venv
|
122
|
+
from meerschaum.utils.misc import filter_keywords
|
125
123
|
plugin = Plugin(plugin_name) if plugin_name else None
|
126
124
|
with Venv(plugin, debug=kw.get('debug', False)):
|
127
125
|
action_name = ' '.join(action_function.__name__.split('_') + kw.get('action', []))
|
128
126
|
try:
|
129
|
-
result = action_function(**kw)
|
127
|
+
result = action_function(**filter_keywords(action_function, **kw))
|
130
128
|
except Exception as e:
|
131
129
|
if kw.get('debug', False):
|
132
130
|
import traceback
|
meerschaum/actions/__init__.py
CHANGED
@@ -40,7 +40,11 @@ def get_subactions(
|
|
40
40
|
for name, f in inspect.getmembers(action_module):
|
41
41
|
if not inspect.isfunction(f):
|
42
42
|
continue
|
43
|
-
|
43
|
+
|
44
|
+
### Detect subactions which may contain an underscore prefix.
|
45
|
+
if (
|
46
|
+
name.lstrip('_').startswith(action_function.__name__.lstrip('_') + '_')
|
47
|
+
):
|
44
48
|
_name = name.replace(action_function.__name__, '')
|
45
49
|
_name = _name.lstrip('_')
|
46
50
|
subactions[_name] = f
|
@@ -0,0 +1,43 @@
|
|
1
|
+
#! /usr/bin/env python3
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
# vim:fenc=utf-8
|
4
|
+
|
5
|
+
"""
|
6
|
+
Backup the stack database.
|
7
|
+
"""
|
8
|
+
|
9
|
+
from __future__ import annotations
|
10
|
+
from meerschaum.utils.typing import SuccessTuple, Any, List, Optional, Union
|
11
|
+
|
12
|
+
def backup(
|
13
|
+
action: Optional[List[str]] = None,
|
14
|
+
**kwargs: Any
|
15
|
+
) -> SuccessTuple:
|
16
|
+
"""
|
17
|
+
Backup the stack database.
|
18
|
+
"""
|
19
|
+
from meerschaum.actions import choose_subaction
|
20
|
+
options = {
|
21
|
+
'database' : _backup_database,
|
22
|
+
}
|
23
|
+
return choose_subaction(action, options, **kwargs)
|
24
|
+
|
25
|
+
|
26
|
+
def _backup_database(
|
27
|
+
action: Optional[List[str]] = None,
|
28
|
+
) -> SuccessTuple:
|
29
|
+
"""
|
30
|
+
Backup the stack's database to a sql file.
|
31
|
+
"""
|
32
|
+
from meerschaum.actions import get_action
|
33
|
+
from meerschaum.config.paths import BACKUP_RESOURCES_PATH
|
34
|
+
do_stack = get_action('stack')
|
35
|
+
cmd_list = ['exec', 'db', 'pg']
|
36
|
+
stack_success, stack_msg = do_stack(['exec'])
|
37
|
+
return True, "Success"
|
38
|
+
|
39
|
+
### NOTE: This must be the final statement of the module.
|
40
|
+
### Any subactions added below these lines will not
|
41
|
+
### be added to the `help` docstring.
|
42
|
+
from meerschaum.utils.misc import choices_docstring as _choices_docstring
|
43
|
+
backup.__doc__ += _choices_docstring('backup')
|
meerschaum/actions/bootstrap.py
CHANGED
@@ -25,6 +25,7 @@ def bootstrap(
|
|
25
25
|
options = {
|
26
26
|
'pipes' : _bootstrap_pipes,
|
27
27
|
'connectors' : _bootstrap_connectors,
|
28
|
+
'plugins' : _bootstrap_plugins,
|
28
29
|
}
|
29
30
|
return choose_subaction(action, options, **kw)
|
30
31
|
|
@@ -382,6 +383,251 @@ def _bootstrap_connectors(
|
|
382
383
|
return True, "Success"
|
383
384
|
|
384
385
|
|
386
|
+
def _bootstrap_plugins(
|
387
|
+
action: Optional[List[str]] = None,
|
388
|
+
debug: bool = False,
|
389
|
+
**kwargs: Any
|
390
|
+
) -> SuccessTuple:
|
391
|
+
"""
|
392
|
+
Launch an interactive wizard to guide the user to creating a new plugin.
|
393
|
+
"""
|
394
|
+
import pathlib
|
395
|
+
import meerschaum as mrsm
|
396
|
+
from meerschaum.utils.warnings import info, warn
|
397
|
+
from meerschaum.utils.prompt import prompt, choose, yes_no
|
398
|
+
from meerschaum.utils.formatting._shell import clear_screen
|
399
|
+
from meerschaum.utils.misc import edit_file
|
400
|
+
from meerschaum.config.paths import PLUGINS_DIR_PATHS
|
401
|
+
from meerschaum._internal.entry import entry
|
402
|
+
|
403
|
+
if not action:
|
404
|
+
action = [prompt("Enter the name of your new plugin:")]
|
405
|
+
|
406
|
+
if len(PLUGINS_DIR_PATHS) > 1:
|
407
|
+
plugins_dir_path = pathlib.Path(
|
408
|
+
choose(
|
409
|
+
"In which directory do you want to write your plugin?",
|
410
|
+
[path.as_posix() for path in PLUGINS_DIR_PATHS],
|
411
|
+
numeric = True,
|
412
|
+
multiple = False,
|
413
|
+
default = PLUGINS_DIR_PATHS[0].as_posix(),
|
414
|
+
)
|
415
|
+
)
|
416
|
+
else:
|
417
|
+
plugins_dir_path = PLUGINS_DIR_PATHS[0]
|
418
|
+
|
419
|
+
clear_screen(debug=debug)
|
420
|
+
info(
|
421
|
+
"Answer the questions below to pick out features.\n"
|
422
|
+
+ " See the Writing Plugins guide for documentation:\n"
|
423
|
+
+ " https://meerschaum.io/reference/plugins/writing-plugins/ for documentation.\n"
|
424
|
+
)
|
425
|
+
|
426
|
+
imports_lines = {
|
427
|
+
'default': (
|
428
|
+
"import meerschaum as mrsm\n"
|
429
|
+
),
|
430
|
+
'action': (
|
431
|
+
"from meerschaum.actions import make_action\n"
|
432
|
+
),
|
433
|
+
'api': (
|
434
|
+
"from meerschaum.plugins import api_plugin\n"
|
435
|
+
),
|
436
|
+
'web': (
|
437
|
+
"from meerschaum.plugins import web_page, dash_plugin\n"
|
438
|
+
),
|
439
|
+
'api+web': (
|
440
|
+
"from meerschaum.plugins import api_plugin, web_page, dash_plugin\n"
|
441
|
+
),
|
442
|
+
}
|
443
|
+
|
444
|
+
### TODO: Add feature for custom connectors.
|
445
|
+
feature_lines = {
|
446
|
+
'header': (
|
447
|
+
"# {plugin_name}.py\n\n"
|
448
|
+
),
|
449
|
+
'default': (
|
450
|
+
"__version__ = '0.0.1'\n"
|
451
|
+
"\n# Add any depedencies to `required` (similar to `requirements.txt`).\n"
|
452
|
+
"required = []\n\n"
|
453
|
+
),
|
454
|
+
'setup': (
|
455
|
+
"def setup(**kwargs) -> mrsm.SuccessTuple:\n"
|
456
|
+
" \"\"\"Executed during installation and `mrsm setup plugin {plugin_name}`.\"\"\"\n"
|
457
|
+
" return True, \"Success\"\n\n\n"
|
458
|
+
),
|
459
|
+
'register': (
|
460
|
+
"def register(pipe: mrsm.Pipe):\n"
|
461
|
+
" \"\"\"Return the default parameters for a new pipe.\"\"\"\n"
|
462
|
+
" return {\n"
|
463
|
+
" 'columns': {\n"
|
464
|
+
" 'datetime': None,\n"
|
465
|
+
" }\n"
|
466
|
+
" }\n\n\n"
|
467
|
+
),
|
468
|
+
'fetch': (
|
469
|
+
"def fetch(pipe: mrsm.Pipe, **kwargs):\n"
|
470
|
+
" \"\"\"Return or yield dataframe-like objects.\"\"\"\n"
|
471
|
+
" docs = []\n"
|
472
|
+
" # populate docs with dictionaries (rows).\n"
|
473
|
+
" return docs\n\n\n"
|
474
|
+
),
|
475
|
+
'action': (
|
476
|
+
"@make_action\n"
|
477
|
+
"def {action_name}(**kwargs) -> mrsm.SuccessTuple:\n"
|
478
|
+
" \"\"\"Run `mrsm {action_spaces}` to trigger.\"\"\"\n"
|
479
|
+
" return True, \"Success\"\n\n\n"
|
480
|
+
),
|
481
|
+
'api': (
|
482
|
+
"@api_plugin\n"
|
483
|
+
"def init_app(fastapi_app):\n"
|
484
|
+
" \"\"\"Add new endpoints to the FastAPI app.\"\"\"\n\n"
|
485
|
+
" import fastapi\n"
|
486
|
+
" from meerschaum.api import manager\n\n"
|
487
|
+
" @fastapi_app.get('/my/endpoint')\n"
|
488
|
+
" def get_my_endpoint(curr_user=fastapi.Depends(manager)):\n"
|
489
|
+
" return {'message': 'Hello, World!'}\n\n\n"
|
490
|
+
),
|
491
|
+
'web': (
|
492
|
+
"@dash_plugin\n"
|
493
|
+
"def init_dash(dash_app):\n"
|
494
|
+
" \"\"\"Initialize the Plotly Dash application.\"\"\"\n"
|
495
|
+
" import dash.html as html\n"
|
496
|
+
" import dash.dcc as dcc\n"
|
497
|
+
" from dash import Input, Output, State, no_update\n"
|
498
|
+
" import dash_bootstrap_components as dbc\n\n"
|
499
|
+
" # Create a new page at the path `/dash/{plugin_name}`.\n"
|
500
|
+
" @web_page('{plugin_name}', login_required=False)\n"
|
501
|
+
" def page_layout():\n"
|
502
|
+
" \"\"\"Return the layout objects for this page.\"\"\"\n"
|
503
|
+
" return dbc.Container([\n"
|
504
|
+
" dcc.Location(id='{plugin_name}-location'),\n"
|
505
|
+
" html.Div(id='output-div'),\n"
|
506
|
+
" ])\n\n"
|
507
|
+
" @dash_app.callback(\n"
|
508
|
+
" Output('output-div', 'children'),\n"
|
509
|
+
" Input('{plugin_name}-location', 'pathname'),\n"
|
510
|
+
" )\n"
|
511
|
+
" def render_page_on_url_change(pathname: str):\n"
|
512
|
+
" \"\"\"Reload page contents when the URL path changes.\"\"\"\n"
|
513
|
+
" return html.H1(\"Hello from plugin '{plugin_name}'!\")\n\n\n"
|
514
|
+
),
|
515
|
+
}
|
516
|
+
|
517
|
+
for plugin_name in action:
|
518
|
+
plugin_path = plugins_dir_path / (plugin_name + '.py')
|
519
|
+
plugin = mrsm.Plugin(plugin_name)
|
520
|
+
if plugin.is_installed():
|
521
|
+
warn(f"Plugin '{plugin_name}' is already installed!", stack=False)
|
522
|
+
uninstall_plugin = yes_no(
|
523
|
+
f"Do you want to first uninstall '{plugin}'?",
|
524
|
+
default = 'n',
|
525
|
+
**kwargs
|
526
|
+
)
|
527
|
+
if not uninstall_plugin:
|
528
|
+
return False, f"Plugin '{plugin_name}' already exists."
|
529
|
+
|
530
|
+
uninstall_success, uninstall_msg = entry(['uninstall', 'plugin', plugin_name, '-f'])
|
531
|
+
if not uninstall_success:
|
532
|
+
return uninstall_success, uninstall_msg
|
533
|
+
clear_screen(debug=debug)
|
534
|
+
|
535
|
+
features = choose(
|
536
|
+
"Which of the following features would you like to add to your plugin?",
|
537
|
+
[
|
538
|
+
(
|
539
|
+
'fetch',
|
540
|
+
'Fetch data\n (e.g. extracting from a remote API)\n'
|
541
|
+
),
|
542
|
+
(
|
543
|
+
'action',
|
544
|
+
'New actions\n (e.g. `mrsm sing song`)\n'
|
545
|
+
),
|
546
|
+
(
|
547
|
+
'api',
|
548
|
+
'New API endpoints\n (e.g. `POST /my/new/endpoint`)\n',
|
549
|
+
),
|
550
|
+
(
|
551
|
+
'web',
|
552
|
+
'New web console page\n (e.g. `/dash/my-web-app`)\n',
|
553
|
+
),
|
554
|
+
],
|
555
|
+
default = 'fetch',
|
556
|
+
multiple = True,
|
557
|
+
as_indices = True,
|
558
|
+
**kwargs
|
559
|
+
)
|
560
|
+
|
561
|
+
action_name = ''
|
562
|
+
if 'action' in features:
|
563
|
+
while True:
|
564
|
+
try:
|
565
|
+
action_name = prompt(
|
566
|
+
"What is name of your action?\n "
|
567
|
+
+ "(separate subactions with spaces, e.g. `sing song`):"
|
568
|
+
).replace(' ', '_')
|
569
|
+
except KeyboardInterrupt as e:
|
570
|
+
return False, "Aborted plugin creation."
|
571
|
+
|
572
|
+
if action_name:
|
573
|
+
break
|
574
|
+
warn("Please enter an action.", stack=False)
|
575
|
+
|
576
|
+
action_spaces = action_name.replace('_', ' ')
|
577
|
+
|
578
|
+
plugin_labels = {
|
579
|
+
'plugin_name': plugin_name,
|
580
|
+
'action_name': action_name,
|
581
|
+
'action_spaces': action_spaces,
|
582
|
+
}
|
583
|
+
|
584
|
+
body_text = ""
|
585
|
+
body_text += feature_lines['header'].format(**plugin_labels)
|
586
|
+
body_text += imports_lines['default'].format(**plugin_labels)
|
587
|
+
if 'action' in features:
|
588
|
+
body_text += imports_lines['action']
|
589
|
+
if 'api' in features and 'web' in features:
|
590
|
+
body_text += imports_lines['api+web']
|
591
|
+
elif 'api' in features:
|
592
|
+
body_text += imports_lines['api']
|
593
|
+
elif 'web' in features:
|
594
|
+
body_text += imports_lines['web']
|
595
|
+
|
596
|
+
body_text += "\n"
|
597
|
+
body_text += feature_lines['default'].format(**plugin_labels)
|
598
|
+
body_text += feature_lines['setup'].format(**plugin_labels)
|
599
|
+
|
600
|
+
if 'fetch' in features:
|
601
|
+
body_text += feature_lines['register']
|
602
|
+
body_text += feature_lines['fetch']
|
603
|
+
|
604
|
+
if 'action' in features:
|
605
|
+
body_text += feature_lines['action'].format(**plugin_labels)
|
606
|
+
|
607
|
+
if 'api' in features:
|
608
|
+
body_text += feature_lines['api']
|
609
|
+
|
610
|
+
if 'web' in features:
|
611
|
+
body_text += feature_lines['web'].format(**plugin_labels)
|
612
|
+
|
613
|
+
try:
|
614
|
+
with open(plugin_path, 'w+', encoding='utf-8') as f:
|
615
|
+
f.write(body_text.rstrip())
|
616
|
+
except Exception as e:
|
617
|
+
error_msg = f"Failed to write file '{plugin_path}':\n{e}"
|
618
|
+
return False, error_msg
|
619
|
+
|
620
|
+
mrsm.pprint((True, f"Successfully created file '{plugin_path}'."))
|
621
|
+
try:
|
622
|
+
_ = prompt(f"Press [Enter] to edit plugin '{plugin_name}', [CTRL+C] to skip.")
|
623
|
+
except (KeyboardInterrupt, Exception):
|
624
|
+
continue
|
625
|
+
|
626
|
+
edit_file(plugin_path, debug=debug)
|
627
|
+
|
628
|
+
return True, "Success"
|
629
|
+
|
630
|
+
|
385
631
|
### NOTE: This must be the final statement of the module.
|
386
632
|
### Any subactions added below these lines will not
|
387
633
|
### be added to the `help` docstring.
|
meerschaum/actions/delete.py
CHANGED
@@ -29,6 +29,7 @@ def delete(
|
|
29
29
|
'users' : _delete_users,
|
30
30
|
'connectors' : _delete_connectors,
|
31
31
|
'jobs' : _delete_jobs,
|
32
|
+
'venvs' : _delete_venvs,
|
32
33
|
}
|
33
34
|
return choose_subaction(action, options, **kw)
|
34
35
|
|
@@ -486,6 +487,67 @@ def _delete_jobs(
|
|
486
487
|
)
|
487
488
|
|
488
489
|
|
490
|
+
def _delete_venvs(
|
491
|
+
action: Optional[List[str]] = None,
|
492
|
+
yes: bool = False,
|
493
|
+
force: bool = False,
|
494
|
+
**kwargs: Any
|
495
|
+
) -> SuccessTuple:
|
496
|
+
"""
|
497
|
+
Remove virtual environments.
|
498
|
+
Specify which venvs to remove, or remove everything at once.
|
499
|
+
"""
|
500
|
+
import os
|
501
|
+
import shutil
|
502
|
+
import pathlib
|
503
|
+
from meerschaum.config.paths import VIRTENV_RESOURCES_PATH
|
504
|
+
from meerschaum.utils.venv import venv_exists
|
505
|
+
from meerschaum.utils.prompt import yes_no
|
506
|
+
from meerschaum.utils.misc import print_options
|
507
|
+
from meerschaum.utils.warnings import warn
|
508
|
+
|
509
|
+
venvs_to_skip = ['mrsm']
|
510
|
+
venvs = [
|
511
|
+
_venv
|
512
|
+
for _venv in action or os.listdir(VIRTENV_RESOURCES_PATH)
|
513
|
+
if venv_exists(_venv)
|
514
|
+
and _venv not in venvs_to_skip
|
515
|
+
]
|
516
|
+
|
517
|
+
if not venvs:
|
518
|
+
msg = "No venvs to delete."
|
519
|
+
if action:
|
520
|
+
return False, msg
|
521
|
+
return True, msg
|
522
|
+
|
523
|
+
print_options(
|
524
|
+
venvs,
|
525
|
+
header = 'Venvs to Delete:',
|
526
|
+
**kwargs
|
527
|
+
)
|
528
|
+
confirm_delete = yes_no(
|
529
|
+
(
|
530
|
+
"Remove the above venv" + ('s' if len(venvs) != 1 else '') + "?\n "
|
531
|
+
+ "Run `mrsm upgrade packages` and `mrsm install required` to reinstall dependencies.\n"
|
532
|
+
),
|
533
|
+
yes = yes,
|
534
|
+
default = 'n',
|
535
|
+
force = force,
|
536
|
+
)
|
537
|
+
if not confirm_delete:
|
538
|
+
return True, "Nothing was deleted."
|
539
|
+
|
540
|
+
for venv in venvs:
|
541
|
+
venv_path = pathlib.Path(VIRTENV_RESOURCES_PATH / venv)
|
542
|
+
try:
|
543
|
+
shutil.rmtree(venv_path)
|
544
|
+
except Exception as e:
|
545
|
+
error_msg = f"Failed to remove '{venv_path}':\n{e}"
|
546
|
+
return False, error_msg
|
547
|
+
|
548
|
+
msg = f"Removed {len(venvs)} venv" + ('s' if len(venvs) != 1 else '') + '.'
|
549
|
+
return True, msg
|
550
|
+
|
489
551
|
|
490
552
|
### NOTE: This must be the final statement of the module.
|
491
553
|
### Any subactions added below these lines will not
|
meerschaum/actions/python.py
CHANGED
@@ -10,8 +10,10 @@ from meerschaum.utils.typing import SuccessTuple, Any, List, Optional
|
|
10
10
|
|
11
11
|
def python(
|
12
12
|
action: Optional[List[str]] = None,
|
13
|
-
venv: Optional[str] = 'mrsm',
|
14
13
|
sub_args: Optional[List[str]] = None,
|
14
|
+
nopretty: bool = False,
|
15
|
+
noask: bool = False,
|
16
|
+
venv: Optional[str] = None,
|
15
17
|
debug: bool = False,
|
16
18
|
**kw: Any
|
17
19
|
) -> SuccessTuple:
|
@@ -24,8 +26,22 @@ def python(
|
|
24
26
|
|
25
27
|
Examples:
|
26
28
|
mrsm python
|
27
|
-
mrsm python
|
28
|
-
|
29
|
+
mrsm python [-m pip -V]
|
30
|
+
|
31
|
+
Flags:
|
32
|
+
`--nopretty`
|
33
|
+
Open a plain Pyhthon REPL rather than ptpython.
|
34
|
+
|
35
|
+
`--noask`
|
36
|
+
Run the supplied Python code and do not open a REPL.
|
37
|
+
|
38
|
+
`--venv`
|
39
|
+
Run the Python interpreter from a virtual environment.
|
40
|
+
Will not have Meercshaum imported.
|
41
|
+
|
42
|
+
`--sub-args` (or flags surrounded by `[]`)
|
43
|
+
Rather than run Python code, execute the interpreter with the given sub-flags
|
44
|
+
(e.g. `mrsm python [-V]`)
|
29
45
|
"""
|
30
46
|
import sys, subprocess, os
|
31
47
|
from meerschaum.utils.debug import dprint
|
@@ -35,14 +51,16 @@ def python(
|
|
35
51
|
from meerschaum.config import __version__ as _version
|
36
52
|
from meerschaum.config.paths import VIRTENV_RESOURCES_PATH, PYTHON_RESOURCES_PATH
|
37
53
|
from meerschaum.utils.packages import run_python_package, attempt_import
|
54
|
+
from meerschaum.utils.process import run_process
|
38
55
|
|
39
56
|
if action is None:
|
40
57
|
action = []
|
41
58
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
59
|
+
joined_actions = (
|
60
|
+
["import meerschaum as mrsm"]
|
61
|
+
if venv is None and not sub_args
|
62
|
+
else []
|
63
|
+
)
|
46
64
|
line = ""
|
47
65
|
for i, a in enumerate(action):
|
48
66
|
if a == '':
|
@@ -73,6 +91,11 @@ def python(
|
|
73
91
|
'"""); '
|
74
92
|
+ 'pts.print_formatted_text(ansi); '
|
75
93
|
)
|
94
|
+
if nopretty or venv is not None or sub_args:
|
95
|
+
print_command = ""
|
96
|
+
|
97
|
+
if sub_args:
|
98
|
+
nopretty = True
|
76
99
|
|
77
100
|
command = print_command
|
78
101
|
for a in joined_actions:
|
@@ -88,19 +111,27 @@ def python(
|
|
88
111
|
with open(init_script_path, 'w', encoding='utf-8') as f:
|
89
112
|
f.write(command)
|
90
113
|
|
91
|
-
env_dict = os.environ.copy()
|
92
|
-
venv_path = (VIRTENV_RESOURCES_PATH / venv) if venv is not None else None
|
93
|
-
if venv_path is not None:
|
94
|
-
env_dict.update({'VIRTUAL_ENV': venv_path.as_posix()})
|
95
|
-
|
96
114
|
try:
|
97
|
-
ptpython = attempt_import('ptpython', venv=
|
115
|
+
ptpython = attempt_import('ptpython', venv=None)
|
98
116
|
return_code = run_python_package(
|
99
117
|
'ptpython',
|
100
118
|
sub_args or ['--dark-bg', '-i', init_script_path.as_posix()],
|
119
|
+
foreground = True,
|
101
120
|
venv = venv,
|
121
|
+
) if not nopretty else run_process(
|
122
|
+
(
|
123
|
+
[venv_executable(venv)] + (
|
124
|
+
sub_args or
|
125
|
+
(
|
126
|
+
(
|
127
|
+
['-i']
|
128
|
+
if not noask
|
129
|
+
else []
|
130
|
+
) + [init_script_path.as_posix()]
|
131
|
+
)
|
132
|
+
)
|
133
|
+
),
|
102
134
|
foreground = True,
|
103
|
-
env = env_dict,
|
104
135
|
)
|
105
136
|
except KeyboardInterrupt:
|
106
137
|
return_code = 1
|
meerschaum/actions/show.py
CHANGED
@@ -42,6 +42,7 @@ def show(
|
|
42
42
|
'logs' : _show_logs,
|
43
43
|
'tags' : _show_tags,
|
44
44
|
'schedules' : _show_schedules,
|
45
|
+
'venvs' : _show_venvs,
|
45
46
|
}
|
46
47
|
return choose_subaction(action, show_options, **kw)
|
47
48
|
|
@@ -903,6 +904,31 @@ def _show_schedules(
|
|
903
904
|
return True, "Success"
|
904
905
|
|
905
906
|
|
907
|
+
def _show_venvs(
|
908
|
+
**kwargs: Any
|
909
|
+
):
|
910
|
+
"""
|
911
|
+
Print the available virtual environments in the current MRSM_ROOT_DIR.
|
912
|
+
"""
|
913
|
+
import os
|
914
|
+
import pathlib
|
915
|
+
from meerschaum.config.paths import VIRTENV_RESOURCES_PATH
|
916
|
+
from meerschaum.utils.venv import venv_exists
|
917
|
+
from meerschaum.utils.misc import print_options
|
918
|
+
|
919
|
+
venvs = [
|
920
|
+
_venv
|
921
|
+
for _venv in os.listdir(VIRTENV_RESOURCES_PATH)
|
922
|
+
if venv_exists(_venv)
|
923
|
+
]
|
924
|
+
print_options(
|
925
|
+
venvs,
|
926
|
+
name = 'Venvs:',
|
927
|
+
**kwargs
|
928
|
+
)
|
929
|
+
|
930
|
+
return True, "Success"
|
931
|
+
|
906
932
|
|
907
933
|
### NOTE: This must be the final statement of the module.
|
908
934
|
### Any subactions added below these lines will not
|
meerschaum/api/_oauth2.py
CHANGED
@@ -13,6 +13,23 @@ fastapi = attempt_import('fastapi', lazy=False, check_update=CHECK_UPDATE)
|
|
13
13
|
fastapi_responses = attempt_import('fastapi.responses', lazy=False, check_update=CHECK_UPDATE)
|
14
14
|
fastapi_login = attempt_import('fastapi_login', check_update=CHECK_UPDATE)
|
15
15
|
|
16
|
+
class CustomOAuth2PasswordRequestForm:
|
17
|
+
def __init__(
|
18
|
+
self,
|
19
|
+
grant_type: str = fastapi.Form(None, regex="password|client_credentials"),
|
20
|
+
username: str = fastapi.Form(...),
|
21
|
+
password: str = fastapi.Form(...),
|
22
|
+
scope: str = fastapi.Form(""),
|
23
|
+
client_id: str = fastapi.Form(None),
|
24
|
+
client_secret: str = fastapi.Form(None),
|
25
|
+
):
|
26
|
+
self.grant_type = grant_type
|
27
|
+
self.username = username
|
28
|
+
self.password = password
|
29
|
+
self.scope = scope
|
30
|
+
self.client_id = client_id
|
31
|
+
self.client_secret = client_secret
|
32
|
+
|
16
33
|
LoginManager = fastapi_login.LoginManager
|
17
34
|
def generate_secret_key() -> str:
|
18
35
|
"""
|
meerschaum/api/routes/_login.py
CHANGED
@@ -8,13 +8,17 @@ Manage access and refresh tokens.
|
|
8
8
|
|
9
9
|
from datetime import datetime, timedelta, timezone
|
10
10
|
import fastapi
|
11
|
+
from fastapi import Request, status
|
11
12
|
from fastapi_login.exceptions import InvalidCredentialsException
|
12
|
-
from fastapi.
|
13
|
+
from fastapi.exceptions import RequestValidationError
|
13
14
|
from starlette.responses import Response, JSONResponse
|
14
15
|
from meerschaum.api import endpoints, get_api_connector, app, debug, manager, no_auth
|
15
16
|
from meerschaum.core import User
|
16
17
|
from meerschaum.config.static import STATIC_CONFIG
|
17
|
-
from meerschaum.utils.typing import Dict, Any
|
18
|
+
from meerschaum.utils.typing import Dict, Any, Optional
|
19
|
+
from meerschaum.core.User._User import verify_password
|
20
|
+
from meerschaum.utils.warnings import warn
|
21
|
+
from meerschaum.api._oauth2 import CustomOAuth2PasswordRequestForm
|
18
22
|
|
19
23
|
|
20
24
|
@manager.user_loader()
|
@@ -28,18 +32,18 @@ def load_user(
|
|
28
32
|
|
29
33
|
|
30
34
|
@app.post(endpoints['login'], tags=['Users'])
|
31
|
-
def login(
|
32
|
-
data:
|
35
|
+
async def login(
|
36
|
+
data: CustomOAuth2PasswordRequestForm = fastapi.Depends()
|
37
|
+
# data: dict[str, str],
|
38
|
+
# request: Request
|
33
39
|
) -> Dict[str, Any]:
|
34
40
|
"""
|
35
41
|
Login and set the session token.
|
36
42
|
"""
|
37
43
|
username, password = (
|
38
|
-
(data
|
39
|
-
else (data.username, data.password)
|
44
|
+
(data.username, data.password)
|
40
45
|
) if not no_auth else ('no-auth', 'no-auth')
|
41
46
|
|
42
|
-
from meerschaum.core.User._User import verify_password
|
43
47
|
user = User(username, password)
|
44
48
|
correct_password = no_auth or verify_password(
|
45
49
|
password,
|
@@ -60,3 +64,15 @@ def login(
|
|
60
64
|
'token_type': 'bearer',
|
61
65
|
'expires' : expires_dt,
|
62
66
|
}
|
67
|
+
|
68
|
+
|
69
|
+
@app.exception_handler(RequestValidationError)
|
70
|
+
async def validation_exception_handler(request: Request, exc: RequestValidationError):
|
71
|
+
"""
|
72
|
+
Log validation errors as warnings.
|
73
|
+
"""
|
74
|
+
warn(f"Validation error: {exc.errors()}", stack=False)
|
75
|
+
return JSONResponse(
|
76
|
+
status_code = status.HTTP_422_UNPROCESSABLE_ENTITY,
|
77
|
+
content = {"detail": exc.errors()},
|
78
|
+
)
|
meerschaum/config/_edit.py
CHANGED
@@ -171,7 +171,7 @@ def general_write_yaml_config(
|
|
171
171
|
path = pathlib.Path(fp)
|
172
172
|
path.parent.mkdir(parents=True, exist_ok=True)
|
173
173
|
path.touch(exist_ok=True)
|
174
|
-
with open(path, 'w+') as f:
|
174
|
+
with open(path, 'w+', encoding='utf-8') as f:
|
175
175
|
if header is not None:
|
176
176
|
if debug:
|
177
177
|
dprint(f"Header detected, writing to {path}...")
|
meerschaum/config/_paths.py
CHANGED
@@ -138,6 +138,9 @@ paths = {
|
|
138
138
|
'SQLITE_RESOURCES_PATH' : ('{ROOT_DIR_PATH}', 'sqlite'),
|
139
139
|
'SQLITE_DB_PATH' : ('{SQLITE_RESOURCES_PATH}', 'mrsm_local.db'),
|
140
140
|
|
141
|
+
'BACKUP_RESOURCES_PATH' : ('{ROOT_DIR_PATH}', 'backup'),
|
142
|
+
'BACKUP_DATABASE_PATH' : ('{BACKUP_RESOURCES_PATH}', 'backup.sql'),
|
143
|
+
|
141
144
|
'DUCKDB_RESOURCES_PATH' : ('{ROOT_DIR_PATH}', 'duckdb'),
|
142
145
|
'DUCKDB_PATH' : ('{DUCKDB_RESOURCES_PATH}', 'duck.db'),
|
143
146
|
|
meerschaum/config/_version.py
CHANGED
@@ -56,6 +56,7 @@ env_dict['MEERSCHAUM_API_CONFIG'] = json.dumps(
|
|
56
56
|
|
57
57
|
volumes = {
|
58
58
|
'api_root': '/meerschaum',
|
59
|
+
'api_user_local': '/home/meerschaum/.local',
|
59
60
|
'meerschaum_db_data': '/var/lib/postgresql/data',
|
60
61
|
'grafana_storage': '/var/lib/grafana',
|
61
62
|
}
|
@@ -122,7 +123,7 @@ default_docker_compose_config = {
|
|
122
123
|
],
|
123
124
|
'hostname' : f'{db_hostname}',
|
124
125
|
'volumes' : [
|
125
|
-
'meerschaum_db_data
|
126
|
+
'meerschaum_db_data:' + volumes['meerschaum_db_data'],
|
126
127
|
],
|
127
128
|
'shm_size': '1024m',
|
128
129
|
'networks' : [
|
@@ -159,6 +160,7 @@ default_docker_compose_config = {
|
|
159
160
|
},
|
160
161
|
'volumes' : [
|
161
162
|
'api_root:' + volumes['api_root'],
|
163
|
+
'api_user_local:' + volumes['api_user_local'],
|
162
164
|
],
|
163
165
|
},
|
164
166
|
'grafana': {
|
@@ -185,7 +185,7 @@ class Daemon:
|
|
185
185
|
result = self.target(*self.target_args, **self.target_kw)
|
186
186
|
self.properties['result'] = result
|
187
187
|
except Exception as e:
|
188
|
-
warn(e, stacklevel=3)
|
188
|
+
warn(f"Exception in daemon target function: {e}", stacklevel=3)
|
189
189
|
result = e
|
190
190
|
finally:
|
191
191
|
self._log_refresh_timer.cancel()
|
@@ -203,9 +203,20 @@ class Daemon:
|
|
203
203
|
daemon_error = traceback.format_exc()
|
204
204
|
with open(DAEMON_ERROR_LOG_PATH, 'a+', encoding='utf-8') as f:
|
205
205
|
f.write(daemon_error)
|
206
|
+
warn(f"Encountered an error while running the daemon '{self}':\n{daemon_error}")
|
207
|
+
finally:
|
208
|
+
self._cleanup_on_exit()
|
206
209
|
|
207
|
-
|
208
|
-
|
210
|
+
def _cleanup_on_exit(self):
|
211
|
+
"""Perform cleanup operations when the daemon exits."""
|
212
|
+
try:
|
213
|
+
self._log_refresh_timer.cancel()
|
214
|
+
self.rotating_log.close()
|
215
|
+
if self.pid is None and self.pid_path.exists():
|
216
|
+
self.pid_path.unlink()
|
217
|
+
self._capture_process_timestamp('ended')
|
218
|
+
except Exception as e:
|
219
|
+
warn(f"Error during daemon cleanup: {e}")
|
209
220
|
|
210
221
|
|
211
222
|
def _capture_process_timestamp(
|
@@ -65,24 +65,31 @@ class FileDescriptorInterceptor:
|
|
65
65
|
except BlockingIOError:
|
66
66
|
continue
|
67
67
|
except OSError as e:
|
68
|
-
|
68
|
+
from meerschaum.utils.warnings import warn
|
69
|
+
warn(f"OSError in FileDescriptorInterceptor: {e}")
|
70
|
+
break
|
69
71
|
|
70
|
-
|
71
|
-
|
72
|
+
try:
|
73
|
+
first_char_is_newline = data[0] == b'\n'
|
74
|
+
last_char_is_newline = data[-1] == b'\n'
|
72
75
|
|
73
|
-
|
74
|
-
|
76
|
+
injected_str = self.injection_hook()
|
77
|
+
injected_bytes = injected_str.encode('utf-8')
|
75
78
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
+
if is_first_read:
|
80
|
+
data = b'\n' + data
|
81
|
+
is_first_read = False
|
79
82
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
83
|
+
modified_data = (
|
84
|
+
(data[:-1].replace(b'\n', b'\n' + injected_bytes) + b'\n')
|
85
|
+
if last_char_is_newline
|
86
|
+
else data.replace(b'\n', b'\n' + injected_bytes)
|
87
|
+
)
|
88
|
+
os.write(self.new_file_descriptor, modified_data)
|
89
|
+
except Exception as e:
|
90
|
+
from meerschaum.utils.warnings import warn
|
91
|
+
warn(f"Error in FileDescriptorInterceptor data processing: {e}")
|
92
|
+
break
|
86
93
|
|
87
94
|
|
88
95
|
def stop_interception(self):
|
@@ -355,26 +355,29 @@ class RotatingFile(io.IOBase):
|
|
355
355
|
As such, if data is larger than max_file_size, then the corresponding subfile
|
356
356
|
may exceed this limit.
|
357
357
|
"""
|
358
|
-
self.file_path.parent.mkdir(exist_ok=True, parents=True)
|
359
|
-
if isinstance(data, bytes):
|
360
|
-
data = data.decode('utf-8')
|
361
|
-
|
362
|
-
prefix_str = self.get_timestamp_prefix_str() if self.write_timestamps else ""
|
363
|
-
suffix_str = "\n" if self.write_timestamps else ""
|
364
|
-
self.refresh_files(
|
365
|
-
potential_new_len = len(prefix_str + data + suffix_str),
|
366
|
-
start_interception = self.write_timestamps,
|
367
|
-
)
|
368
358
|
try:
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
359
|
+
self.file_path.parent.mkdir(exist_ok=True, parents=True)
|
360
|
+
if isinstance(data, bytes):
|
361
|
+
data = data.decode('utf-8')
|
362
|
+
|
363
|
+
prefix_str = self.get_timestamp_prefix_str() if self.write_timestamps else ""
|
364
|
+
suffix_str = "\n" if self.write_timestamps else ""
|
365
|
+
self.refresh_files(
|
366
|
+
potential_new_len = len(prefix_str + data + suffix_str),
|
367
|
+
start_interception = self.write_timestamps,
|
368
|
+
)
|
369
|
+
try:
|
370
|
+
if prefix_str:
|
371
|
+
self._current_file_obj.write(prefix_str)
|
372
|
+
self._current_file_obj.write(data)
|
373
|
+
if suffix_str:
|
374
|
+
self._current_file_obj.write(suffix_str)
|
375
|
+
except Exception as e:
|
376
|
+
warn(f"Failed to write to subfile:\n{traceback.format_exc()}")
|
377
|
+
self.flush()
|
378
|
+
self.delete(unused_only=True)
|
374
379
|
except Exception as e:
|
375
|
-
warn(f"
|
376
|
-
self.flush()
|
377
|
-
self.delete(unused_only=True)
|
380
|
+
warn(f"Unexpected error in RotatingFile.write: {e}")
|
378
381
|
|
379
382
|
|
380
383
|
def delete(self, unused_only: bool = False) -> None:
|
@@ -42,6 +42,7 @@ packages: Dict[str, Dict[str, str]] = {
|
|
42
42
|
'requests' : 'requests>=2.23.0',
|
43
43
|
'binaryornot' : 'binaryornot>=0.4.4',
|
44
44
|
'pyvim' : 'pyvim>=3.0.2',
|
45
|
+
'ptpython' : 'ptpython>=3.0.27',
|
45
46
|
'aiofiles' : 'aiofiles>=0.6.0',
|
46
47
|
'packaging' : 'packaging>=21.3.0',
|
47
48
|
'prompt_toolkit' : 'prompt-toolkit>=3.0.39',
|
meerschaum/utils/prompt.py
CHANGED
@@ -61,6 +61,7 @@ def prompt(
|
|
61
61
|
from meerschaum.utils.formatting import colored, ANSI, CHARSET, highlight_pipes, fill_ansi
|
62
62
|
from meerschaum.config import get_config
|
63
63
|
from meerschaum.config.static import _static_config
|
64
|
+
from meerschaum.utils.misc import filter_keywords
|
64
65
|
noask = check_noask(noask)
|
65
66
|
if not noask:
|
66
67
|
prompt_toolkit = attempt_import('prompt_toolkit')
|
@@ -101,7 +102,7 @@ def prompt(
|
|
101
102
|
prompt_toolkit.prompt(
|
102
103
|
prompt_toolkit.formatted_text.ANSI(question),
|
103
104
|
wrap_lines = wrap_lines,
|
104
|
-
**kw
|
105
|
+
**filter_keywords(prompt_toolkit.prompt, **kw)
|
105
106
|
) if not noask else ''
|
106
107
|
)
|
107
108
|
if noask:
|
@@ -192,10 +193,11 @@ def yes_no(
|
|
192
193
|
|
193
194
|
def choose(
|
194
195
|
question: str,
|
195
|
-
choices: List[str],
|
196
|
-
default:
|
196
|
+
choices: List[Union[str, Tuple[str, str]]],
|
197
|
+
default: Union[str, List[str], None] = None,
|
197
198
|
numeric: bool = True,
|
198
199
|
multiple: bool = False,
|
200
|
+
as_indices: bool = False,
|
199
201
|
delimiter: str = ',',
|
200
202
|
icon: bool = True,
|
201
203
|
warn: bool = True,
|
@@ -210,10 +212,12 @@ def choose(
|
|
210
212
|
question: str
|
211
213
|
The question to be printed.
|
212
214
|
|
213
|
-
choices: List[str]
|
215
|
+
choices: List[Union[str, Tuple[str, str]]
|
214
216
|
A list of options.
|
217
|
+
If an option is a tuple of two strings, the first string is treated as the index
|
218
|
+
and not displayed. In this case, set `as_indices` to `True` to return the index.
|
215
219
|
|
216
|
-
default:
|
220
|
+
default: Union[str, List[str], None], default None
|
217
221
|
If the user declines to enter a choice, return this value.
|
218
222
|
|
219
223
|
numeric: bool, default True
|
@@ -223,6 +227,11 @@ def choose(
|
|
223
227
|
multiple: bool, default False
|
224
228
|
If `True`, allow the user to choose multiple answers separated by `delimiter`.
|
225
229
|
|
230
|
+
as_indices: bool, default False
|
231
|
+
If `True`, return the indices for the choices.
|
232
|
+
If a choice is a tuple of two strings, the first is assumed to be the index.
|
233
|
+
Otherwise the index in the list is returned.
|
234
|
+
|
226
235
|
delimiter: str, default ','
|
227
236
|
If `multiple`, separate answers by this string. Raise a warning if this string is contained
|
228
237
|
in any of the choices.
|
@@ -254,8 +263,14 @@ def choose(
|
|
254
263
|
if isinstance(default, list):
|
255
264
|
multiple = True
|
256
265
|
|
266
|
+
choices_indices = {}
|
267
|
+
for i, c in enumerate(choices):
|
268
|
+
if isinstance(c, tuple):
|
269
|
+
i, c = c
|
270
|
+
choices_indices[i] = c
|
271
|
+
|
257
272
|
def _enforce_default(d):
|
258
|
-
if d is not None and d not in choices and warn:
|
273
|
+
if d is not None and d not in choices and d not in choices_indices and warn:
|
259
274
|
_warn(
|
260
275
|
f"Default choice '{default}' is not contained in the choices {choices}. "
|
261
276
|
+ "Setting numeric = False.",
|
@@ -271,16 +286,18 @@ def choose(
|
|
271
286
|
break
|
272
287
|
|
273
288
|
_default = default
|
274
|
-
_choices =
|
289
|
+
_choices = list(choices_indices.values())
|
275
290
|
if multiple:
|
276
|
-
question += f"\n Enter your choices, separated by '{delimiter}'
|
291
|
+
question += f"\n Enter your choices, separated by '{delimiter}'.\n"
|
277
292
|
|
278
293
|
altered_choices = {}
|
279
294
|
altered_indices = {}
|
280
295
|
altered_default_indices = {}
|
281
296
|
delim_replacement = '_' if delimiter != '_' else '-'
|
282
297
|
can_strip_start_spaces, can_strip_end_spaces = True, True
|
283
|
-
for c in
|
298
|
+
for i, c in choices_indices.items():
|
299
|
+
if isinstance(c, tuple):
|
300
|
+
key, c = c
|
284
301
|
if can_strip_start_spaces and c.startswith(' '):
|
285
302
|
can_strip_start_spaces = False
|
286
303
|
if can_strip_end_spaces and c.endswith(' '):
|
@@ -301,8 +318,8 @@ def choose(
|
|
301
318
|
default[i] = new_d
|
302
319
|
|
303
320
|
### Check if the choices have the delimiter.
|
304
|
-
for i, c in
|
305
|
-
if delimiter in c and warn:
|
321
|
+
for i, c in choices_indices.items():
|
322
|
+
if delimiter in c and not numeric and warn:
|
306
323
|
_warn(
|
307
324
|
f"The delimiter '{delimiter}' is contained within choice '{c}'.\n"
|
308
325
|
+ f"Replacing the string '{delimiter}' with '{delim_replacement}' in "
|
@@ -313,7 +330,7 @@ def choose(
|
|
313
330
|
altered_choices[new_c] = c
|
314
331
|
altered_indices[i] = new_c
|
315
332
|
for i, new_c in altered_indices.items():
|
316
|
-
|
333
|
+
choices_indices[i] = new_c
|
317
334
|
default = delimiter.join(default) if isinstance(default, list) else default
|
318
335
|
|
319
336
|
if numeric:
|
@@ -321,23 +338,36 @@ def choose(
|
|
321
338
|
_default = ''
|
322
339
|
if default is not None:
|
323
340
|
for d in (default.split(delimiter) if multiple else [default]):
|
341
|
+
if d not in choices and d in choices_indices:
|
342
|
+
d_index = d
|
343
|
+
d_value = choices_indices[d]
|
344
|
+
for _i, _option in enumerate(choices):
|
345
|
+
if (
|
346
|
+
isinstance(_option, tuple) and (
|
347
|
+
_option[1] == d_value
|
348
|
+
or
|
349
|
+
_option[0] == d_index
|
350
|
+
)
|
351
|
+
) or d_index == _i:
|
352
|
+
d = _option
|
353
|
+
|
324
354
|
_d = str(choices.index(d) + 1)
|
325
355
|
_default += _d + delimiter
|
326
356
|
_default = _default[:-1 * len(delimiter)]
|
327
357
|
question += '\n'
|
328
358
|
choices_digits = len(str(len(choices)))
|
329
|
-
for i, c in enumerate(
|
359
|
+
for i, c in enumerate(choices_indices.values()):
|
330
360
|
question += f" {i + 1}. " + (" " * (choices_digits - len(str(i + 1)))) + f"{c}\n"
|
331
361
|
default_tuple = (_default, default) if default is not None else None
|
332
362
|
else:
|
333
363
|
default_tuple = default
|
334
364
|
question += '\n'
|
335
|
-
for c in
|
365
|
+
for c in choices_indices.values():
|
336
366
|
question += f" - {c}\n"
|
337
367
|
|
338
368
|
if 'completer' not in kw:
|
339
369
|
WordCompleter = attempt_import('prompt_toolkit.completion').WordCompleter
|
340
|
-
kw['completer'] = WordCompleter(
|
370
|
+
kw['completer'] = WordCompleter(choices_indices.values(), sentence=True)
|
341
371
|
|
342
372
|
valid = False
|
343
373
|
while not valid:
|
@@ -383,7 +413,10 @@ def choose(
|
|
383
413
|
if not numeric:
|
384
414
|
return answer
|
385
415
|
try:
|
386
|
-
|
416
|
+
_answer = choices[int(answer) - 1]
|
417
|
+
if as_indices and isinstance(choice, tuple):
|
418
|
+
return _answer[0]
|
419
|
+
return _answer
|
387
420
|
except Exception as e:
|
388
421
|
_warn(f"Could not cast answer '{answer}' to an integer.", stacklevel=3)
|
389
422
|
|
@@ -393,7 +426,10 @@ def choose(
|
|
393
426
|
for a in answers:
|
394
427
|
try:
|
395
428
|
_answer = choices[int(a) - 1]
|
396
|
-
|
429
|
+
_answer_to_return = altered_choices.get(_answer, _answer)
|
430
|
+
if isinstance(_answer_to_return, tuple) and as_indices:
|
431
|
+
_answer_to_return = _answer_to_return[0]
|
432
|
+
_answers.append(_answer_to_return)
|
397
433
|
except Exception as e:
|
398
434
|
_warn(f"Could not cast answer '{a}' to an integer.", stacklevel=3)
|
399
435
|
return _answers
|
meerschaum/utils/yaml.py
CHANGED
@@ -18,6 +18,7 @@ _lib = None
|
|
18
18
|
_import_name = 'yaml'
|
19
19
|
|
20
20
|
_yaml = None
|
21
|
+
_dumper = None
|
21
22
|
_locks = {
|
22
23
|
'_lib': Lock(),
|
23
24
|
'_yaml': Lock(),
|
@@ -46,7 +47,7 @@ class yaml:
|
|
46
47
|
"""
|
47
48
|
Wrapper around `PyYAML` and `ruamel.yaml` so that we may switch between implementations.
|
48
49
|
"""
|
49
|
-
global _yaml, _lib
|
50
|
+
global _yaml, _lib, _dumper
|
50
51
|
if _import_name is None:
|
51
52
|
error(f"No YAML library declared.")
|
52
53
|
with _locks['_lib']:
|
@@ -85,6 +86,7 @@ class yaml:
|
|
85
86
|
and packaging_version.parse(_yaml.__version__) >= packaging_version.parse('6.0')
|
86
87
|
):
|
87
88
|
_args += [_yaml.Loader]
|
89
|
+
|
88
90
|
return _yaml.load(*_args, **filter_keywords(_yaml.load, **kw))
|
89
91
|
|
90
92
|
|
@@ -98,7 +100,36 @@ class yaml:
|
|
98
100
|
if stream is None and _import_name == 'ruamel.yaml':
|
99
101
|
stream = _lib.compat.StringIO()
|
100
102
|
get_string = True
|
103
|
+
|
104
|
+
if _import_name == 'yaml' and 'Dumper' not in kw:
|
105
|
+
kw['Dumper'] = get_dumper_class()
|
106
|
+
|
101
107
|
result = _yaml.dump(data, stream, **filter_keywords(_yaml.dump, **kw))
|
102
108
|
if get_string:
|
103
109
|
return stream.getvalue()
|
104
110
|
return result
|
111
|
+
|
112
|
+
|
113
|
+
def get_dumper_class():
|
114
|
+
"""
|
115
|
+
Return the dumper class to use when writing.
|
116
|
+
Only supports `yaml`.
|
117
|
+
"""
|
118
|
+
global _dumper
|
119
|
+
if _dumper is not None:
|
120
|
+
return _dumper
|
121
|
+
|
122
|
+
if _import_name != 'yaml':
|
123
|
+
return None
|
124
|
+
|
125
|
+
class CustomDumper(_yaml.Dumper):
|
126
|
+
"""
|
127
|
+
Add an extra line break when writing.
|
128
|
+
"""
|
129
|
+
def write_line_break(self, data=None):
|
130
|
+
if len(self.indents) == 1:
|
131
|
+
super(CustomDumper, self).write_line_break(data)
|
132
|
+
super(CustomDumper, self).write_line_break(data)
|
133
|
+
|
134
|
+
_dumper = CustomDumper
|
135
|
+
return _dumper
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: meerschaum
|
3
|
-
Version: 2.2.
|
3
|
+
Version: 2.2.5.dev0
|
4
4
|
Summary: Sync Time-Series Pipes with Meerschaum
|
5
5
|
Home-page: https://meerschaum.io
|
6
6
|
Author: Bennett Meares
|
@@ -68,6 +68,7 @@ Requires-Dist: python-dateutil >=2.7.5 ; extra == 'api'
|
|
68
68
|
Requires-Dist: requests >=2.23.0 ; extra == 'api'
|
69
69
|
Requires-Dist: binaryornot >=0.4.4 ; extra == 'api'
|
70
70
|
Requires-Dist: pyvim >=3.0.2 ; extra == 'api'
|
71
|
+
Requires-Dist: ptpython >=3.0.27 ; extra == 'api'
|
71
72
|
Requires-Dist: aiofiles >=0.6.0 ; extra == 'api'
|
72
73
|
Requires-Dist: packaging >=21.3.0 ; extra == 'api'
|
73
74
|
Requires-Dist: prompt-toolkit >=3.0.39 ; extra == 'api'
|
@@ -117,6 +118,7 @@ Requires-Dist: python-dateutil >=2.7.5 ; extra == 'core'
|
|
117
118
|
Requires-Dist: requests >=2.23.0 ; extra == 'core'
|
118
119
|
Requires-Dist: binaryornot >=0.4.4 ; extra == 'core'
|
119
120
|
Requires-Dist: pyvim >=3.0.2 ; extra == 'core'
|
121
|
+
Requires-Dist: ptpython >=3.0.27 ; extra == 'core'
|
120
122
|
Requires-Dist: aiofiles >=0.6.0 ; extra == 'core'
|
121
123
|
Requires-Dist: packaging >=21.3.0 ; extra == 'core'
|
122
124
|
Requires-Dist: prompt-toolkit >=3.0.39 ; extra == 'core'
|
@@ -203,6 +205,7 @@ Requires-Dist: python-dateutil >=2.7.5 ; extra == 'full'
|
|
203
205
|
Requires-Dist: requests >=2.23.0 ; extra == 'full'
|
204
206
|
Requires-Dist: binaryornot >=0.4.4 ; extra == 'full'
|
205
207
|
Requires-Dist: pyvim >=3.0.2 ; extra == 'full'
|
208
|
+
Requires-Dist: ptpython >=3.0.27 ; extra == 'full'
|
206
209
|
Requires-Dist: aiofiles >=0.6.0 ; extra == 'full'
|
207
210
|
Requires-Dist: packaging >=21.3.0 ; extra == 'full'
|
208
211
|
Requires-Dist: prompt-toolkit >=3.0.39 ; extra == 'full'
|
@@ -287,6 +290,7 @@ Requires-Dist: python-dateutil >=2.7.5 ; extra == 'sql'
|
|
287
290
|
Requires-Dist: requests >=2.23.0 ; extra == 'sql'
|
288
291
|
Requires-Dist: binaryornot >=0.4.4 ; extra == 'sql'
|
289
292
|
Requires-Dist: pyvim >=3.0.2 ; extra == 'sql'
|
293
|
+
Requires-Dist: ptpython >=3.0.27 ; extra == 'sql'
|
290
294
|
Requires-Dist: aiofiles >=0.6.0 ; extra == 'sql'
|
291
295
|
Requires-Dist: packaging >=21.3.0 ; extra == 'sql'
|
292
296
|
Requires-Dist: prompt-toolkit >=3.0.39 ; extra == 'sql'
|
@@ -1,7 +1,7 @@
|
|
1
1
|
meerschaum/__init__.py,sha256=mw_PhxT7155SW8d_S51T4-WTibL5g205CwMa3qX8qyA,1487
|
2
2
|
meerschaum/__main__.py,sha256=Q43yra6APe5CdhDp9ngaorUjs8ZoabeYnMa6C5kQchA,2763
|
3
3
|
meerschaum/_internal/__init__.py,sha256=ilC7utfKtin7GAvuN34fKyUQYfPyqH0Mm3MJF5iyEf4,169
|
4
|
-
meerschaum/_internal/entry.py,sha256=
|
4
|
+
meerschaum/_internal/entry.py,sha256=TNqt79kfNgXH-7Pi75Jus4Osi9y776ZVptFuvrs4wWY,5680
|
5
5
|
meerschaum/_internal/arguments/__init__.py,sha256=HFciFQgo2ZOT19Mo6CpLhPYlpLYh2sNn1C9Lo7NMADc,519
|
6
6
|
meerschaum/_internal/arguments/_parse_arguments.py,sha256=dNVDBvXYNgEw-TrlZJ9A6VPlG696EpQcQm6FOAhseqw,10249
|
7
7
|
meerschaum/_internal/arguments/_parser.py,sha256=OtcrZK-_qV9a5qpdcP9NLKOGRevjmCU9fBLbB88px3c,13719
|
@@ -20,25 +20,26 @@ meerschaum/_internal/shell/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeu
|
|
20
20
|
meerschaum/_internal/term/TermPageHandler.py,sha256=Rt5S47Pr_3HLJc8xIXpZUczYE_Dw2qT8qwf1jZFtUHQ,520
|
21
21
|
meerschaum/_internal/term/__init__.py,sha256=LqTySBfHYnOtHGYCWfovcxV4FnZdON2BPYp0UbgI1QI,1640
|
22
22
|
meerschaum/_internal/term/tools.py,sha256=dXVAimKD-Yv2fg2WOTr0YGBY7XDKjQqw-RizcS65YVI,727
|
23
|
-
meerschaum/actions/__init__.py,sha256=
|
23
|
+
meerschaum/actions/__init__.py,sha256=ZUdQk6FvuMqyEvLCP-RiDDlIK4NwkqLRgFZSKq04qq4,11440
|
24
24
|
meerschaum/actions/api.py,sha256=mWhv4bn3Ap17_Gqf2Cx9bAsHKG-Zhy072pBbNzHLEJc,12756
|
25
|
-
meerschaum/actions/
|
25
|
+
meerschaum/actions/backup.py,sha256=MhdNEo7ptOdFpR-L0o9Z590hBmKeqHsSx64GPKhx9e8,1230
|
26
|
+
meerschaum/actions/bootstrap.py,sha256=dWUmvagqRwBI22PLsTX9PN3dY6ibvBxOXhTdxImRqAE,22530
|
26
27
|
meerschaum/actions/clear.py,sha256=OoFZE0bK5m8s3GLNZcixuVT0DMj1izXVxGCATcmUGbI,4851
|
27
28
|
meerschaum/actions/copy.py,sha256=8g3ANXfVdvuyaoXcZjgTg3BxHTOhHGrzVDOOsTBrpSU,6213
|
28
29
|
meerschaum/actions/deduplicate.py,sha256=puYyxeFYEUy1Sd2IOcZB2e6MrNxAZl2bTLmNzFDkCiw,1167
|
29
|
-
meerschaum/actions/delete.py,sha256=
|
30
|
+
meerschaum/actions/delete.py,sha256=RDkIQEnnN8t52dMAJpxoQ7LN-pzH7UAfGF8yT5oNiIw,17325
|
30
31
|
meerschaum/actions/drop.py,sha256=Hd5h4rrWd7qL2rTqglsTonUsEoH7qQlsfqNFSHGeqr0,2453
|
31
32
|
meerschaum/actions/edit.py,sha256=dHfqVtc1v6UUT_GEhWQkgoUf1nMbX_LLOETLGR5cCXQ,9487
|
32
33
|
meerschaum/actions/install.py,sha256=akzzgsvy5yqUFuUqzSMG4eBKARY2iSnL3n_BiaNcM58,7431
|
33
34
|
meerschaum/actions/login.py,sha256=fNgsgkrFCn9wBQJY50SQhz2PwsN_TvEYYHnXK3JG4ig,4206
|
34
35
|
meerschaum/actions/os.py,sha256=dtoppoBhLzW3rLNF0SFovEfNxA4WJWt_9WrOGlS5KbA,2251
|
35
36
|
meerschaum/actions/pause.py,sha256=kDK0UMm90TuohFEG5Gugl3PEbuqGua-ghidqvgYShoc,3909
|
36
|
-
meerschaum/actions/python.py,sha256=
|
37
|
+
meerschaum/actions/python.py,sha256=hOtA_opi6ulF71XP5p0V5zXsLD7CCdSFWhiBanFevtw,4539
|
37
38
|
meerschaum/actions/register.py,sha256=l_21LWZCzKwJVex_xAXECC5WVW1VEuIX9HSp7CuyCwA,11326
|
38
39
|
meerschaum/actions/reload.py,sha256=gMXeFBGVfyQ7uhKhYf6bLaDMD0fLPcA9BrLBSiuvWIc,508
|
39
40
|
meerschaum/actions/setup.py,sha256=KkAGWcgwzl_L6A19fTmTX1KtBjW2FwD8QenLjPy0mQQ,3205
|
40
41
|
meerschaum/actions/sh.py,sha256=fLfTJaacKu4sjLTRqEzzYlT2WbbdZBEczsKb6F-qAek,2026
|
41
|
-
meerschaum/actions/show.py,sha256=
|
42
|
+
meerschaum/actions/show.py,sha256=S6J8bM2Jc9mzi31Dp_qqZLVqsRswPemWYzQc4wSzSr4,29631
|
42
43
|
meerschaum/actions/sql.py,sha256=wYofwk1vGO96U2ncigGEfMtYMZeprz2FR1PRRZhkAPI,4311
|
43
44
|
meerschaum/actions/stack.py,sha256=WMRMebyYwZGNlbnj6Ja09qvCSDNteFJOTa8_joHlnVo,5886
|
44
45
|
meerschaum/actions/start.py,sha256=9Ej3Hk7qXfbrBaQq17KirTII_4Pa-BWSdrkutMnLA3k,18352
|
@@ -51,7 +52,7 @@ meerschaum/actions/verify.py,sha256=tY5slGpHiWiE0v9TDnjbmxSKn86zBnu9WBpixUgKNQU,
|
|
51
52
|
meerschaum/api/__init__.py,sha256=oOYwNnkXM2s-h52EYnN2NGPfrlaQh6D4-zmHa7uPLG0,7450
|
52
53
|
meerschaum/api/_chain.py,sha256=h8-WXUGXX6AqzdALfsBC5uv0FkAcLdHJXCGzqzuq89k,875
|
53
54
|
meerschaum/api/_events.py,sha256=NrjiabEr7rmHMfxnX07DOGzr9sPiEbRkFqPjuA_8Zx8,1603
|
54
|
-
meerschaum/api/_oauth2.py,sha256=
|
55
|
+
meerschaum/api/_oauth2.py,sha256=o4ub9gMLvJ5ewxuCbk78kA8c6MOHV4FDxURupH1lT7U,1645
|
55
56
|
meerschaum/api/_websockets.py,sha256=OmrSDGx3xGlfP5XXeLEyYW6-Y3iRUcB-xSqL3RWsjqQ,1609
|
56
57
|
meerschaum/api/dash/__init__.py,sha256=yr4zR7uFPlLFc_lDpwa2rCM1h9ZWGiu5-2XhHcM-5f8,2252
|
57
58
|
meerschaum/api/dash/actions.py,sha256=eUClPPdNVNSCtyq8Ecr1saasxj5VBgd1gcXejvQxXEc,9418
|
@@ -115,7 +116,7 @@ meerschaum/api/routes/__init__.py,sha256=vEXVwhvhVlGf0SFBjjCpQiYM4mMoxH3oywBgVOa
|
|
115
116
|
meerschaum/api/routes/_actions.py,sha256=nPLJlhnl0kDjsjFiBmUub63fe1N1_4cVimmlxtInsK0,2240
|
116
117
|
meerschaum/api/routes/_connectors.py,sha256=NNbcn5xWhKqw2PqueSEaqRaZ95hFGDKazG5lE7gsssc,1849
|
117
118
|
meerschaum/api/routes/_index.py,sha256=QI6CBo6pI2Zi0a6fJHDjZfiLa9f4okb0BGe3A_JD0kM,578
|
118
|
-
meerschaum/api/routes/_login.py,sha256=
|
119
|
+
meerschaum/api/routes/_login.py,sha256=EozEq592WA81x5VI8WU4-4S2w4C-w2oL45UZFbETzzw,2466
|
119
120
|
meerschaum/api/routes/_misc.py,sha256=05--9ZVFeaCgZrHER2kA3SYdK4TyfkEXOCjLvPbum-w,2469
|
120
121
|
meerschaum/api/routes/_pipes.py,sha256=d1JCu334xqjzvK1r2NzBxFqYScLKDATUjGWZkrjLTKU,21677
|
121
122
|
meerschaum/api/routes/_plugins.py,sha256=vR6-uTJraY1YEJMuRvds1-xFLB2mexxnp2dJwN_0rVo,6216
|
@@ -126,20 +127,20 @@ meerschaum/api/tables/__init__.py,sha256=e2aNC0CdlWICTUMx1i9RauF8Pm426J0RZJbsJWv
|
|
126
127
|
meerschaum/config/__init__.py,sha256=01hZ4Z8jGs4LbcIwNTeVzSvHD-Y0nAgiewRgngVUYb4,11517
|
127
128
|
meerschaum/config/_dash.py,sha256=BJHl4xMrQB-YHUEU7ldEW8q_nOPoIRSOqLrfGElc6Dw,187
|
128
129
|
meerschaum/config/_default.py,sha256=J6AWxxXM4mntCdkM6bKIpyQznZ5NuMICmTaurF11J9Q,5275
|
129
|
-
meerschaum/config/_edit.py,sha256=
|
130
|
+
meerschaum/config/_edit.py,sha256=_kabgFbJdI5kcLs-JIsoaTo0JdyxnPnBdrlTyTAgPm8,8236
|
130
131
|
meerschaum/config/_environment.py,sha256=Vv4DLDfc2vKLbCLsMvkQDj77K4kEvHKEBmUBo-wCrgo,4419
|
131
132
|
meerschaum/config/_formatting.py,sha256=RT_oU0OSfUkzeqbY5jvEJwuove_t9sP4MyUb1Px250U,6635
|
132
133
|
meerschaum/config/_jobs.py,sha256=2bEikO48qVSguFS3lrbWz6uiKt_0b3oSRWhwyz8RRDM,1279
|
133
134
|
meerschaum/config/_patch.py,sha256=21N30q1ANmWMDQ-2RUjpMx7KafWfPQ3lKx9rrMqg1s4,1526
|
134
|
-
meerschaum/config/_paths.py,sha256=
|
135
|
+
meerschaum/config/_paths.py,sha256=jov62LjyHQGlDssQ2b_kd4QENWr8UOoKKByuULfkjyo,8418
|
135
136
|
meerschaum/config/_preprocess.py,sha256=-AEA8m_--KivZwTQ1sWN6LTn5sio_fUr2XZ51BO6wLs,1220
|
136
137
|
meerschaum/config/_read_config.py,sha256=WFZKIXZMDe_ca0ES7ivgM_mnwShvFxLdoeisT_X5-h0,14720
|
137
138
|
meerschaum/config/_shell.py,sha256=s74cmJl8NrhM_Y1cB_P41_JDUYXV0g4WXnKFZWMtnrY,3551
|
138
139
|
meerschaum/config/_sync.py,sha256=oK2ZujO2T1he08BXCFyiniBUevNGWSQKXLcS_jRv_7Y,4155
|
139
|
-
meerschaum/config/_version.py,sha256=
|
140
|
+
meerschaum/config/_version.py,sha256=aJrQ0SowIZBgPrXHghgYBS2TpjhP0IDnQyvcMX4qZiU,76
|
140
141
|
meerschaum/config/paths.py,sha256=Z7vaOunaEJbVFXiSSz4IthhNOQDI-dhtpi5TpSyrcXg,206
|
141
142
|
meerschaum/config/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
142
|
-
meerschaum/config/stack/__init__.py,sha256=
|
143
|
+
meerschaum/config/stack/__init__.py,sha256=Yt7GNzC_hz7iUDZ4gVho_lugJO2DnXgnMtsMG_ReoRg,9114
|
143
144
|
meerschaum/config/stack/grafana/__init__.py,sha256=LNXQw2FvHKrD68RDhqDmi2wJjAHaKw9IWx8rNuyWEPo,2010
|
144
145
|
meerschaum/config/stack/mosquitto/__init__.py,sha256=-OwOjq8KiBoSH_pmgCAAF3Dp3CRD4KgAEdimZSadROs,186
|
145
146
|
meerschaum/config/stack/mosquitto/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -206,16 +207,16 @@ meerschaum/utils/misc.py,sha256=2_FyirImT793NH-wQriJ1VPOW7DcXm98Id0XMn8nh9I,4344
|
|
206
207
|
meerschaum/utils/networking.py,sha256=Sr_eYUGW8_UV9-k9LqRFf7xLtbUcsDucODyLCRsFRUc,1006
|
207
208
|
meerschaum/utils/pool.py,sha256=vkE42af4fjrTEJTxf6Ek3xGucm1MtEkpsSEiaVzNKHs,2655
|
208
209
|
meerschaum/utils/process.py,sha256=UyiNszYXdnjF6WN8Tt0NchWjkCV10MbOXEFn_TAhXeg,7510
|
209
|
-
meerschaum/utils/prompt.py,sha256=
|
210
|
+
meerschaum/utils/prompt.py,sha256=gdQYIspF1Jp9sAVAqqhI7amo8wNWYQH6ZsVz5bhGKjA,18305
|
210
211
|
meerschaum/utils/schedule.py,sha256=btAeSDaoFH62-7wTO0U7NK9P9QHV46PtY3DJ8DN_mCY,10860
|
211
212
|
meerschaum/utils/sql.py,sha256=4sCNEpgUd6uFz6ySs4nnUMVaOT0YAvPM1ZlQYJTSF-0,46656
|
212
213
|
meerschaum/utils/threading.py,sha256=3N8JXPAnwqJiSjuQcbbJg3Rv9-CCUMJpeQRfKFR7MaA,2489
|
213
214
|
meerschaum/utils/typing.py,sha256=L05wOXfWdn_nJ0KnZVr-2zdMYcqjdyOW_7InT3xe6-s,2807
|
214
215
|
meerschaum/utils/warnings.py,sha256=0b5O2DBbhEAGnu6RAB1hlHSVmwL_hcR3EiMkExXmBJ0,6535
|
215
|
-
meerschaum/utils/yaml.py,sha256=
|
216
|
-
meerschaum/utils/daemon/Daemon.py,sha256=
|
217
|
-
meerschaum/utils/daemon/FileDescriptorInterceptor.py,sha256=
|
218
|
-
meerschaum/utils/daemon/RotatingFile.py,sha256=
|
216
|
+
meerschaum/utils/yaml.py,sha256=ptfS5XncNPaYXuvYX11yOpnEwPxDdaelHgDzIrvp9yc,3908
|
217
|
+
meerschaum/utils/daemon/Daemon.py,sha256=TTbBj3M0RoGRaTvvq6IPr9EJb2KiPV_HLucqbPQVzs8,35184
|
218
|
+
meerschaum/utils/daemon/FileDescriptorInterceptor.py,sha256=G5eJDapiTH1IX1x-2r62pLDS1MkRfpJaaZ-3FOAJgo0,4933
|
219
|
+
meerschaum/utils/daemon/RotatingFile.py,sha256=KTkCBtTwz7MvznSsUa6Mnyzk1lM3LlClEkVezjf-SQY,23843
|
219
220
|
meerschaum/utils/daemon/__init__.py,sha256=I_ki51yml4vsh9OoH7BWTaz9SnATD8qM0i0fN3aUMn0,8375
|
220
221
|
meerschaum/utils/daemon/_names.py,sha256=Prf7xA2GWDbKR_9Xq9_5RTTIf9GNWY3Yt0s4tEU3JgM,4330
|
221
222
|
meerschaum/utils/dtypes/__init__.py,sha256=JR9PViJTzhukZhq0QoPIs73HOnXZZr8OmfhAAD4OAUA,6261
|
@@ -226,15 +227,15 @@ meerschaum/utils/formatting/_pipes.py,sha256=wy0iWJFsFl3X2VloaiA_gp9Yx9w6tD3FQZv
|
|
226
227
|
meerschaum/utils/formatting/_pprint.py,sha256=tgrT3FyGyu5CWJYysqK3kX1xdZYorlbOk9fcU_vt9Qg,3096
|
227
228
|
meerschaum/utils/formatting/_shell.py,sha256=ox75O7VHDAiwzSvdMSJZhXLadvAqYJVeihU6WeZ2Ogc,3677
|
228
229
|
meerschaum/utils/packages/__init__.py,sha256=6zRX0g8Kh-wHjvkbiMf_kLKM2BlR5FJUCA3JStX9YeU,62628
|
229
|
-
meerschaum/utils/packages/_packages.py,sha256=
|
230
|
+
meerschaum/utils/packages/_packages.py,sha256=gWrco46rqKjtAI714aJp0XfzYQFAdNi08Kq5dq1VEkg,7977
|
230
231
|
meerschaum/utils/packages/lazy_loader.py,sha256=VHnph3VozH29R4JnSSBfwtA5WKZYZQFT_GeQSShCnuc,2540
|
231
232
|
meerschaum/utils/venv/_Venv.py,sha256=sBnlmxHdAh2bx8btfVoD79-H9-cYsv5lP02IIXkyECs,3553
|
232
233
|
meerschaum/utils/venv/__init__.py,sha256=4vFAu85lQ5GYEVhc6e_KhFmrks4t_Ma6ysmtraccYbs,24187
|
233
|
-
meerschaum-2.2.
|
234
|
-
meerschaum-2.2.
|
235
|
-
meerschaum-2.2.
|
236
|
-
meerschaum-2.2.
|
237
|
-
meerschaum-2.2.
|
238
|
-
meerschaum-2.2.
|
239
|
-
meerschaum-2.2.
|
240
|
-
meerschaum-2.2.
|
234
|
+
meerschaum-2.2.5.dev0.dist-info/LICENSE,sha256=jG2zQEdRNt88EgHUWPpXVWmOrOduUQRx7MnYV9YIPaw,11359
|
235
|
+
meerschaum-2.2.5.dev0.dist-info/METADATA,sha256=uD2d6sBxf_fMlrdGCsOpfRLPHT_hvD-u1rEq8CpgxKw,24192
|
236
|
+
meerschaum-2.2.5.dev0.dist-info/NOTICE,sha256=OTA9Fcthjf5BRvWDDIcBC_xfLpeDV-RPZh3M-HQBRtQ,114
|
237
|
+
meerschaum-2.2.5.dev0.dist-info/WHEEL,sha256=Z4pYXqR_rTB7OWNDYFOm1qRk0RX6GFP2o8LgvP453Hk,91
|
238
|
+
meerschaum-2.2.5.dev0.dist-info/entry_points.txt,sha256=5YBVzibw-0rNA_1VjB16z5GABsOGf-CDhW4yqH8C7Gc,88
|
239
|
+
meerschaum-2.2.5.dev0.dist-info/top_level.txt,sha256=bNoSiDj0El6buocix-FRoAtJOeq1qOF5rRm2u9i7Q6A,11
|
240
|
+
meerschaum-2.2.5.dev0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
241
|
+
meerschaum-2.2.5.dev0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|