meerschaum 2.2.6__py3-none-any.whl → 2.3.0.dev1__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 (61) hide show
  1. meerschaum/__init__.py +4 -1
  2. meerschaum/__main__.py +10 -5
  3. meerschaum/_internal/arguments/_parser.py +44 -15
  4. meerschaum/_internal/entry.py +35 -14
  5. meerschaum/_internal/shell/Shell.py +155 -53
  6. meerschaum/_internal/shell/updates.py +175 -0
  7. meerschaum/actions/api.py +12 -12
  8. meerschaum/actions/attach.py +95 -0
  9. meerschaum/actions/delete.py +35 -26
  10. meerschaum/actions/register.py +19 -5
  11. meerschaum/actions/show.py +119 -148
  12. meerschaum/actions/start.py +85 -75
  13. meerschaum/actions/stop.py +68 -39
  14. meerschaum/actions/sync.py +3 -3
  15. meerschaum/actions/upgrade.py +28 -36
  16. meerschaum/api/_events.py +18 -1
  17. meerschaum/api/_oauth2.py +2 -0
  18. meerschaum/api/_websockets.py +2 -2
  19. meerschaum/api/dash/jobs.py +5 -2
  20. meerschaum/api/routes/__init__.py +1 -0
  21. meerschaum/api/routes/_actions.py +122 -44
  22. meerschaum/api/routes/_jobs.py +340 -0
  23. meerschaum/api/routes/_pipes.py +25 -25
  24. meerschaum/config/_default.py +1 -0
  25. meerschaum/config/_formatting.py +1 -0
  26. meerschaum/config/_paths.py +5 -0
  27. meerschaum/config/_shell.py +84 -67
  28. meerschaum/config/_version.py +1 -1
  29. meerschaum/config/static/__init__.py +9 -0
  30. meerschaum/connectors/__init__.py +9 -11
  31. meerschaum/connectors/api/APIConnector.py +18 -1
  32. meerschaum/connectors/api/_actions.py +60 -71
  33. meerschaum/connectors/api/_jobs.py +260 -0
  34. meerschaum/connectors/api/_misc.py +1 -1
  35. meerschaum/connectors/api/_request.py +13 -9
  36. meerschaum/connectors/parse.py +23 -7
  37. meerschaum/core/Pipe/_sync.py +3 -0
  38. meerschaum/plugins/__init__.py +89 -5
  39. meerschaum/utils/daemon/Daemon.py +333 -149
  40. meerschaum/utils/daemon/FileDescriptorInterceptor.py +19 -10
  41. meerschaum/utils/daemon/RotatingFile.py +18 -7
  42. meerschaum/utils/daemon/StdinFile.py +110 -0
  43. meerschaum/utils/daemon/__init__.py +40 -27
  44. meerschaum/utils/formatting/__init__.py +83 -37
  45. meerschaum/utils/formatting/_jobs.py +118 -51
  46. meerschaum/utils/formatting/_shell.py +6 -0
  47. meerschaum/utils/jobs/_Job.py +684 -0
  48. meerschaum/utils/jobs/__init__.py +245 -0
  49. meerschaum/utils/misc.py +18 -17
  50. meerschaum/utils/packages/__init__.py +21 -15
  51. meerschaum/utils/packages/_packages.py +2 -2
  52. meerschaum/utils/prompt.py +20 -7
  53. meerschaum/utils/schedule.py +21 -15
  54. {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dev1.dist-info}/METADATA +9 -9
  55. {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dev1.dist-info}/RECORD +61 -54
  56. {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dev1.dist-info}/WHEEL +1 -1
  57. {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dev1.dist-info}/LICENSE +0 -0
  58. {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dev1.dist-info}/NOTICE +0 -0
  59. {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dev1.dist-info}/entry_points.txt +0 -0
  60. {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dev1.dist-info}/top_level.txt +0 -0
  61. {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dev1.dist-info}/zip-safe +0 -0
meerschaum/__init__.py CHANGED
@@ -20,15 +20,16 @@ limitations under the License.
20
20
 
21
21
  import atexit
22
22
  from meerschaum.utils.typing import SuccessTuple
