meerschaum 2.2.7__py3-none-any.whl → 2.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. meerschaum/__init__.py +6 -1
  2. meerschaum/__main__.py +0 -5
  3. meerschaum/_internal/arguments/__init__.py +1 -1
  4. meerschaum/_internal/arguments/_parse_arguments.py +72 -6
  5. meerschaum/_internal/arguments/_parser.py +45 -15
  6. meerschaum/_internal/docs/index.py +265 -8
  7. meerschaum/_internal/entry.py +154 -24
  8. meerschaum/_internal/shell/Shell.py +264 -77
  9. meerschaum/actions/__init__.py +29 -17
  10. meerschaum/actions/api.py +12 -12
  11. meerschaum/actions/attach.py +113 -0
  12. meerschaum/actions/copy.py +68 -41
  13. meerschaum/actions/delete.py +112 -50
  14. meerschaum/actions/edit.py +3 -3
  15. meerschaum/actions/install.py +40 -32
  16. meerschaum/actions/pause.py +44 -27
  17. meerschaum/actions/restart.py +107 -0
  18. meerschaum/actions/show.py +130 -159
  19. meerschaum/actions/start.py +161 -100
  20. meerschaum/actions/stop.py +78 -42
  21. meerschaum/api/_events.py +25 -1
  22. meerschaum/api/_oauth2.py +2 -0
  23. meerschaum/api/_websockets.py +2 -2
  24. meerschaum/api/dash/callbacks/jobs.py +36 -44
  25. meerschaum/api/dash/jobs.py +89 -78
  26. meerschaum/api/routes/__init__.py +1 -0
  27. meerschaum/api/routes/_actions.py +148 -17
  28. meerschaum/api/routes/_jobs.py +407 -0
  29. meerschaum/api/routes/_pipes.py +5 -5
  30. meerschaum/config/_default.py +1 -0
  31. meerschaum/config/_jobs.py +1 -1
  32. meerschaum/config/_paths.py +7 -0
  33. meerschaum/config/_shell.py +8 -3
  34. meerschaum/config/_version.py +1 -1
  35. meerschaum/config/static/__init__.py +17 -0
  36. meerschaum/connectors/Connector.py +13 -7
  37. meerschaum/connectors/__init__.py +28 -15
  38. meerschaum/connectors/api/APIConnector.py +27 -1
  39. meerschaum/connectors/api/_actions.py +71 -6
  40. meerschaum/connectors/api/_jobs.py +368 -0
  41. meerschaum/connectors/api/_pipes.py +85 -84
  42. meerschaum/connectors/parse.py +27 -15
  43. meerschaum/core/Pipe/_bootstrap.py +16 -8
  44. meerschaum/jobs/_Executor.py +69 -0
  45. meerschaum/jobs/_Job.py +899 -0
  46. meerschaum/jobs/__init__.py +396 -0
  47. meerschaum/jobs/systemd.py +694 -0
  48. meerschaum/plugins/__init__.py +97 -12
  49. meerschaum/utils/daemon/Daemon.py +276 -30
  50. meerschaum/utils/daemon/FileDescriptorInterceptor.py +5 -5
  51. meerschaum/utils/daemon/RotatingFile.py +14 -7
  52. meerschaum/utils/daemon/StdinFile.py +121 -0
  53. meerschaum/utils/daemon/__init__.py +15 -7
  54. meerschaum/utils/daemon/_names.py +15 -13
  55. meerschaum/utils/formatting/__init__.py +2 -1
  56. meerschaum/utils/formatting/_jobs.py +115 -62
  57. meerschaum/utils/formatting/_shell.py +6 -0
  58. meerschaum/utils/misc.py +41 -22
  59. meerschaum/utils/packages/_packages.py +9 -6
  60. meerschaum/utils/process.py +9 -9
  61. meerschaum/utils/prompt.py +16 -8
  62. meerschaum/utils/venv/__init__.py +2 -2
  63. {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dist-info}/METADATA +22 -25
  64. {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dist-info}/RECORD +70 -61
  65. {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dist-info}/WHEEL +1 -1
  66. {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dist-info}/LICENSE +0 -0
  67. {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dist-info}/NOTICE +0 -0
  68. {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dist-info}/entry_points.txt +0 -0
  69. {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dist-info}/top_level.txt +0 -0
  70. {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dist-info}/zip-safe +0 -0
