meerschaum 3.0.0rc4__py3-none-any.whl → 3.0.0rc8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. meerschaum/_internal/arguments/_parser.py +14 -2
  2. meerschaum/_internal/cli/__init__.py +6 -0
  3. meerschaum/_internal/cli/daemons.py +103 -0
  4. meerschaum/_internal/cli/entry.py +220 -0
  5. meerschaum/_internal/cli/workers.py +435 -0
  6. meerschaum/_internal/docs/index.py +1 -2
  7. meerschaum/_internal/entry.py +44 -8
  8. meerschaum/_internal/shell/Shell.py +115 -24
  9. meerschaum/_internal/shell/__init__.py +4 -1
  10. meerschaum/_internal/static.py +4 -1
  11. meerschaum/_internal/term/TermPageHandler.py +1 -2
  12. meerschaum/_internal/term/__init__.py +40 -6
  13. meerschaum/_internal/term/tools.py +33 -8
  14. meerschaum/actions/__init__.py +6 -4
  15. meerschaum/actions/api.py +39 -11
  16. meerschaum/actions/attach.py +1 -0
  17. meerschaum/actions/delete.py +4 -2
  18. meerschaum/actions/edit.py +27 -8
  19. meerschaum/actions/login.py +8 -8
  20. meerschaum/actions/register.py +13 -7
  21. meerschaum/actions/reload.py +22 -5
  22. meerschaum/actions/restart.py +14 -0
  23. meerschaum/actions/show.py +69 -4
  24. meerschaum/actions/start.py +135 -14
  25. meerschaum/actions/stop.py +36 -3
  26. meerschaum/actions/sync.py +6 -1
  27. meerschaum/api/__init__.py +35 -13
  28. meerschaum/api/_events.py +2 -2
  29. meerschaum/api/_oauth2.py +47 -4
  30. meerschaum/api/dash/callbacks/dashboard.py +29 -0
  31. meerschaum/api/dash/callbacks/jobs.py +3 -2
  32. meerschaum/api/dash/callbacks/login.py +10 -1
  33. meerschaum/api/dash/callbacks/register.py +9 -2
  34. meerschaum/api/dash/pages/login.py +2 -2
  35. meerschaum/api/dash/pipes.py +72 -36
  36. meerschaum/api/dash/webterm.py +14 -6
  37. meerschaum/api/models/_pipes.py +7 -1
  38. meerschaum/api/resources/static/js/terminado.js +3 -0
  39. meerschaum/api/resources/static/js/xterm-addon-unicode11.js +2 -0
  40. meerschaum/api/resources/templates/termpage.html +1 -0
  41. meerschaum/api/routes/_jobs.py +23 -11
  42. meerschaum/api/routes/_login.py +73 -5
  43. meerschaum/api/routes/_pipes.py +6 -4
  44. meerschaum/api/routes/_webterm.py +3 -3
  45. meerschaum/config/__init__.py +60 -13
  46. meerschaum/config/_default.py +89 -61
  47. meerschaum/config/_edit.py +10 -8
  48. meerschaum/config/_formatting.py +2 -0
  49. meerschaum/config/_patch.py +4 -2
  50. meerschaum/config/_paths.py +127 -12
  51. meerschaum/config/_read_config.py +32 -12
  52. meerschaum/config/_version.py +1 -1
  53. meerschaum/config/environment.py +262 -0
  54. meerschaum/config/stack/__init__.py +7 -5
  55. meerschaum/connectors/_Connector.py +1 -2
  56. meerschaum/connectors/__init__.py +37 -2
  57. meerschaum/connectors/api/_APIConnector.py +1 -1
  58. meerschaum/connectors/api/_jobs.py +11 -0
  59. meerschaum/connectors/api/_pipes.py +7 -1
  60. meerschaum/connectors/instance/_plugins.py +9 -1
  61. meerschaum/connectors/instance/_tokens.py +20 -3
  62. meerschaum/connectors/instance/_users.py +8 -1
  63. meerschaum/connectors/parse.py +1 -1
  64. meerschaum/connectors/sql/_create_engine.py +3 -0
  65. meerschaum/connectors/sql/_pipes.py +93 -79
  66. meerschaum/connectors/sql/_users.py +8 -1
  67. meerschaum/connectors/valkey/_ValkeyConnector.py +3 -3
  68. meerschaum/connectors/valkey/_pipes.py +7 -5
  69. meerschaum/core/Pipe/__init__.py +45 -71
  70. meerschaum/core/Pipe/_attributes.py +66 -90
  71. meerschaum/core/Pipe/_cache.py +555 -0
  72. meerschaum/core/Pipe/_clear.py +0 -11
  73. meerschaum/core/Pipe/_data.py +0 -50
  74. meerschaum/core/Pipe/_deduplicate.py +0 -13
  75. meerschaum/core/Pipe/_delete.py +12 -21
  76. meerschaum/core/Pipe/_drop.py +11 -23
  77. meerschaum/core/Pipe/_dtypes.py +1 -1
  78. meerschaum/core/Pipe/_index.py +8 -14
  79. meerschaum/core/Pipe/_sync.py +12 -18
  80. meerschaum/core/Plugin/_Plugin.py +7 -1
  81. meerschaum/core/Token/_Token.py +1 -1
  82. meerschaum/core/User/_User.py +1 -2
  83. meerschaum/jobs/_Executor.py +88 -4
  84. meerschaum/jobs/_Job.py +146 -36
  85. meerschaum/jobs/systemd.py +7 -2
  86. meerschaum/plugins/__init__.py +277 -81
  87. meerschaum/utils/daemon/Daemon.py +197 -42
  88. meerschaum/utils/daemon/FileDescriptorInterceptor.py +0 -1
  89. meerschaum/utils/daemon/RotatingFile.py +63 -36
  90. meerschaum/utils/daemon/StdinFile.py +53 -13
  91. meerschaum/utils/daemon/__init__.py +18 -5
  92. meerschaum/utils/daemon/_names.py +6 -3
  93. meerschaum/utils/debug.py +34 -4
  94. meerschaum/utils/dtypes/__init__.py +5 -1
  95. meerschaum/utils/formatting/__init__.py +4 -1
  96. meerschaum/utils/formatting/_jobs.py +1 -1
  97. meerschaum/utils/formatting/_pipes.py +47 -46
  98. meerschaum/utils/formatting/_shell.py +33 -9
  99. meerschaum/utils/misc.py +22 -38
  100. meerschaum/utils/packages/__init__.py +15 -13
  101. meerschaum/utils/packages/_packages.py +1 -0
  102. meerschaum/utils/pipes.py +33 -5
  103. meerschaum/utils/process.py +1 -1
  104. meerschaum/utils/prompt.py +172 -143
  105. meerschaum/utils/sql.py +12 -2
  106. meerschaum/utils/threading.py +42 -0
  107. meerschaum/utils/venv/__init__.py +2 -0
  108. meerschaum/utils/warnings.py +19 -13
  109. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/METADATA +3 -1
  110. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/RECORD +116 -110
  111. meerschaum/config/_environment.py +0 -145
  112. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/WHEEL +0 -0
  113. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/entry_points.txt +0 -0
  114. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/licenses/LICENSE +0 -0
  115. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/licenses/NOTICE +0 -0
  116. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/top_level.txt +0 -0
  117. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/zip-safe +0 -0