23
+ from meerschaum.utils.packages import attempt_import
23
24
  from meerschaum.core.Pipe import Pipe
24
25
  from meerschaum.plugins import Plugin
25
26
  from meerschaum.utils.venv import Venv
27
+ from meerschaum.utils.jobs import Job
26
28
  from meerschaum.connectors import get_connector, Connector, make_connector
27
29
  from meerschaum.utils import get_pipes
28
30
  from meerschaum.utils.formatting import pprint
29
31
  from meerschaum._internal.docs import index as __doc__
30
32
  from meerschaum.config import __version__, get_config
31
- from meerschaum.utils.packages import attempt_import
32
33
  from meerschaum.__main__ import _close_pools
33
34
 
34
35
  atexit.register(_close_pools)
@@ -42,6 +43,8 @@ __all__ = (
42
43
  "Plugin",
43
44
  "Venv",
44
45
  "Plugin",
46
+ "Job",
47
+ "Daemon",
45
48
  "pprint",
46
49
  "attempt_import",
47
50
  "actions",
meerschaum/__main__.py CHANGED
@@ -19,9 +19,15 @@ See the License for the specific language governing permissions and
19
19
  limitations under the License.
20
20
  """
21
21
 
22
- import sys, os, copy
22
+ import sys
23
+ import os
24
+ import copy
23
25
 
24
- def main(sysargs: list = None) -> None:
26
+ from meerschaum.utils.typing import List, Optional
27
+ from meerschaum.utils.formatting import print_tuple as _print_tuple
28
+
29
+
30
+ def main(sysargs: Optional[List[str]] = None) -> None:
25
31
  """Main CLI entry point."""
26
32
  if sysargs is None:
27
33
  sysargs = copy.deepcopy(sys.argv[1:])
@@ -41,7 +47,7 @@ def main(sysargs: list = None) -> None:
41
47
 
42
48
  if ('-d' in sysargs or '--daemon' in sysargs) and ('stack' not in sysargs):
43
49
  from meerschaum.utils.daemon import daemon_entry
44
- daemon_entry(sysargs)
50
+ _print_tuple(daemon_entry(sysargs), upper_padding=1)
45
51
  return _exit(old_cwd=old_cwd)
46
52
 
47
53
  from meerschaum._internal.entry import entry, get_shell
@@ -57,8 +63,7 @@ def main(sysargs: list = None) -> None:
57
63
  return_tuple = entry(sysargs)
58
64
  rc = 0
59
65
  if isinstance(return_tuple, tuple) and '--nopretty' not in sysargs:
60
- from meerschaum.utils.formatting import print_tuple
61
- print_tuple(return_tuple, upper_padding=1)
66
+ _print_tuple(return_tuple, upper_padding=1)
62
67
  rc = 0 if (return_tuple[0] is True) else 1
63
68
 
64
69
  return _exit(rc, old_cwd=old_cwd)
@@ -62,7 +62,24 @@ def parse_datetime(dt_str: str) -> Union[datetime, int, str]:
62
62
  return dt
63
63
 
64
64
 
65
- def parse_help(sysargs : Union[List[str], Dict[str, Any]]) -> None:
65
+ def parse_executor_keys(executor_keys_str: str) -> Union[str, None]:
66
+ """
67
+ Ensure that only API keys are provided for executor_keys.
68
+ """
69
+ if executor_keys_str == 'local':
70
+ return executor_keys_str
71
+
72
+ if executor_keys_str.lower() == 'none':
73
+ return 'local'
74
+
75
+ if not executor_keys_str.startswith('api:'):
76
+ from meerschaum.utils.warnings import error
77
+ error(f"Invalid exectutor keys '{executor_keys_str}'.", stack=False)
78
+
79
+ return executor_keys_str
80
+
81
+
82
+ def parse_help(sysargs: Union[List[str], Dict[str, Any]]) -> None:
66
83
  """Parse the `--help` flag to determine which help message to print."""
67
84
  from meerschaum._internal.arguments._parse_arguments import parse_arguments, parse_line
68
85
  from meerschaum.actions import actions, get_subactions
@@ -135,6 +152,7 @@ _seen_plugin_args = {}
135
152
 
136
153
  groups = {}
137
154
  groups['actions'] = parser.add_argument_group(title='Actions options')
155
+ groups['jobs'] = parser.add_argument_group(title='Jobs options')
138
156
  groups['pipes'] = parser.add_argument_group(title='Pipes options')
139
157
  groups['sync'] = parser.add_argument_group(title='Sync options')
140
158
  groups['api'] = parser.add_argument_group(title='API options')
@@ -166,18 +184,25 @@ groups['actions'].add_argument(
166
184
  help="Automatically choose the defaults answers to questions. Does not result in data loss.",
167
185
  )
168
186
  groups['actions'].add_argument(
169
- '-d', '--daemon', action='store_true',
170
- help = "Run an action as a background daemon."
187
+ '-A', '--sub-args', nargs=argparse.REMAINDER,
188
+ help = (
189
+ "Provide a list of arguments for subprocesses. " +
190
+ "You can also type sub-arguments in [] instead." +
191
+ " E.g. `stack -A='--version'`, `ls [-lh]`, `echo -A these are sub-arguments`"
192
+ )
171
193
  )
172
- groups['actions'].add_argument(
173
- '--rm', action='store_true', help="Delete a job once it has finished executing."
194
+
195
+ ### Jobs options
196
+ groups['jobs'].add_argument(
197
+ '-d', '--daemon', action='store_true',
198
+ help = "Run an action as a background daemon to create a job."
174
199
  )
175
- groups['actions'].add_argument(
200
+ groups['jobs'].add_argument(
176
201
  '--name', '--job-name', type=parse_name, help=(
177
202
  "Assign a name to a job. If no name is provided, a random name will be assigned."
178
203
  ),
179
204
  )
180
- groups['actions'].add_argument(
205
+ groups['jobs'].add_argument(
181
206
  '-s', '--schedule', '--cron', type=str,
182
207
  help = (
183
208
  "Continue executing the action according to a schedule (e.g. 'every 1 seconds'). \n"
@@ -185,15 +210,19 @@ groups['actions'].add_argument(
185
210
  + "https://red-engine.readthedocs.io/en/stable/condition_syntax/index.html"
186
211
  )
187
212
  )
188
- groups['actions'].add_argument(
189
- '-A', '--sub-args', nargs=argparse.REMAINDER,
190
- help = (
191
- "Provide a list of arguments for subprocesses. " +
192
- "You can also type sub-arguments in [] instead." +
193
- " E.g. `stack -A='--version'`, `ls [-lh]`, `echo -A these are sub-arguments`"
194
- )
213
+ groups['jobs'].add_argument(
214
+ '--restart', action='store_true',
215
+ help=("Restart a job if not stopped manually."),
216
+ )
217
+ groups['jobs'].add_argument(
218
+ '--executor-keys', '--executor', '-e', type=parse_executor_keys,
219
+ help=(
220
+ "Remotely execute jobs on an API instance."
221
+ ),
222
+ )
223
+ groups['jobs'].add_argument(
224
+ '--rm', action='store_true', help="Delete a job once it has finished executing."
195
225
  )
196
-
197
226
  ### Pipes options
198
227
  groups['pipes'].add_argument(
199
228
  '-c', '-C', '--connector-keys', nargs='+',
@@ -8,20 +8,15 @@ The entry point for launching Meerschaum actions.
8
8
  """
9
9
 
10
10
  from __future__ import annotations
11
- from meerschaum.utils.typing import SuccessTuple, List, Optional, Dict
11
+ from meerschaum.utils.typing import SuccessTuple, List, Optional, Dict, Callable, Any
12
12
 
13
13
  def entry(sysargs: Optional[List[str]] = None) -> SuccessTuple:
14
- """Parse arguments and launch a Meerschaum action.
15
- The `action` list removes the first element.
16
-
17
- Examples of action:
18
- 'show actions' -> ['actions']
19
- 'show' -> []
14
+ """
15
+ Parse arguments and launch a Meerschaum action.
20
16
 
21
17
  Returns
22
18
  -------
23
- A `SuccessTuple` indicating success. If `schedule` is provided, this will never return.
24
-
19
+ A `SuccessTuple` indicating success.
25
20
  """
26
21
  from meerschaum._internal.arguments import parse_arguments
27
22
  from meerschaum.config.static import STATIC_CONFIG
@@ -51,14 +46,15 @@ def entry(sysargs: Optional[List[str]] = None) -> SuccessTuple:
51
46
 
52
47
 
53
48
  def entry_with_args(
54
- _actions: Optional[Dict[str, Callable[[Any], SuccessTuple]]] = None,
55
- **kw
56
- ) -> SuccessTuple:
49
+ _actions: Optional[Dict[str, Callable[[Any], SuccessTuple]]] = None,
50
+ **kw
51
+ ) -> SuccessTuple:
57
52
  """Execute a Meerschaum action with keyword arguments.
58
53
  Use `_entry()` for parsing sysargs before executing.
59
54
  """
60
55
  import sys
61
56
  import functools
57
+ import inspect
62
58
  from meerschaum.actions import get_action, get_main_action_name
63
59
  from meerschaum._internal.arguments import remove_leading_action
64
60
  from meerschaum.utils.venv import Venv, active_venvs, deactivate_venv
@@ -72,6 +68,25 @@ def entry_with_args(
72
68
  ):
73
69
  return get_shell().cmdloop()
74
70
 
71
+ skip_schedule = False
72
+
73
+ executor_keys = kw.get('executor_keys', None)
74
+ if executor_keys is None:
75
+ executor_keys = 'local'
76
+
77
+ if executor_keys.startswith('api:'):
78
+ intended_action_function = get_action(kw['action'], _actions=_actions)
79
+ function_accepts_executor_keys = (
80
+ 'executor_keys' in inspect.signature(intended_action_function).parameters
81
+ if intended_action_function is not None
82
+ else False
83
+ )
84
+ if not function_accepts_executor_keys:
85
+ api_label = executor_keys.split(':')[-1]
86
+ kw['action'].insert(0, 'api')
87
+ kw['action'].insert(1, api_label)
88
+ skip_schedule = True
89
+
75
90
  action_function = get_action(kw['action'], _actions=_actions)
76
91
 
77
92
  ### If action does not exist, execute in a subshell.
@@ -86,7 +101,6 @@ def entry_with_args(
86
101
  ) else None
87
102
  )
88
103
 
89
- skip_schedule = False
90
104
  if (
91
105
  kw['action']
92
106
  and kw['action'][0] == 'start'
@@ -96,7 +110,14 @@ def entry_with_args(
96
110
 
97
111
  kw['action'] = remove_leading_action(kw['action'], _actions=_actions)
98
112
 
99
- do_action = functools.partial(_do_action_wrapper, action_function, plugin_name, **kw)
113
+ do_action = functools.partial(
114
+ _do_action_wrapper,
115
+ action_function,
116
+ plugin_name,
117
+ **kw
118
+ )
119
+
120
+
100
121
  if kw.get('schedule', None) and not skip_schedule:
101
122
  from meerschaum.utils.schedule import schedule_function
102
123
  from meerschaum.utils.misc import interval_str
@@ -28,7 +28,7 @@ prompt_toolkit = attempt_import('prompt_toolkit', lazy=False, warn=False, instal
28
28
  from meerschaum._internal.shell.ValidAutoSuggest import ValidAutoSuggest
29
29
  from meerschaum._internal.shell.ShellCompleter import ShellCompleter
30
30
  _clear_screen = get_config('shell', 'clear_screen', patch=True)
31
- from meerschaum.utils.misc import string_width
31
+ from meerschaum.utils.misc import string_width, remove_ansi
32
32
 
33
33
  patch = True
34
34
  ### remove default cmd2 commands
@@ -56,7 +56,7 @@ hidden_commands = {
56
56
  'ipy',
57
57
  }
58
58
  reserved_completers = {
59
- 'instance', 'repo'
59
+ 'instance', 'repo', 'executor',
60
60
  }
61
61
 
62
62
  ### To handle dynamic reloading, store shell attributes externally.
@@ -95,8 +95,8 @@ def _insert_shell_actions(
95
95
  setattr(_shell_class, 'complete_' + a, completer)
96
96
 
97
97
  def _completer_wrapper(
98
- target: Callable[[Any], List[str]]
99
- ) -> Callable[['meerschaum._internal.shell.Shell', str, str, int, int], Any]:
98
+ target: Callable[[Any], List[str]]
99
+ ) -> Callable[['meerschaum._internal.shell.Shell', str, str, int, int], Any]:
100
100
  """
101
101
  Wrapper for `complete_` functions so they can instead use Meerschaum arguments.
102
102
  """
@@ -125,13 +125,13 @@ def _completer_wrapper(
125
125
 
126
126
 
127
127
  def default_action_completer(
128
- text: Optional[str] = None,
129
- line: Optional[str] = None,
130
- begin_index: Optional[int] = None,
131
- end_index: Optional[int] = None,
132
- action: Optional[List[str]] = None,
133
- **kw: Any
134
- ) -> List[str]:
128
+ text: Optional[str] = None,
129
+ line: Optional[str] = None,
130
+ begin_index: Optional[int] = None,
131
+ end_index: Optional[int] = None,
132
+ action: Optional[List[str]] = None,
133
+ **kw: Any
134
+ ) -> List[str]:
135
135
  """
136
136
  Search for subactions by default. This may be overridden by each action.
137
137
  """
@@ -206,13 +206,11 @@ def get_shell_intro(with_color: bool = True) -> str:
206
206
  """
207
207
  from meerschaum.utils.formatting import CHARSET, ANSI, colored
208
208
  intro = get_config('shell', CHARSET, 'intro', patch=patch)
209
- intro += '\n' + ''.join(
210
- [' '
211
- for i in range(
212
- string_width(intro) - len('v' + version)
213
- )
214
- ]
215
- ) + 'v' + version
209
+ intro += (
210
+ '\n'
211
+ + (' ' * (string_width(intro) - len('v' + version)))
212
+ + f'v{version}'
213
+ )
216
214
 
217
215
  if not with_color or not ANSI:
218
216
  return intro
@@ -225,10 +223,10 @@ def get_shell_intro(with_color: bool = True) -> str:
225
223
 
226
224
  class Shell(cmd.Cmd):
227
225
  def __init__(
228
- self,
229
- actions: Optional[Dict[str, Any]] = None,
230
- sysargs: Optional[List[str]] = None
231
- ):
226
+ self,
227
+ actions: Optional[Dict[str, Any]] = None,
228
+ sysargs: Optional[List[str]] = None
229
+ ):
232
230
  """
233
231
  Customize the CLI from configuration
234
232
  """
