meerschaum 2.2.4__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.
@@ -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
@@ -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
- if action_function.__name__ + '_' in name and not name.lstrip('_').startswith('complete'):
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')
@@ -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.
@@ -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
@@ -11,6 +11,9 @@ from meerschaum.utils.typing import SuccessTuple, Any, List, Optional
11
11
  def python(
12
12
  action: Optional[List[str]] = None,
13
13
  sub_args: Optional[List[str]] = None,
14
+ nopretty: bool = False,
15
+ noask: bool = False,
16
+ venv: Optional[str] = None,
14
17
  debug: bool = False,
15
18
  **kw: Any
16
19
  ) -> SuccessTuple:
@@ -24,6 +27,21 @@ def python(
24
27
  Examples:
25
28
  mrsm python
26
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]`)
27
45
  """
28
46
  import sys, subprocess, os
29
47
  from meerschaum.utils.debug import dprint
@@ -33,11 +51,16 @@ def python(
33
51
  from meerschaum.config import __version__ as _version
34
52
  from meerschaum.config.paths import VIRTENV_RESOURCES_PATH, PYTHON_RESOURCES_PATH
35
53
  from meerschaum.utils.packages import run_python_package, attempt_import
54
+ from meerschaum.utils.process import run_process
36
55
 
37
56
  if action is None:
38
57
  action = []
39
58
 
40
- joined_actions = ["import meerschaum as mrsm"]
59
+ joined_actions = (
60
+ ["import meerschaum as mrsm"]
61
+ if venv is None and not sub_args
62
+ else []
63
+ )
41
64
  line = ""
42
65
  for i, a in enumerate(action):
43
66
  if a == '':
@@ -68,6 +91,11 @@ def python(
68
91
  '"""); '
69
92
  + 'pts.print_formatted_text(ansi); '
70
93
  )
94
+ if nopretty or venv is not None or sub_args:
95
+ print_command = ""
96
+
97
+ if sub_args:
98
+ nopretty = True
71
99
 
72
100
  command = print_command
73
101
  for a in joined_actions:
@@ -89,7 +117,21 @@ def python(
89
117
  'ptpython',
90
118
  sub_args or ['--dark-bg', '-i', init_script_path.as_posix()],
91
119
  foreground = True,
92
- venv = None,
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
+ ),
134
+ foreground = True,
93
135
  )
94
136
  except KeyboardInterrupt:
95
137
  return_code = 1
@@ -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
  """
@@ -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.security import OAuth2PasswordRequestForm
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: OAuth2PasswordRequestForm = fastapi.Depends()
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['username'], data['password']) if isinstance(data, dict)
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
+ )
@@ -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}...")
@@ -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
 
@@ -2,4 +2,4 @@
2
2
  Specify the Meerschaum release version.
3
3
  """
4
4
 
5
- __version__ = "2.2.4"
5
+ __version__ = "2.2.5.dev0"
@@ -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' + ':' + volumes['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
- if daemon_error:
208
- warn(f"Encountered an error while starting the daemon '{self}':\n{daemon_error}")
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
- continue
68
+ from meerschaum.utils.warnings import warn
69
+ warn(f"OSError in FileDescriptorInterceptor: {e}")
70
+ break
69
71
 
70
- first_char_is_newline = data[0] == b'\n'
71
- last_char_is_newline = data[-1] == b'\n'
72
+ try:
73
+ first_char_is_newline = data[0] == b'\n'
74
+ last_char_is_newline = data[-1] == b'\n'
72
75
 
73
- injected_str = self.injection_hook()
74
- injected_bytes = injected_str.encode('utf-8')
76
+ injected_str = self.injection_hook()
77
+ injected_bytes = injected_str.encode('utf-8')
75
78
 
76
- if is_first_read:
77
- data = b'\n' + data
78
- is_first_read = False
79
+ if is_first_read:
80
+ data = b'\n' + data
81
+ is_first_read = False
79
82
 
80
- modified_data = (
81
- (data[:-1].replace(b'\n', b'\n' + injected_bytes) + b'\n')
82
- if last_char_is_newline
83
- else data.replace(b'\n', b'\n' + injected_bytes)
84
- )
85
- os.write(self.new_file_descriptor, modified_data)
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
- if prefix_str:
370
- self._current_file_obj.write(prefix_str)
371
- self._current_file_obj.write(data)
372
- if suffix_str:
373
- self._current_file_obj.write(suffix_str)
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"Failed to write to subfile:\n{traceback.format_exc()}")
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',
@@ -74,7 +75,6 @@ packages: Dict[str, Dict[str, str]] = {
74
75
  'litecli' : 'litecli>=1.5.0',
75
76
  'mssqlcli' : 'mssql-cli>=1.0.0',
76
77
  'gadwall' : 'gadwall>=0.2.0',
77
- 'ptpython' : 'ptpython>=3.0.27',
78
78
  },
79
79
  'stack': {
80
80
  'compose' : 'docker-compose>=1.29.2',
@@ -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: Optional[str] = None,
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: Optional[str], default None
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 = 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 choices:
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 enumerate(choices):
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
- choices[i] = new_c
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(choices):
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 choices:
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(choices, sentence=True)
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
- return choices[int(answer) - 1]
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
- _answers.append(altered_choices.get(_answer, _answer))
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.4
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'
@@ -105,7 +106,6 @@ Requires-Dist: mycli >=1.23.2 ; extra == 'cli'
105
106
  Requires-Dist: litecli >=1.5.0 ; extra == 'cli'
106
107
  Requires-Dist: mssql-cli >=1.0.0 ; extra == 'cli'
107
108
  Requires-Dist: gadwall >=0.2.0 ; extra == 'cli'
108
- Requires-Dist: ptpython >=3.0.27 ; extra == 'cli'
109
109
  Provides-Extra: core
110
110
  Requires-Dist: wheel >=0.34.2 ; extra == 'core'
111
111
  Requires-Dist: setuptools >=63.3.0 ; extra == 'core'
@@ -118,6 +118,7 @@ Requires-Dist: python-dateutil >=2.7.5 ; extra == 'core'
118
118
  Requires-Dist: requests >=2.23.0 ; extra == 'core'
119
119
  Requires-Dist: binaryornot >=0.4.4 ; extra == 'core'
120
120
  Requires-Dist: pyvim >=3.0.2 ; extra == 'core'
121
+ Requires-Dist: ptpython >=3.0.27 ; extra == 'core'
121
122
  Requires-Dist: aiofiles >=0.6.0 ; extra == 'core'
122
123
  Requires-Dist: packaging >=21.3.0 ; extra == 'core'
123
124
  Requires-Dist: prompt-toolkit >=3.0.39 ; extra == 'core'
@@ -204,6 +205,7 @@ Requires-Dist: python-dateutil >=2.7.5 ; extra == 'full'
204
205
  Requires-Dist: requests >=2.23.0 ; extra == 'full'
205
206
  Requires-Dist: binaryornot >=0.4.4 ; extra == 'full'
206
207
  Requires-Dist: pyvim >=3.0.2 ; extra == 'full'
208
+ Requires-Dist: ptpython >=3.0.27 ; extra == 'full'
207
209
  Requires-Dist: aiofiles >=0.6.0 ; extra == 'full'
208
210
  Requires-Dist: packaging >=21.3.0 ; extra == 'full'
209
211
  Requires-Dist: prompt-toolkit >=3.0.39 ; extra == 'full'
@@ -288,6 +290,7 @@ Requires-Dist: python-dateutil >=2.7.5 ; extra == 'sql'
288
290
  Requires-Dist: requests >=2.23.0 ; extra == 'sql'
289
291
  Requires-Dist: binaryornot >=0.4.4 ; extra == 'sql'
290
292
  Requires-Dist: pyvim >=3.0.2 ; extra == 'sql'
293
+ Requires-Dist: ptpython >=3.0.27 ; extra == 'sql'
291
294
  Requires-Dist: aiofiles >=0.6.0 ; extra == 'sql'
292
295
  Requires-Dist: packaging >=21.3.0 ; extra == 'sql'
293
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=_pfnlnXhqzP4XQ5syZ04sDG9LxS5UhNdziBlETDMUhc,5756
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=7CNoKEqkqqafqMcChspJX9cR9OdgEWk9ggj0000Jl98,11360
23
+ meerschaum/actions/__init__.py,sha256=ZUdQk6FvuMqyEvLCP-RiDDlIK4NwkqLRgFZSKq04qq4,11440
24
24
  meerschaum/actions/api.py,sha256=mWhv4bn3Ap17_Gqf2Cx9bAsHKG-Zhy072pBbNzHLEJc,12756
25
- meerschaum/actions/bootstrap.py,sha256=JnIyJ4odw6cA4e0Cw7J8THkLavMcj68nRyGsQDAT8nc,13396
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=Nf1c47i_qZcWVpVe_se3qG5LhhYpXzvDPCqFBy_ER8U,15528
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=Ogqm9NFFq3E710mRWAAU7F8JiEHrxYcGKj5H3ZDsP-s,3295
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=p92UahbFeKOThGooO8Ul5Z6s6_Ll54FCxXsMAFnhuT0,29024
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=He8JnFDhfCq25Wlz1Jh3TcGz83VOttrJk6RgzJKbFLw,1056
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=zoBdrNlR66ZZ5xoA_64EkV3XaAMypu7BD3LP5dLlbFY,1933
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=CE8gumBiOtnZZdTATCLAZgUnhL04yJdReaNCrv1yuJc,8218
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=CWhB17T00NeyyKW5T0fjmetd5AqOGYIN5ySbFg3dAMI,8265
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=tzwD6PP0EAlINX0JJN0MPxAdg_vH8JKm0JxuSFEgJ_A,71
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=c_WdTSejVdj8lqSE_pK5MhIBkHoftiZWDuEuB9dmk2I,9007
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=0mBFbgi_l9rCou9UnC_6qKTHkqyl1Z_jSRzfmc0xRXM,16490
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=vbCrFjdapKsZ9wRRaI9Ih8dVUwZ-KHpSzfGhRcpDBgQ,3162
216
- meerschaum/utils/daemon/Daemon.py,sha256=rd9L9BgB0vwnPuhAbgLI-KkiEhFIPJqTQZ4PJ66lTf4,34686
217
- meerschaum/utils/daemon/FileDescriptorInterceptor.py,sha256=1cn5nQYLImL-BHXlLoxN_TadgN7XmGRLl1b7xecYMPc,4544
218
- meerschaum/utils/daemon/RotatingFile.py,sha256=LuaHPs-c4GaMc3VkMjvL6WJ5r7wmMUOm9ZLyk4OHAlg,23658
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=yU8bD4fYs4cv1gOtEp3QrnejyvPi0K2W9TYaP_cd59w,7977
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.4.dist-info/LICENSE,sha256=jG2zQEdRNt88EgHUWPpXVWmOrOduUQRx7MnYV9YIPaw,11359
234
- meerschaum-2.2.4.dist-info/METADATA,sha256=qA6ciSqJ8o1Cfsl2Al74zn3a7NS1wzsteaim54kRDIQ,24035
235
- meerschaum-2.2.4.dist-info/NOTICE,sha256=OTA9Fcthjf5BRvWDDIcBC_xfLpeDV-RPZh3M-HQBRtQ,114
236
- meerschaum-2.2.4.dist-info/WHEEL,sha256=cpQTJ5IWu9CdaPViMhC9YzF8gZuS5-vlfoFihTBC86A,91
237
- meerschaum-2.2.4.dist-info/entry_points.txt,sha256=5YBVzibw-0rNA_1VjB16z5GABsOGf-CDhW4yqH8C7Gc,88
238
- meerschaum-2.2.4.dist-info/top_level.txt,sha256=bNoSiDj0El6buocix-FRoAtJOeq1qOF5rRm2u9i7Q6A,11
239
- meerschaum-2.2.4.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
240
- meerschaum-2.2.4.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (70.1.0)
2
+ Generator: setuptools (70.3.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5