meerschaum/jobs/_Job.py CHANGED
@@ -12,8 +12,8 @@ import shlex
12
12
  import asyncio
13
13
  import pathlib
14
14
  import sys
15
+ import json
15
16
  import traceback
16
- from functools import partial
17
17
  from datetime import datetime, timezone
18
18
 
19
19
  import meerschaum as mrsm
@@ -25,6 +25,7 @@ from meerschaum.utils.warnings import warn
25
25
  from meerschaum.config.paths import LOGS_RESOURCES_PATH
26
26
  from meerschaum.config import get_config
27
27
  from meerschaum._internal.static import STATIC_CONFIG
28
+ from meerschaum.utils.formatting._shell import clear_screen, flush_stdout
28
29
 
29
30
  if TYPE_CHECKING:
30
31
  from meerschaum.jobs._Executor import Executor
@@ -39,6 +40,10 @@ RESTART_FLAGS: List[str] = [
39
40
  '--schedule',
40
41
  '--cron',
41
42
  ]
43
+ STOP_TOKEN: str = STATIC_CONFIG['jobs']['stop_token']
44
+ CLEAR_TOKEN: str = STATIC_CONFIG['jobs']['clear_token']
45
+ FLUSH_TOKEN: str = STATIC_CONFIG['jobs']['flush_token']
46
+
42
47
 