@@ -249,11 +247,11 @@ class Shell(cmd.Cmd):
249
247
 
250
248
  from meerschaum.config._paths import SHELL_HISTORY_PATH
251
249
  shell_attrs['session'] = prompt_toolkit_shortcuts.PromptSession(
252
- history = prompt_toolkit_history.FileHistory(str(SHELL_HISTORY_PATH)),
253
- auto_suggest = ValidAutoSuggest(),
254
- completer = ShellCompleter(),
255
- complete_while_typing = True,
256
- reserve_space_for_menu = False,
250
+ history=prompt_toolkit_history.FileHistory(SHELL_HISTORY_PATH.as_posix()),
251
+ auto_suggest=ValidAutoSuggest(),
252
+ completer=ShellCompleter(),
253
+ complete_while_typing=True,
254
+ reserve_space_for_menu=False,
257
255
  )
258
256
 
259
257
  try: ### try cmd2 arguments first
@@ -283,6 +281,7 @@ class Shell(cmd.Cmd):
283
281
  shell_attrs['_sysargs'] = sysargs
284
282
  shell_attrs['_actions']['instance'] = self.do_instance
285
283
  shell_attrs['_actions']['repo'] = self.do_repo
284
+ shell_attrs['_actions']['executor'] = self.do_executor
286
285
  shell_attrs['_actions']['debug'] = self.do_debug