@@ -8,6 +8,9 @@ This module is the entry point for the interactive shell.
8
8
  from __future__ import annotations
9
9
  import os
10
10
  from copy import deepcopy
11
+ from itertools import chain
12
+ import shlex
13
+
11
14
  from meerschaum.utils.typing import Union, SuccessTuple, Any, Callable, Optional, List, Dict
12
15
  from meerschaum.utils.packages import attempt_import
13
16
  from meerschaum.config import __doc__, __version__ as version, get_config
@@ -28,7 +31,17 @@ prompt_toolkit = attempt_import('prompt_toolkit', lazy=False, warn=False, instal
28
31
  from meerschaum._internal.shell.ValidAutoSuggest import ValidAutoSuggest
29
32
  from meerschaum._internal.shell.ShellCompleter import ShellCompleter
30
33
  _clear_screen = get_config('shell', 'clear_screen', patch=True)
31
- from meerschaum.utils.misc import string_width
34
+ from meerschaum.utils.misc import string_width, remove_ansi
35
+ from meerschaum.utils.warnings import warn
36
+ from meerschaum.jobs import get_executor_keys_from_context
37
+ from meerschaum.config.static import STATIC_CONFIG
38
+ from meerschaum._internal.arguments._parse_arguments import (
39
+ split_chained_sysargs,
40
+ split_pipeline_sysargs,
41
+ parse_arguments,
42
+ parse_line,
43
+ parse_dict_to_sysargs,
44
+ )
32
45
 
33
46
  patch = True
34
47
  ### remove default cmd2 commands
@@ -56,12 +69,16 @@ hidden_commands = {
56
69
  'ipy',
57
70
  }
58
71
  reserved_completers = {
59
- 'instance', 'repo'
72
+ 'instance', 'repo', 'executor',
60
73
  }
61
74
 
62
75
  ### To handle dynamic reloading, store shell attributes externally.
63
76
  ### This is because the shell object address gets lost upon reloads.
64
77
  shell_attrs = {}
78
+ AND_KEY: str = STATIC_CONFIG['system']['arguments']['and_key']
79
+ ESCAPED_AND_KEY: str = STATIC_CONFIG['system']['arguments']['escaped_and_key']
80
+ PIPELINE_KEY: str = STATIC_CONFIG['system']['arguments']['pipeline_key']
81
+ ESCAPED_PIPELINE_KEY: str = STATIC_CONFIG['system']['arguments']['escaped_pipeline_key']
65
82
 
66
83
  def _insert_shell_actions(
67
84
  _shell: Optional['Shell'] = None,
@@ -110,7 +127,6 @@ def _completer_wrapper(
110
127
  if _check_keys is not None:
111
128
  return _check_keys
112
129
 
113
- from meerschaum._internal.arguments._parse_arguments import parse_line
114
130
  args = parse_line(line)
115
131
  if target.__name__ != 'default_action_completer':
116
132
  if len(args['action']) > 0:
@@ -148,7 +164,6 @@ def default_action_completer(
148
164
 
149
165
  def _check_complete_keys(line: str) -> Optional[List[str]]:
150
166
  from meerschaum._internal.arguments._parser import parser, get_arguments_triggers
151
- from meerschaum._internal.arguments._parse_arguments import parse_line
152
167
 
153
168
  ### TODO Add all triggers
154
169
  trigger_args = {
@@ -164,7 +179,6 @@ def _check_complete_keys(line: str) -> Optional[List[str]]:
164
179
 
165
180
  ### TODO Find out arg possibilities
166
181
  possibilities = []
167
- # last_word = line.split(' ')[-1]
168
182
  last_word = line.rstrip(' ').split(' ')[-1]
169
183
 
170
184
  if last_word.startswith('-'):
@@ -247,11 +261,11 @@ class Shell(cmd.Cmd):
247
261
 
248
262
  from meerschaum.config._paths import SHELL_HISTORY_PATH
249
263
  shell_attrs['session'] = prompt_toolkit_shortcuts.PromptSession(
250
- history = prompt_toolkit_history.FileHistory(str(SHELL_HISTORY_PATH)),
251
- auto_suggest = ValidAutoSuggest(),
252
- completer = ShellCompleter(),
253
- complete_while_typing = True,
254
- reserve_space_for_menu = False,
264
+ history=prompt_toolkit_history.FileHistory(SHELL_HISTORY_PATH.as_posix()),
265
+ auto_suggest=ValidAutoSuggest(),
266
+ completer=ShellCompleter(),
267
+ complete_while_typing=True,
268
+ reserve_space_for_menu=False,
255
269
  )
256
270
 
257
271
  try: ### try cmd2 arguments first
@@ -281,6 +295,7 @@ class Shell(cmd.Cmd):
281
295
  shell_attrs['_sysargs'] = sysargs
282
296
  shell_attrs['_actions']['instance'] = self.do_instance
283
297
  shell_attrs['_actions']['repo'] = self.do_repo
298
+ shell_attrs['_actions']['executor'] = self.do_executor
284
299
  shell_attrs['_actions']['debug'] = self.do_debug
285
300
  shell_attrs['_update_bottom_toolbar'] = True
286
301
  shell_attrs['_old_bottom_toolbar'] = ''
@@ -337,6 +352,9 @@ class Shell(cmd.Cmd):
337
352
  shell_attrs['instance_keys'] = remove_ansi(str(instance))
338
353
  if shell_attrs.get('repo_keys', None) is None:
339
354
  shell_attrs['repo_keys'] = get_config('meerschaum', 'default_repository', patch=patch)
355
+ if shell_attrs.get('executor_keys', None) is None:
356
+ shell_attrs['executor_keys'] = get_executor_keys_from_context()
357
+
340
358
  ### this will be updated later in update_prompt ONLY IF {username} is in the prompt
341
359
  shell_attrs['username'] = ''
342
360
 
@@ -362,14 +380,19 @@ class Shell(cmd.Cmd):
362
380
  def insert_actions(self):
363
381
  from meerschaum.actions import actions
364
382
 
365
- def update_prompt(self, instance: Optional[str] = None, username: Optional[str] = None):
383
+ def update_prompt(
384
+ self,
385
+ instance: Optional[str] = None,
386
+ username: Optional[str] = None,
387
+ executor_keys: Optional[str] = None,
388
+ ):
366
389
  from meerschaum.utils.formatting import ANSI, colored
367
390
  from meerschaum._internal.entry import _shell, get_shell
368
391
 
369
392
  cmd.__builtins__['input'] = input_with_sigint(
370
393
  _old_input,
371
394
  shell_attrs['session'],
372
- shell = self,
395
+ shell=self,
373
396
  )
374
397
  prompt = shell_attrs['_prompt']
375
398
  mask = prompt
@@ -395,7 +418,8 @@ class Shell(cmd.Cmd):
395
418
  from meerschaum.connectors.sql import SQLConnector
396
419
  try:
397
420
  conn_attrs = parse_instance_keys(
398
- remove_ansi(shell_attrs['instance_keys']), construct=False
421
+ remove_ansi(shell_attrs['instance_keys']),
422
+ construct=False,
399
423
  )
400
424
  if 'username' not in conn_attrs:
401
425
  if 'uri' in conn_attrs:
@@ -409,12 +433,27 @@ class Shell(cmd.Cmd):
409
433
  if username is None:
410
434
  username = '(no username)'
411
435
  shell_attrs['username'] = (
412
- username if not ANSI else
413
- colored(username, **get_config('shell', 'ansi', 'username', 'rich'))
436
+ username
437
+ if not ANSI
438
+ else colored(username, **get_config('shell', 'ansi', 'username', 'rich'))
414
439
  )
415
440
  prompt = prompt.replace('{username}', shell_attrs['username'])
416
441
  mask = mask.replace('{username}', ''.join(['\0' for c in '{username}']))
417
442
 
443
+ if '{executor_keys}' in shell_attrs['_prompt']:
444
+ if executor_keys is None:
445
+ executor_keys = shell_attrs.get('executor_keys', None) or 'local'
446
+ shell_attrs['executor_keys'] = (
447
+ executor_keys
448
+ if not ANSI
449
+ else colored(
450
+ remove_ansi(executor_keys),
451
+ **get_config('shell', 'ansi', 'executor', 'rich')
452
+ )
453
+ )
454
+ prompt = prompt.replace('{executor_keys}', shell_attrs['executor_keys'])
455
+ mask = mask.replace('{executor_keys}', ''.join(['\0' for c in '{executor_keys}']))
456
+
418
457
  remainder_prompt = list(shell_attrs['_prompt'])
419
458
  for i, c in enumerate(mask):
420
459
  if c != '\0':
@@ -422,10 +461,13 @@ class Shell(cmd.Cmd):
422
461
  if ANSI:
423
462
  _c = colored(_c, **get_config('shell', 'ansi', 'prompt', 'rich'))
424
463
  remainder_prompt[i] = _c
464
+
425
465
  self.prompt = ''.join(remainder_prompt).replace(
426
466
  '{username}', shell_attrs['username']
427
467
  ).replace(
428
468
  '{instance}', shell_attrs['instance']
469
+ ).replace(
470
+ '{executor_keys}', shell_attrs['executor_keys']
429
471
  )
430
472
  shell_attrs['prompt'] = self.prompt
431
473
  ### flush stdout
@@ -448,6 +490,9 @@ class Shell(cmd.Cmd):
448
490
  ### make a backup of line for later
449
491
  original_line = deepcopy(line)
450
492
 
493
+ ### Escape backslashes to allow for multi-line input.
494
+ line = line.replace('\\\n', ' ')
495
+
451
496
  ### cmd2 support: check if command exists
452
497
  try:
453
498
  command = line.command
@@ -478,72 +523,125 @@ class Shell(cmd.Cmd):
478
523
  return "help " + line[len(help_token):]
479
524
 
480
525
  from meerschaum._internal.arguments import parse_line
481
- args = parse_line(line)
482
- if args.get('help', False):
526
+ try:
527
+ sysargs = shlex.split(line)
528
+ except ValueError as e:
529
+ warn(e, stack=False)
530
+ return ""
531
+
532
+ sysargs, pipeline_args = split_pipeline_sysargs(sysargs)
533
+ chained_sysargs = split_chained_sysargs(sysargs)
534
+ chained_kwargs = [
535
+ parse_arguments(_sysargs)
536
+ for _sysargs in chained_sysargs
537
+ ]
538
+
539
+ if '--help' in sysargs or '-h' in sysargs:
483
540
  from meerschaum._internal.arguments._parser import parse_help
484
- parse_help(args)
541
+ parse_help(sysargs)
485
542
  return ""
486
543
 
544
+ patch_args: Dict[str, Any] = {}
545
+
487
546
  ### NOTE: pass `shell` flag in case actions need to distinguish between
488
547
  ### being run on the command line and being run in the shell
489
- args['shell'] = True
490
- args['line'] = line
548
+ patch_args.update({
549
+ 'shell': True,
550
+ 'line': line,
551
+ })
552
+ patches: List[Dict[str, Any]] = [{} for _ in chained_kwargs]
491
553
 
492
554
  ### if debug is not set on the command line,
493
555
  ### default to shell setting
494
- if not args.get('debug', False):
495
- args['debug'] = shell_attrs['debug']
556
+ for kwargs in chained_kwargs:
557
+ if not kwargs.get('debug', False):
558
+ kwargs['debug'] = shell_attrs['debug']
496
559
 
497
560
  ### Make sure an action was provided.
498
- if not args.get('action', None):
561
+ if (
562
+ not chained_kwargs
563
+ or not chained_kwargs[0].get('action', None)
564
+ ):
499
565
  self.emptyline()
500
566
  return ''
501
567
 
502
568
  ### Strip a leading 'mrsm' if it's provided.
503
- if args['action'][0] == 'mrsm':
504
- args['action'] = args['action'][1:]
505
- if not args['action']:
506
- self.emptyline()
507
- return ''
569
+ for kwargs in chained_kwargs:
570
+ if kwargs['action'][0] == 'mrsm':
571
+ kwargs['action'] = kwargs['action'][1:]
572
+ if not kwargs['action']:
573
+ self.emptyline()
574
+ return ''
508
575
 
509
576
  ### If we don't recognize the action,
510
577
  ### make it a shell action.
511
- from meerschaum.actions import get_main_action_name
512
- main_action_name = get_main_action_name(args['action'])
513
- if main_action_name is None:
514
- if not hasattr(self, 'do_' + args['action'][0]):
515
- args['action'].insert(0, 'sh')
516
- main_action_name = 'sh'
578
+ ### TODO: make this work for chained actions
579
+ def _get_main_action_name(kwargs):
580
+ from meerschaum.actions import get_main_action_name
581
+ main_action_name = get_main_action_name(kwargs['action'])
582
+ if main_action_name is None:
583
+ if not hasattr(self, 'do_' + kwargs['action'][0]):
584
+ kwargs['action'].insert(0, 'sh')
585
+ main_action_name = 'sh'
586
+ else:
587
+ main_action_name = kwargs['action'][0]
588
+ return main_action_name
589
+
590
+ def _add_flag_to_kwargs(kwargs, i, key, shell_key=None):
591
+ shell_key = shell_key or key
592
+ shell_value = remove_ansi(shell_attrs.get(shell_key) or '')
593
+ if key == 'mrsm_instance':
594
+ default_value = get_config('meerschaum', 'instance')
595
+ elif key == 'repository':
596
+ default_value = get_config('meerschaum', 'default_repository')
597
+ elif key == 'executor_keys':
598
+ default_value = get_executor_keys_from_context()
517
599
  else:
518
- main_action_name = args['action'][0]
600
+ default_value = None
601
+
602
+ if key in kwargs or shell_value == default_value:
603
+ return
604
+
605
+ patches[i][key] = shell_value
519
606
 
520
607
  ### if no instance is provided, use current shell default,
521
608
  ### but not for the 'api' command (to avoid recursion)
522
- if 'mrsm_instance' not in args and main_action_name != 'api':
523
- args['mrsm_instance'] = str(shell_attrs['instance_keys'])
609
+ for i, kwargs in enumerate(chained_kwargs):
610
+ main_action_name = _get_main_action_name(kwargs)
611
+ if main_action_name == 'api':
612
+ continue
524
613
 
525
- if 'repository' not in args and main_action_name != 'api':
526
- args['repository'] = str(shell_attrs['repo_keys'])
614
+ _add_flag_to_kwargs(kwargs, i, 'mrsm_instance', shell_key='instance_keys')
615
+ _add_flag_to_kwargs(kwargs, i, 'repository', shell_key='repo_keys')
616
+ _add_flag_to_kwargs(kwargs, i, 'executor_keys')
527
617
 
528
618
  ### parse out empty strings
529
- if args['action'][0].strip("\"'") == '':
619
+ if chained_kwargs[0]['action'][0].strip("\"'") == '':
530
620
  self.emptyline()
531
621
  return ""
532
622
 
533
- ### If the `--daemon` flag is present, prepend 'start job'.
534
- if args.get('daemon', False) and 'stack' not in args['action']:
535
- args['action'] = ['start', 'jobs'] + args['action']
536
- main_action_name = 'start'
537
-
538
- positional_only = (main_action_name not in shell_attrs['_actions'])
623
+ positional_only = (_get_main_action_name(chained_kwargs[0]) not in shell_attrs['_actions'])
539
624
  if positional_only:
540
625
  return original_line
541
626
 
542
- from meerschaum._internal.entry import entry_with_args
627
+ ### Apply patch to all kwargs.
628
+ for i, kwargs in enumerate([_ for _ in chained_kwargs]):
629
+ kwargs.update(patches[i])
630
+
631
+ from meerschaum._internal.entry import entry_with_args, entry
632
+ sysargs_to_execute = []
633
+ for i, kwargs in enumerate(chained_kwargs):
634
+ step_kwargs = {k: v for k, v in kwargs.items() if k != 'line'}
635
+ step_sysargs = parse_dict_to_sysargs(step_kwargs)
636
+ sysargs_to_execute.extend(step_sysargs)
637
+ sysargs_to_execute.append(AND_KEY)
638
+
639
+ sysargs_to_execute = sysargs_to_execute[:-1] + (
640
+ ([':'] + pipeline_args) if pipeline_args else []
641
+ )
543
642
 
544
643
  try:
545
- success_tuple = entry_with_args(_actions=shell_attrs['_actions'], **args)
546
- # success_tuple = entry_with_args(**args)
644
+ success_tuple = entry(sysargs_to_execute, _patch_args=patch_args)
547
645
  except Exception as e:
548
646
  success_tuple = False, str(e)
549
647
 
@@ -551,9 +649,9 @@ class Shell(cmd.Cmd):
551
649
  if isinstance(success_tuple, tuple):
552
650
  print_tuple(
553
651
  success_tuple,
554
- skip_common = (not shell_attrs['debug']),
555
- upper_padding = 1,
556
- lower_padding = 0,
652
+ skip_common=(not shell_attrs['debug']),
653
+ upper_padding=1,
654
+ lower_padding=0,
557
655
  )
558
656
 
559
657
  ### Restore the old working directory.
@@ -569,12 +667,12 @@ class Shell(cmd.Cmd):
569
667
  if stop:
570
668
  return True
571
669
 
572
- def do_pass(self, line):
670
+ def do_pass(self, line, executor_keys=None):
573
671
  """
574
672
  Do nothing.
575
673
  """
576
674
 
577
- def do_debug(self, action: Optional[List[str]] = None, **kw):
675
+ def do_debug(self, action: Optional[List[str]] = None, executor_keys=None, **kw):
578
676
  """
579
677
  Toggle the shell's debug mode.
580
678
  If debug = on, append `--debug` to all commands.
@@ -604,11 +702,12 @@ class Shell(cmd.Cmd):
604
702
  info(f"Debug mode is {'on' if shell_attrs['debug'] else 'off'}.")
605
703
 
606
704
  def do_instance(
607
- self,
608
- action : Optional[List[str]] = None,
609
- debug : bool = False,
610
- **kw : Any
611
- ) -> SuccessTuple:
705
+ self,
706
+ action: Optional[List[str]] = None,
707
+ executor_keys=None,
708
+ debug: bool = False,
709
+ **kw: Any
710
+ ) -> SuccessTuple:
612
711
  """
613
712
  Temporarily set a default Meerschaum instance for the duration of the shell.
614
713
  The default instance is loaded from the Meerschaum configuraton file
@@ -665,22 +764,42 @@ class Shell(cmd.Cmd):
665
764
  return True, "Success"
666
765
 
667
766
 
668
- def complete_instance(self, text: str, line: str, begin_index: int, end_index: int):
767
+ def complete_instance(
768
+ self,
769
+ text: str,
770
+ line: str,
771
+ begin_index: int,
772
+ end_index: int,
773
+ _executor: bool = False,
774
+ _additional_options: Optional[List[str]] = None,
775
+ ):
669
776
  from meerschaum.utils.misc import get_connector_labels
670
777
  from meerschaum._internal.arguments._parse_arguments import parse_line
671
- from meerschaum.connectors import instance_types
778
+ from meerschaum.connectors import instance_types, _load_builtin_custom_connectors
779
+ if _executor:
780
+ _load_builtin_custom_connectors()
781
+ from meerschaum.jobs import executor_types
782
+
783
+ conn_types = instance_types if not _executor else executor_types
784
+
672
785
  args = parse_line(line)
673
786
  action = args['action']
674
787
  _text = action[1] if len(action) > 1 else ""
675
- return get_connector_labels(*instance_types, search_term=_text, ignore_exact_match=True)
788
+ return get_connector_labels(
789
+ *conn_types,
790
+ search_term=_text,
791
+ ignore_exact_match=True,
792
+ _additional_options=_additional_options,
793
+ )
676
794
 
677
795
 
678
796
  def do_repo(
679
- self,
680
- action: Optional[List[str]] = None,
681
- debug: bool = False,
682
- **kw: Any
683
- ) -> SuccessTuple:
797
+ self,
798
+ action: Optional[List[str]] = None,
799
+ executor_keys=None,
800
+ debug: bool = False,
801
+ **kw: Any
802
+ ) -> SuccessTuple:
684
803
  """
685
804
  Temporarily set a default Meerschaum repository for the duration of the shell.
686
805
  The default repository (mrsm.io) is loaded from the Meerschaum configuraton file
@@ -727,9 +846,65 @@ class Shell(cmd.Cmd):
727
846
  return True, "Success"
728
847
 
729
848
  def complete_repo(self, *args) -> List[str]:
730
- return self.complete_instance(*args)
849
+ results = self.complete_instance(*args)
850
+ return [result for result in results if result.startswith('api:')]
731
851
 
732
- def do_help(self, line: str) -> List[str]:
852
+ def do_executor(
853
+ self,
854
+ action: Optional[List[str]] = None,
855
+ executor_keys=None,
856
+ debug: bool = False,
857
+ **kw: Any
858
+ ) -> SuccessTuple:
859
+ """
860
+ Temporarily set a default Meerschaum executor for the duration of the shell.
861
+
862
+ You can change the default repository with `edit config`.
863
+
864
+ Usage:
865
+ executor {API label}
866
+
867
+ Examples:
868
+ ### reset to default executor
869
+ executor
870
+
871
+ ### set the executor to 'api:main'
872
+ executor api:main
873
+
874
+ Note that executors are API instances.
875
+ """
876
+ from meerschaum import get_connector
877
+ from meerschaum.connectors.parse import parse_executor_keys
878
+ from meerschaum.utils.warnings import warn, info
879
+ from meerschaum.jobs import get_executor_keys_from_context
880
+
881
+ if action is None:
882
+ action = []
883
+
884
+ try:
885
+ executor_keys = action[0]
886
+ except (IndexError, AttributeError):
887
+ executor_keys = ''
888
+ if executor_keys == '':
889
+ executor_keys = get_executor_keys_from_context()
890
+
891
+ if executor_keys == 'systemd' and get_executor_keys_from_context() != 'systemd':
892
+ warn(f"Cannot execute via `systemd`, falling back to `local`...", stack=False)
893
+ executor_keys = 'local'
894
+
895
+ conn = parse_executor_keys(executor_keys, debug=debug)
896
+
897
+ shell_attrs['executor_keys'] = str(conn).replace('systemd:main', 'systemd')
898
+
899
+ info(f"Default executor for the current shell: {executor_keys}")
900
+ return True, "Success"
901
+
902
+ def complete_executor(self, *args) -> List[str]:
903
+ from meerschaum.jobs import executor_types
904
+ results = self.complete_instance(*args, _executor=True, _additional_options=['local'])
905
+ return [result for result in results if result.split(':')[0] in executor_types]
906
+
907
+ def do_help(self, line: str, executor_keys=None) -> List[str]:
733
908
  """
734
909
  Show help for Meerschaum actions.
735
910
 
@@ -800,7 +975,7 @@ class Shell(cmd.Cmd):
800
975
  possibilities.append(name.replace('do_', ''))
801
976
  return possibilities
802
977
 
803
- def do_exit(self, params) -> True:
978
+ def do_exit(self, params, executor_keys=None) -> True:
804
979
  """
805
980
  Exit the Meerschaum shell.
806
981
  """
@@ -865,23 +1040,31 @@ def input_with_sigint(_input, session, shell: Optional[Shell] = None):
865
1040
 
866
1041
  instance_colored = (
867
1042
  colored(
868
- shell_attrs['instance_keys'], 'on ' + get_config(
869
- 'shell', 'ansi', 'instance', 'rich', 'style'
870
- )
1043
+ remove_ansi(shell_attrs['instance_keys']),
1044
+ 'on ' + get_config('shell', 'ansi', 'instance', 'rich', 'style')
871
1045
  )
872
1046
  if ANSI
873
1047
  else colored(shell_attrs['instance_keys'], 'on white')
874
1048
  )
875
1049
  repo_colored = (
876
1050
  colored(
877
- shell_attrs['repo_keys'],
1051
+ remove_ansi(shell_attrs['repo_keys']),
878
1052
  'on ' + get_config('shell', 'ansi', 'repo', 'rich', 'style')
879
1053
  )
880
1054
  if ANSI
881
1055
  else colored(shell_attrs['repo_keys'], 'on white')
882
1056
  )
1057
+ executor_colored = (
1058
+ colored(
1059
+ remove_ansi(shell_attrs['executor_keys']),
1060
+ 'on ' + get_config('shell', 'ansi', 'executor', 'rich', 'style')
1061
+ )
1062
+ if ANSI
1063
+ else colored(remove_ansi(shell_attrs['executor_keys']), 'on white')
1064
+ )
1065
+
883
1066
  try:
884
- typ, label = shell_attrs['instance_keys'].split(':')
1067
+ typ, label = shell_attrs['instance_keys'].split(':', maxsplit=1)
885
1068
  connected = typ in connectors and label in connectors[typ]
886
1069
  except Exception as e:
887
1070
  connected = False
@@ -898,8 +1081,12 @@ def input_with_sigint(_input, session, shell: Optional[Shell] = None):
898
1081
  )
899
1082
 
900
1083
  left = (
901
- colored(' Instance: ', 'on white') + instance_colored
902
- + colored(' Repo: ', 'on white') + repo_colored
1084
+ ' '
1085
+ + instance_colored
1086
+ + colored(' | ', 'on white')
1087
+ + executor_colored
1088
+ + colored(' | ', 'on white')
1089
+ + repo_colored
903
1090
  )
904
1091
  right = connection_text
905
1092
  buffer_size = (
@@ -11,10 +11,20 @@ from meerschaum.utils.typing import Callable, Any, Optional, Union, List, Dict,
11
11
  from meerschaum.utils.packages import get_modules_from_package
12
12
  _custom_actions = []
13
13
 
14
+ __all__ = (
15
+ 'get_action',
16
+ 'get_subactions',
17
+ 'make_action',
18
+ 'pre_sync_hook',
19
+ 'post_sync_hook',
20
+ 'get_main_action_name',
21
+ 'get_completer',
22
+ )
23
+
14
24
  def get_subactions(
15
- action: Union[str, List[str]],
16
- _actions: Optional[Dict[str, Callable[[Any], Any]]] = None,
17
- ) -> Dict[str, Callable[[Any], Any]]:
25
+ action: Union[str, List[str]],
26
+ _actions: Optional[Dict[str, Callable[[Any], Any]]] = None,
27
+ ) -> Dict[str, Callable[[Any], Any]]:
18
28
  """
19
29
  Return a dictionary of an action's sub-action functions.
20
30
 
@@ -52,9 +62,9 @@ def get_subactions(
52
62
 
53
63
 
54
64
  def get_action(
55
- action: Union[str, List[str]],
56
- _actions: Optional[Dict[str, Callable[[Any], Any]]] = None,
57
- ) -> Union[Callable[[Any], Any], None]:
65
+ action: Union[str, List[str]],
66
+ _actions: Optional[Dict[str, Callable[[Any], Any]]] = None,
67
+ ) -> Union[Callable[[Any], Any], None]:
58
68
  """
59
69
  Return a function corresponding to the given action list.
60
70
  This may be a custom action with an underscore, in which case, allow for underscores.
@@ -92,6 +102,8 @@ def get_action(
92
102
  if action[0] in _actions:
93
103
  subactions = get_subactions([action[0]], _actions=_actions)
94
104
  if action[1] not in subactions:
105
+ if (action[1] + 's') in subactions:
106
+ return subactions[action[1] + 's']
95
107
  return _actions[action[0]]
96
108
  return subactions[action[1]]
97
109
 
@@ -99,9 +111,9 @@ def get_action(
99
111
 
100
112
 
101
113
  def get_main_action_name(
102
- action: Union[List[str], str],
103
- _actions: Optional[Dict[str, Callable[[Any], SuccessTuple]]] = None,
104
- ) -> Union[str, None]:
114
+ action: Union[List[str], str],
115
+ _actions: Optional[Dict[str, Callable[[Any], SuccessTuple]]] = None,
116
+ ) -> Union[str, None]:
105
117
  """
106
118
  Given an action list, return the name of the main function.
107
119
  For subactions, this will return the root function.
@@ -138,9 +150,9 @@ def get_main_action_name(
138
150
 
139
151
 
140
152
  def get_completer(
141
- action: Union[List[str], str],
142
- _actions: Optional[Dict[str, Callable[[Any], SuccessTuple]]] = None,
143
- ) -> Union[
153
+ action: Union[List[str], str],
154
+ _actions: Optional[Dict[str, Callable[[Any], SuccessTuple]]] = None,
155
+ ) -> Union[
144
156
  Callable[['meerschaum._internal.shell.Shell', str, str, int, int], List[str]], None
145
157
  ]:
146
158
  """Search for a custom completer function for an action."""
@@ -177,10 +189,10 @@ def get_completer(
177
189
 
178
190
 
179
191
  def choose_subaction(
180
- action: Optional[List[str]] = None,
181
- options: Optional[Dict[str, Any]] = None,
182
- **kw
183
- ) -> SuccessTuple:
192
+ action: Optional[List[str]] = None,
193
+ options: Optional[Dict[str, Any]] = None,
194
+ **kw
195
+ ) -> SuccessTuple:
184
196
  """
185
197
  Given a dictionary of options and the standard Meerschaum actions list,
186
198
  check if choice is valid and execute chosen function, else show available
@@ -245,7 +257,7 @@ def _get_subaction_names(action: str, globs: dict = None) -> List[str]:
245
257
  return subactions
246
258
 
247
259
 
248
- def choices_docstring(action: str, globs : Optional[Dict[str, Any]] = None) -> str:
260
+ def choices_docstring(action: str, globs: Optional[Dict[str, Any]] = None) -> str:
249
261
  """
250
262
  Append the an action's available options to the module docstring.
251
263
  This function is to be placed at the bottom of each action module.