43
48
  class StopMonitoringLogs(Exception):
44
49
  """
@@ -46,6 +51,21 @@ class StopMonitoringLogs(Exception):
46
51
  """
47
52
 
48
53
 
54
+ def _default_stdout_callback(line: str):
55
+ if line == '\n' or line.startswith(FLUSH_TOKEN):
56
+ flush_stdout()
57
+ return
58
+
59
+ if CLEAR_TOKEN in line:
60
+ clear_screen()
61
+ return
62
+
63
+ if STOP_TOKEN in line:
64
+ return
65
+
66
+ print(line, end='', flush=True)
67
+
68
+
49
69
  class Job:
50
70
  """
51
71
  Manage a `meerschaum.utils.daemon.Daemon`, locally or remotely via the API.
@@ -58,6 +78,7 @@ class Job:
58
78
  env: Optional[Dict[str, str]] = None,
59
79
  executor_keys: Optional[str] = None,
60
80
  delete_after_completion: bool = False,
81
+ refresh_seconds: Union[int, float, None] = None,
61
82
  _properties: Optional[Dict[str, Any]] = None,
62
83
  _rotating_log=None,
63
84
  _stdin_file=None,
@@ -86,6 +107,10 @@ class Job:
86
107
  delete_after_completion: bool, default False
87
108
  If `True`, delete this job when it has finished executing.
88
109
 
110
+ refresh_seconds: Union[int, float, None], default None
111
+ The number of seconds to sleep between refreshes.
112
+ Defaults to the configured value `system.cli.refresh_seconds`.
113
+
89
114
  _properties: Optional[Dict[str, Any]], default None
90
115
  If provided, use this to patch the daemon's properties.
91
116
  """
@@ -112,6 +137,11 @@ class Job:
112
137
 
113
138
  self.executor_keys = executor_keys
114
139
  self.name = name
140
+ self.refresh_seconds = (
141
+ refresh_seconds
142
+ if refresh_seconds is not None
143
+ else mrsm.get_config('system', 'cli', 'refresh_seconds')
144
+ )
115
145
  try:
116
146
  self._daemon = (
117
147
  Daemon(daemon_id=name)
@@ -257,7 +287,11 @@ class Job:
257
287
 
258
288
  return success, f"Started {self}."
259
289
 
260
- def stop(self, timeout_seconds: Optional[int] = None, debug: bool = False) -> SuccessTuple:
290
+ def stop(
291
+ self,
292
+ timeout_seconds: Union[int, float, None] = None,
293
+ debug: bool = False,
294
+ ) -> SuccessTuple:
261
295
  """
262
296
  Stop the job's daemon.
263
297
  """
@@ -284,7 +318,11 @@ class Job:
284
318
 
285
319
  return kill_success, f"Killed {self}."
286
320
 
287
- def pause(self, timeout_seconds: Optional[int] = None, debug: bool = False) -> SuccessTuple:
321
+ def pause(
322
+ self,
323
+ timeout_seconds: Union[int, float, None] = None,
324
+ debug: bool = False,
325
+ ) -> SuccessTuple:
288
326
  """
289
327
  Pause the job's daemon.
290
328
  """
@@ -342,7 +380,7 @@ class Job:
342
380
 
343
381
  def monitor_logs(
344
382
  self,
345
- callback_function: Callable[[str], None] = partial(print, end=''),
383
+ callback_function: Callable[[str], None] = _default_stdout_callback,
346
384
  input_callback_function: Optional[Callable[[], str]] = None,
347
385
  stop_callback_function: Optional[Callable[[SuccessTuple], None]] = None,
348
386
  stop_event: Optional[asyncio.Event] = None,
@@ -350,6 +388,10 @@ class Job:
350
388
  strip_timestamps: bool = False,
351
389
  accept_input: bool = True,
352
390
  debug: bool = False,
391
+ _logs_path: Optional[pathlib.Path] = None,
392
+ _log=None,
393
+ _stdin_file=None,
394
+ _wait_if_stopped: bool = True,
353
395
  ):
354
396
  """
355
397
  Monitor the job's log files and execute a callback on new lines.
@@ -382,12 +424,6 @@ class Job:
382
424
  accept_input: bool, default True
383
425
  If `True`, accept input when the daemon blocks on stdin.
384
426
  """
385
- def default_input_callback_function():
386
- return sys.stdin.readline()
387
-
388
- if input_callback_function is None:
389
- input_callback_function = default_input_callback_function
390
-
391
427
  if self.executor is not None:
392
428
  self.executor.monitor_logs(
393
429
  self.name,
@@ -409,29 +445,35 @@ class Job:
409
445
  stop_on_exit=stop_on_exit,
410
446
  strip_timestamps=strip_timestamps,
411
447
  accept_input=accept_input,
448
+ debug=debug,
449
+ _logs_path=_logs_path,
450
+ _log=_log,
451
+ _stdin_file=_stdin_file,
452
+ _wait_if_stopped=_wait_if_stopped,
412
453
  )
413
454
  return asyncio.run(monitor_logs_coroutine)
414
455
 
415
456
  async def monitor_logs_async(
416
457
  self,
417
- callback_function: Callable[[str], None] = partial(print, end='', flush=True),
458
+ callback_function: Callable[[str], None] = _default_stdout_callback,
418
459
  input_callback_function: Optional[Callable[[], str]] = None,
419
460
  stop_callback_function: Optional[Callable[[SuccessTuple], None]] = None,
420
461
  stop_event: Optional[asyncio.Event] = None,
421
462
  stop_on_exit: bool = False,
422
463
  strip_timestamps: bool = False,
423
464
  accept_input: bool = True,
465
+ debug: bool = False,
424
466
  _logs_path: Optional[pathlib.Path] = None,
425
467
  _log=None,
426
468
  _stdin_file=None,
427
- debug: bool = False,
469
+ _wait_if_stopped: bool = True,
428
470
  ):
429
471
  """
430
472
  Monitor the job's log files and await a callback on new lines.
431
473
 
432
474
  Parameters
433
475
  ----------
434
- callback_function: Callable[[str], None], default partial(print, end='')
476
+ callback_function: Callable[[str], None], default _default_stdout_callback
435
477
  The callback to execute as new data comes in.
436
478
  Defaults to printing the output directly to `stdout`.
437
479
 
@@ -457,7 +499,13 @@ class Job:
457
499
  accept_input: bool, default True
458
500
  If `True`, accept input when the daemon blocks on stdin.
459
501
  """
502
+ from meerschaum.utils.prompt import prompt
503
+
460
504
  def default_input_callback_function():
505
+ prompt_kwargs = self.get_prompt_kwargs(debug=debug)
506
+ if prompt_kwargs:
507
+ answer = prompt(**prompt_kwargs)
508
+ return answer + '\n'
461
509
  return sys.stdin.readline()
462
510
 
463
511
  if input_callback_function is None:
@@ -481,23 +529,26 @@ class Job:
481
529
  events = {
482
530
  'user': stop_event,
483
531
  'stopped': asyncio.Event(),
532
+ 'stop_token': asyncio.Event(),
533
+ 'stop_exception': asyncio.Event(),
534
+ 'stopped_timeout': asyncio.Event(),
484
535
  }
485
536
  combined_event = asyncio.Event()
486
537
  emitted_text = False
487
538
  stdin_file = _stdin_file if _stdin_file is not None else self.daemon.stdin_file
488
539
 
489
540
  async def check_job_status():
490
- nonlocal emitted_text
491
- stopped_event = events.get('stopped', None)
492
- if stopped_event is None:
541
+ if not stop_on_exit:
493
542
  return
494
543
 
544
+ nonlocal emitted_text
545
+
495
546
  sleep_time = 0.1
496
- while sleep_time < 60:
547
+ while sleep_time < 0.2:
497
548
  if self.status == 'stopped':
498
- if not emitted_text:
549
+ if not emitted_text and _wait_if_stopped:
499
550
  await asyncio.sleep(sleep_time)
500
- sleep_time = round(sleep_time * 1.1, 2)
551
+ sleep_time = round(sleep_time * 1.1, 3)
501
552
  continue
502
553
 
503
554
  if stop_callback_function is not None:
@@ -517,11 +568,13 @@ class Job:
517
568
  break
518
569
  await asyncio.sleep(0.1)
519
570
 
571
+ events['stopped_timeout'].set()
572
+
520
573
  async def check_blocking_on_input():
521
574
  while True:
522
575
  if not emitted_text or not self.is_blocking_on_stdin():
523
576
  try:
524
- await asyncio.sleep(0.1)
577
+ await asyncio.sleep(self.refresh_seconds)
525
578
  except asyncio.exceptions.CancelledError:
526
579
  break
527
580
  continue
@@ -536,14 +589,15 @@ class Job:
536
589
  if asyncio.iscoroutinefunction(input_callback_function):
537
590
  data = await input_callback_function()
538
591
  else:
539
- data = input_callback_function()
592
+ loop = asyncio.get_running_loop()
593
+ data = await loop.run_in_executor(None, input_callback_function)
540
594
  except KeyboardInterrupt:
541
595
  break
542
- if not data.endswith('\n'):
543
- data += '\n'
596
+ # if not data.endswith('\n'):
597
+ # data += '\n'
544
598
 
545
599
  stdin_file.write(data)
546
- await asyncio.sleep(0.1)
600
+ await asyncio.sleep(self.refresh_seconds)
547
601
 
548
602
  async def combine_events():
549
603
  event_tasks = [
@@ -571,17 +625,39 @@ class Job:
571
625
  combine_events_task = asyncio.create_task(combine_events())
572
626
 
573
627
  log = _log if _log is not None else self.daemon.rotating_log
574
- lines_to_show = get_config('jobs', 'logs', 'lines_to_show')
628
+ lines_to_show = (
629
+ self.daemon.properties.get(
630
+ 'logs', {}
631
+ ).get(
632
+ 'lines_to_show', get_config('jobs', 'logs', 'lines_to_show')
633
+ )
634
+ )
575
635
 
576
636
  async def emit_latest_lines():
577
637
  nonlocal emitted_text
638
+ nonlocal stop_event
578
639
  lines = log.readlines()
579
640
  for line in lines[(-1 * lines_to_show):]:
580
641
  if stop_event is not None and stop_event.is_set():
581
642
  return
582
643
 
644
+ line_stripped_extra = strip_timestamp_from_line(line.strip())
645
+ line_stripped = strip_timestamp_from_line(line)
646
+
647
+ if line_stripped_extra == STOP_TOKEN:
648
+ events['stop_token'].set()
649
+ return
650
+
651
+ if line_stripped_extra == CLEAR_TOKEN:
652
+ clear_screen(debug=debug)
653
+ continue
654
+
655
+ if line_stripped_extra == FLUSH_TOKEN.strip():
656
+ line_stripped = ''
657
+ line = ''
658
+
583
659
  if strip_timestamps:
584
- line = strip_timestamp_from_line(line)
660
+ line = line_stripped
585
661
 
586
662
  try:
587
663
  if asyncio.iscoroutinefunction(callback_function):
@@ -590,6 +666,7 @@ class Job:
590
666
  callback_function(line)
591
667
  emitted_text = True
592
668
  except StopMonitoringLogs:
669
+ events['stop_exception'].set()
593
670
  return
594
671
  except Exception:
595
672
  warn(f"Error in logs callback:\n{traceback.format_exc()}")
@@ -608,9 +685,14 @@ class Job:
608
685
  except Exception:
609
686
  warn(f"Failed to run async checks:\n{traceback.format_exc()}")
610
687
 
611
- watchfiles = mrsm.attempt_import('watchfiles')
688
+ watchfiles = mrsm.attempt_import('watchfiles', lazy=False)
689
+ dir_path_to_monitor = (
690
+ _logs_path
691
+ or (log.file_path.parent if log else None)
692
+ or LOGS_RESOURCES_PATH
693
+ )
612
694
  async for changes in watchfiles.awatch(
613
- _logs_path or LOGS_RESOURCES_PATH,
695
+ dir_path_to_monitor,
614
696
  stop_event=combined_event,
615
697
  ):
616
698
  for change in changes:
@@ -633,6 +715,27 @@ class Job:
633
715
 
634
716
  return self.is_running() and self.daemon.blocking_stdin_file_path.exists()
635
717
 
718
+ def get_prompt_kwargs(self, debug: bool = False) -> Dict[str, Any]:
719
+ """
720
+ Return the kwargs to the blocking `prompt()`, if available.
721
+ """
722
+ if self.executor is not None:
723
+ return self.executor.get_job_prompt_kwargs(self.name, debug=debug)
724
+
725
+ if not self.daemon.prompt_kwargs_file_path.exists():
726
+ return {}
727
+
728
+ try:
729
+ with open(self.daemon.prompt_kwargs_file_path, 'r', encoding='utf-8') as f:
730
+ prompt_kwargs = json.load(f)
731
+
732
+ return prompt_kwargs
733
+
734
+ except Exception:
735
+ import traceback
736
+ traceback.print_exc()
737
+ return {}
738
+
636
739
  def write_stdin(self, data):
637
740
  """
638
741
  Write to a job's daemon's `stdin`.
@@ -724,6 +827,20 @@ class Job:
724
827
  self._sysargs = target_args[0] if len(target_args) > 0 else []
725
828
  return self._sysargs
726
829
 
830
+ def get_daemon_properties(self) -> Dict[str, Any]:
831
+ """
832
+ Return the `properties` dictionary for the job's daemon.
833
+ """
834
+ remote_properties = (
835
+ {}
836
+ if self.executor is None
837
+ else self.executor.get_job_properties(self.name)
838
+ )
839
+ return {
840
+ **remote_properties,
841
+ **self._properties_patch
842
+ }
843
+
727
844
  @property
728
845
  def daemon(self) -> 'Daemon':
729
846
  """
@@ -733,20 +850,13 @@ class Job:
733
850
  if self._daemon is not None and self.executor is None and self._sysargs:
734
851
  return self._daemon
735
852
 
736
- remote_properties = (
737
- {}
738
- if self.executor is None
739
- else self.executor.get_job_properties(self.name)
740
- )
741
- properties = {**remote_properties, **self._properties_patch}
742
-
743
853
  self._daemon = Daemon(
744
854
  target=entry,
745
855
  target_args=[self._sysargs],
746
856
  target_kw={},
747
857
  daemon_id=self.name,
748
858
  label=shlex.join(self._sysargs),
749
- properties=properties,
859
+ properties=self.get_daemon_properties(),
750
860
  )
751
861
  if '_rotating_log' in self.__dict__:
752
862
  self._daemon._rotating_log = self._rotating_log
@@ -12,7 +12,6 @@ import sys
12
12
  import asyncio
13
13
  import json
14
14
  import time
15
- import traceback
16
15
  import shutil
17
16
  from datetime import datetime, timezone
18
17
  from functools import partial
@@ -23,7 +22,6 @@ from meerschaum.utils.typing import Dict, Any, List, SuccessTuple, Union, Option
23
22
  from meerschaum.config import get_config
24
23
  from meerschaum._internal.static import STATIC_CONFIG
25
24
  from meerschaum.utils.warnings import warn, dprint
26
- from meerschaum._internal.arguments._parse_arguments import parse_arguments
27
25
 
28
26
  JOB_METADATA_CACHE_SECONDS: int = STATIC_CONFIG['api']['jobs']['metadata_cache_seconds']
29
27
 
@@ -705,6 +703,13 @@ class SystemdExecutor(Executor):
705
703
  blocking_path = socket_path.parent / (socket_path.name + '.block')
706
704
  return blocking_path.exists()
707
705
 
706
+ def get_job_prompt_kwargs(self, name: str, debug: bool = False) -> Dict[str, Any]:
707
+ """
708
+ Return the kwargs to the blocking prompt.
709
+ """
710
+ job = self.get_hidden_job(name, debug=debug)
711
+ return job.get_prompt_kwargs(debug=debug)
712
+
708
713
  def get_job_rotating_file(self, name: str, debug: bool = False):
709
714
  """
710
715
  Return a `RotatingFile` for the job's log output.