287
286
  shell_attrs['_update_bottom_toolbar'] = True
288
287
  shell_attrs['_old_bottom_toolbar'] = ''
@@ -297,19 +296,25 @@ class Shell(cmd.Cmd):
297
296
  except Exception as e:
298
297
  pass
299
298
 
299
+ ### Finally, spawn the version update thread.
300
+ from meerschaum._internal.shell.updates import run_version_check_thread
301
+ self._update_thread = run_version_check_thread(debug=shell_attrs.get('debug', False))
302
+
303
+
300
304
  def load_config(self, instance: Optional[str] = None):
301
305
  """
302
306
  Set attributes from the shell configuration.
303
307
  """
304
308
  from meerschaum.utils.misc import remove_ansi
305
309
  from meerschaum.utils.formatting import CHARSET, ANSI, colored
306
-
310
+ from meerschaum._internal.shell.updates import get_update_message
311
+
307
312
  if shell_attrs.get('intro', None) != '':
308
313
  self.intro = (
309
314
  get_shell_intro(with_color=False)
310
315
  if shell_attrs.get('intro', None) != ''
311
316
  else ""
312
- )
317
+ ) + get_update_message()
313
318
 
314
319
  shell_attrs['intro'] = self.intro
315
320
  shell_attrs['_prompt'] = get_config('shell', CHARSET, 'prompt', patch=patch)
@@ -333,6 +338,12 @@ class Shell(cmd.Cmd):
333
338
  shell_attrs['instance_keys'] = remove_ansi(str(instance))
334
339
  if shell_attrs.get('repo_keys', None) is None:
335
340
  shell_attrs['repo_keys'] = get_config('meerschaum', 'default_repository', patch=patch)
341
+ if shell_attrs.get('executor_keys', None) is None:
342
+ shell_attrs['executor_keys'] = get_config(
343
+ 'meerschaum', 'default_executor',
344
+ patch=patch,
345
+ ) or 'local'
346
+
336
347
  ### this will be updated later in update_prompt ONLY IF {username} is in the prompt
337
348
  shell_attrs['username'] = ''
338
349
 
@@ -358,14 +369,19 @@ class Shell(cmd.Cmd):
358
369
  def insert_actions(self):
359
370
  from meerschaum.actions import actions
360
371
 
361
- def update_prompt(self, instance: Optional[str] = None, username: Optional[str] = None):
372
+ def update_prompt(
373
+ self,
374
+ instance: Optional[str] = None,
375
+ username: Optional[str] = None,
376
+ executor_keys: Optional[str] = None,
377
+ ):
362
378
  from meerschaum.utils.formatting import ANSI, colored
363
379
  from meerschaum._internal.entry import _shell, get_shell
364
380
 
365
381
  cmd.__builtins__['input'] = input_with_sigint(
366
382
  _old_input,
367
383
  shell_attrs['session'],
368
- shell = self,
384
+ shell=self,
369
385
  )
370
386
  prompt = shell_attrs['_prompt']
371
387
  mask = prompt
@@ -391,7 +407,8 @@ class Shell(cmd.Cmd):
391
407
  from meerschaum.connectors.sql import SQLConnector
392
408
  try:
393
409
  conn_attrs = parse_instance_keys(
394
- remove_ansi(shell_attrs['instance_keys']), construct=False
410
+ remove_ansi(shell_attrs['instance_keys']),
411
+ construct=False,
395
412
  )
396
413
  if 'username' not in conn_attrs:
397
414
  if 'uri' in conn_attrs:
@@ -405,12 +422,27 @@ class Shell(cmd.Cmd):
405
422
  if username is None:
406
423
  username = '(no username)'
407
424
  shell_attrs['username'] = (
408
- username if not ANSI else
409
- colored(username, **get_config('shell', 'ansi', 'username', 'rich'))
425
+ username
426
+ if not ANSI
427
+ else colored(username, **get_config('shell', 'ansi', 'username', 'rich'))
410
428
  )
411
429
  prompt = prompt.replace('{username}', shell_attrs['username'])
412
430
  mask = mask.replace('{username}', ''.join(['\0' for c in '{username}']))
413
431
 
432
+ if '{executor_keys}' in shell_attrs['_prompt']:
433
+ if executor_keys is None:
434
+ executor_keys = shell_attrs.get('executor_keys', None) or 'local'
435
+ shell_attrs['executor_keys'] = (
436
+ executor_keys
437
+ if not ANSI
438
+ else colored(
439
+ remove_ansi(executor_keys),
440
+ **get_config('shell', 'ansi', 'executor', 'rich')
441
+ )
442
+ )
443
+ prompt = prompt.replace('{executor_keys}', shell_attrs['executor_keys'])
444
+ mask = mask.replace('{executor_keys}', ''.join(['\0' for c in '{executor_keys}']))
445
+
414
446
  remainder_prompt = list(shell_attrs['_prompt'])
415
447
  for i, c in enumerate(mask):
416
448
  if c != '\0':
@@ -418,10 +450,13 @@ class Shell(cmd.Cmd):
418
450
  if ANSI:
419
451
  _c = colored(_c, **get_config('shell', 'ansi', 'prompt', 'rich'))
420
452
  remainder_prompt[i] = _c
453
+
421
454
  self.prompt = ''.join(remainder_prompt).replace(
422
455
  '{username}', shell_attrs['username']
423
456
  ).replace(
424
457
  '{instance}', shell_attrs['instance']
458
+ ).replace(
459
+ '{executor_keys}', shell_attrs['executor_keys']
425
460
  )
426
461
  shell_attrs['prompt'] = self.prompt
427
462
  ### flush stdout
@@ -521,6 +556,9 @@ class Shell(cmd.Cmd):
521
556
  if 'repository' not in args and main_action_name != 'api':
522
557
  args['repository'] = str(shell_attrs['repo_keys'])
523
558
 
559
+ if 'executor_keys' not in args:
560
+ args['executor_keys'] = remove_ansi(str(shell_attrs['executor_keys']))
561
+
524
562
  ### parse out empty strings
525
563
  if args['action'][0].strip("\"'") == '':
526
564
  self.emptyline()
@@ -565,12 +603,12 @@ class Shell(cmd.Cmd):
565
603
  if stop:
566
604
  return True
567
605
 
568
- def do_pass(self, line):
606
+ def do_pass(self, line, executor_keys=None):
569
607
  """
570
608
  Do nothing.
571
609
  """
572
610
 
573
- def do_debug(self, action: Optional[List[str]] = None, **kw):
611
+ def do_debug(self, action: Optional[List[str]] = None, executor_keys=None, **kw):
574
612
  """
575
613
  Toggle the shell's debug mode.
576
614
  If debug = on, append `--debug` to all commands.
@@ -601,9 +639,10 @@ class Shell(cmd.Cmd):
601
639
 
602
640
  def do_instance(
603
641
  self,
604
- action : Optional[List[str]] = None,
605
- debug : bool = False,
606
- **kw : Any
642
+ action: Optional[List[str]] = None,
643
+ executor_keys=None,
644
+ debug: bool = False,
645
+ **kw: Any
607
646
  ) -> SuccessTuple:
608
647
  """
609
648
  Temporarily set a default Meerschaum instance for the duration of the shell.
@@ -672,11 +711,12 @@ class Shell(cmd.Cmd):
672
711
 
673
712
 
674
713
  def do_repo(
675
- self,
676
- action: Optional[List[str]] = None,
677
- debug: bool = False,
678
- **kw: Any
679
- ) -> SuccessTuple:
714
+ self,
715
+ action: Optional[List[str]] = None,
716
+ executor_keys=None,
717
+ debug: bool = False,
718
+ **kw: Any
719
+ ) -> SuccessTuple:
680
720
  """
681
721
  Temporarily set a default Meerschaum repository for the duration of the shell.
682
722
  The default repository (mrsm.io) is loaded from the Meerschaum configuraton file
@@ -723,9 +763,59 @@ class Shell(cmd.Cmd):
723
763
  return True, "Success"
724
764
 
725
765
  def complete_repo(self, *args) -> List[str]:
726
- return self.complete_instance(*args)
766
+ results = self.complete_instance(*args)
767
+ return [result for result in results if result.startswith('api:')]
768
+
769
+ def do_executor(
770
+ self,
771
+ action: Optional[List[str]] = None,
772
+ executor_keys=None,
773
+ debug: bool = False,
774
+ **kw: Any
775
+ ) -> SuccessTuple:
776
+ """
777
+ Temporarily set a default Meerschaum executor for the duration of the shell.
778
+
779
+ You can change the default repository with `edit config`.
780
+
781
+ Usage:
782
+ executor {API label}
783
+
784
+ Examples:
785
+ ### reset to default executor
786
+ executor
787
+
788
+ ### set the executor to 'api:main'
789
+ executor api:main
790
+
791
+ Note that executors are API instances.
792
+ """
793
+ from meerschaum import get_connector
794
+ from meerschaum.connectors.parse import parse_executor_keys
795
+ from meerschaum.utils.warnings import warn, info
796
+
797
+ if action is None:
798
+ action = []
799
+
800
+ try:
801
+ executor_keys = action[0]
802
+ except (IndexError, AttributeError):
803
+ executor_keys = ''
804
+ if executor_keys == '':
805
+ executor_keys = get_config('meerschaum', 'default_executor') or 'local'
806
+
807
+ conn = parse_executor_keys(executor_keys, debug=debug)
808
+
809
+ shell_attrs['executor_keys'] = str(conn)
810
+
811
+ info(f"Default executor for the current shell: {executor_keys}")
812
+ return True, "Success"
727
813
 
728
- def do_help(self, line: str) -> List[str]:
814
+ def complete_executor(self, *args) -> List[str]:
815
+ results = self.complete_instance(*args)
816
+ return ['local'] + [result for result in results if result.startswith('api:')]
817
+
818
+ def do_help(self, line: str, executor_keys=None) -> List[str]:
729
819
  """
730
820
  Show help for Meerschaum actions.
731
821
 
@@ -796,7 +886,7 @@ class Shell(cmd.Cmd):
796
886
  possibilities.append(name.replace('do_', ''))
797
887
  return possibilities
798
888
 
799
- def do_exit(self, params) -> True:
889
+ def do_exit(self, params, executor_keys=None) -> True:
800
890
  """
801
891
  Exit the Meerschaum shell.
802
892
  """
@@ -861,21 +951,29 @@ def input_with_sigint(_input, session, shell: Optional[Shell] = None):
861
951
 
862
952
  instance_colored = (
863
953
  colored(
864
- shell_attrs['instance_keys'], 'on ' + get_config(
865
- 'shell', 'ansi', 'instance', 'rich', 'style'
866
- )
954
+ remove_ansi(shell_attrs['instance_keys']),
955
+ 'on ' + get_config('shell', 'ansi', 'instance', 'rich', 'style')
867
956
  )
868
957
  if ANSI
869
958
  else colored(shell_attrs['instance_keys'], 'on white')
870
959
  )
871
960
  repo_colored = (
872
961
  colored(
873
- shell_attrs['repo_keys'],
962
+ remove_ansi(shell_attrs['repo_keys']),
874
963
  'on ' + get_config('shell', 'ansi', 'repo', 'rich', 'style')
875
964
  )
876
965
  if ANSI
877
966
  else colored(shell_attrs['repo_keys'], 'on white')
878
967
  )
968
+ executor_colored = (
969
+ colored(
970
+ remove_ansi(shell_attrs['executor_keys']),
971
+ 'on ' + get_config('shell', 'ansi', 'executor', 'rich', 'style')
972
+ )
973
+ if ANSI
974
+ else colored(remove_ansi(shell_attrs['executor_keys']), 'on white')
975
+ )
976
+
879
977
  try:
880
978
  typ, label = shell_attrs['instance_keys'].split(':')
881
979
  connected = typ in connectors and label in connectors[typ]
@@ -894,8 +992,12 @@ def input_with_sigint(_input, session, shell: Optional[Shell] = None):
894
992
  )
895
993
 
896
994
  left = (
897
- colored(' Instance: ', 'on white') + instance_colored
898
- + colored(' Repo: ', 'on white') + repo_colored
995
+ ' '
996
+ + instance_colored
997
+ + colored(' | ', 'on white')
998
+ + executor_colored
999
+ + colored(' | ', 'on white')
1000
+ + repo_colored
899
1001
  )
900
1002
  right = connection_text
901
1003
  buffer_size = (