meerschaum 3.0.0rc4__py3-none-any.whl → 3.0.0rc7__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 +434 -0
  6. meerschaum/_internal/docs/index.py +1 -2
  7. meerschaum/_internal/entry.py +44 -8
  8. meerschaum/_internal/shell/Shell.py +113 -19
  9. meerschaum/_internal/shell/__init__.py +4 -1
  10. meerschaum/_internal/static.py +3 -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 +20 -10
  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 +135 -35
  85. meerschaum/jobs/systemd.py +7 -2
  86. meerschaum/plugins/__init__.py +277 -81
  87. meerschaum/utils/daemon/Daemon.py +195 -41
  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 +16 -6
  99. meerschaum/utils/misc.py +18 -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 +171 -144
  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.0rc7.dist-info}/METADATA +3 -1
  110. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/RECORD +116 -110
  111. meerschaum/config/_environment.py +0 -145
  112. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/WHEEL +0 -0
  113. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/entry_points.txt +0 -0
  114. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/licenses/LICENSE +0 -0
  115. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/licenses/NOTICE +0 -0
  116. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/top_level.txt +0 -0
  117. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/zip-safe +0 -0
@@ -9,11 +9,11 @@ Delete a Pipe's contents and registration
9
9
  from meerschaum.utils.typing import SuccessTuple
10
10
 
11
11
  def delete(
12
- self,
13
- drop: bool = True,
14
- debug: bool = False,
15
- **kw
16
- ) -> SuccessTuple:
12
+ self,
13
+ drop: bool = True,
14
+ debug: bool = False,
15
+ **kw
16
+ ) -> SuccessTuple:
17
17
  """
18
18
  Call the Pipe's instance connector's `delete_pipe()` method.
19
19
 
@@ -30,29 +30,22 @@ def delete(
30
30
  A `SuccessTuple` of success (`bool`), message (`str`).
31
31
 
32
32
  """
33
- import os, pathlib
34
33
  from meerschaum.utils.warnings import warn
35
34
  from meerschaum.utils.venv import Venv
36
35
  from meerschaum.connectors import get_connector_plugin
37
36
 
38
37
  if self.temporary:
38
+ if self.cache:
39
+ invalidate_success, invalidate_msg = self._invalidate_cache(hard=True, debug=debug)
40
+ if not invalidate_success:
41
+ return invalidate_success, invalidate_msg
42
+
39
43
  return (
40
44
  False,
41
45
  "Cannot delete pipes created with `temporary=True` (read-only). "
42
46
  + "You may want to call `pipe.drop()` instead."
43
47
  )
44
48
 
45
- if self.cache_pipe is not None:
46
- _drop_cache_tuple = self.cache_pipe.drop(debug=debug, **kw)
47
- if not _drop_cache_tuple[0]:
48
- warn(_drop_cache_tuple[1])
49
- if getattr(self.cache_connector, 'flavor', None) == 'sqlite':
50
- _cache_db_path = pathlib.Path(self.cache_connector.database)
51
- try:
52
- os.remove(_cache_db_path)
53
- except Exception as e:
54
- warn(f"Could not delete cache file '{_cache_db_path}' for {self}:\n{e}")
55
-
56
49
  if drop:
57
50
  drop_success, drop_msg = self.drop(debug=debug)
58
51
  if not drop_success:
@@ -65,8 +58,6 @@ def delete(
65
58
  return False, f"Received an unexpected result from '{self.instance_connector}': {result}"
66
59
 
67
60
  if result[0]:
68
- to_delete = ['_id']
69
- for member in to_delete:
70
- if member in self.__dict__:
71
- del self.__dict__[member]
61
+ self._invalidate_cache(hard=True, debug=debug)
62
+
72
63
  return result
@@ -28,15 +28,10 @@ def drop(
28
28
  A `SuccessTuple` of success, message.
29
29
 
30
30
  """
31
- self._exists = False
32
- from meerschaum.utils.warnings import warn
33
31
  from meerschaum.utils.venv import Venv
34
32
  from meerschaum.connectors import get_connector_plugin
35
33
 
36
- if self.cache_pipe is not None:
37
- _drop_cache_tuple = self.cache_pipe.drop(debug=debug, **kw)
38
- if not _drop_cache_tuple[0]:
39
- warn(_drop_cache_tuple[1])
34
+ self._clear_cache_key('_exists', debug=debug)
40
35
 
41
36
  with Venv(get_connector_plugin(self.instance_connector)):
42
37
  if hasattr(self.instance_connector, 'drop_pipe'):
@@ -50,9 +45,8 @@ def drop(
50
45
  )
51
46
  )
52
47
 
53
-
54
- _ = self.__dict__.pop('_exists', None)
55
- _ = self.__dict__.pop('_exists_timestamp', None)
48
+ self._clear_cache_key('_exists', debug=debug)
49
+ self._clear_cache_key('_exists_timestamp', debug=debug)
56
50
 
57
51
  return result
58
52
 
@@ -79,19 +73,13 @@ def drop_indices(
79
73
  A `SuccessTuple` of success, message.
80
74
 
81
75
  """
82
- from meerschaum.utils.warnings import warn
83
76
  from meerschaum.utils.venv import Venv
84
77
  from meerschaum.connectors import get_connector_plugin
85
78
 
86
- _ = self.__dict__.pop('_columns_indices', None)
87
- _ = self.__dict__.pop('_columns_indices_timestamp', None)
88
- _ = self.__dict__.pop('_columns_types_timestamp', None)
89
- _ = self.__dict__.pop('_columns_types', None)
90
-
91
- if self.cache_pipe is not None:
92
- _drop_cache_tuple = self.cache_pipe.drop_indices(columns=columns, debug=debug, **kw)
93
- if not _drop_cache_tuple[0]:
94
- warn(_drop_cache_tuple[1])
79
+ self._clear_cache_key('_columns_indices', debug=debug)
80
+ self._clear_cache_key('_columns_indices_timestamp', debug=debug)
81
+ self._clear_cache_key('_columns_types', debug=debug)
82
+ self._clear_cache_key('_columns_types_timestamp', debug=debug)
95
83
 
96
84
  with Venv(get_connector_plugin(self.instance_connector)):
97
85
  if hasattr(self.instance_connector, 'drop_pipe_indices'):
@@ -110,9 +98,9 @@ def drop_indices(
110
98
  )
111
99
  )
112
100
 
113
- _ = self.__dict__.pop('_columns_indices', None)
114
- _ = self.__dict__.pop('_columns_indices_timestamp', None)
115
- _ = self.__dict__.pop('_columns_types_timestamp', None)
116
- _ = self.__dict__.pop('_columns_types', None)
101
+ self._clear_cache_key('_columns_indices', debug=debug)
102
+ self._clear_cache_key('_columns_indices_timestamp', debug=debug)
103
+ self._clear_cache_key('_columns_types', debug=debug)
104
+ self._clear_cache_key('_columns_types_timestamp', debug=debug)
117
105
 
118
106
  return result
@@ -98,7 +98,7 @@ def enforce_dtypes(
98
98
  if debug:
99
99
  dprint(
100
100
  f"Could not find dtypes for {self}.\n"
101
- + " Skipping dtype enforcement..."
101
+ + "Skipping dtype enforcement..."
102
102
  )
103
103
  return df
104
104
 
@@ -29,19 +29,13 @@ def create_indices(
29
29
  A `SuccessTuple` of success, message.
30
30
 
31
31
  """
32
- from meerschaum.utils.warnings import warn
33
32
  from meerschaum.utils.venv import Venv
34
33
  from meerschaum.connectors import get_connector_plugin
35
34
 
36
- _ = self.__dict__.pop('_columns_indices', None)
37
- _ = self.__dict__.pop('_columns_indices_timestamp', None)
38
- _ = self.__dict__.pop('_columns_types_timestamp', None)
39
- _ = self.__dict__.pop('_columns_types', None)
40
-
41
- if self.cache_pipe is not None:
42
- cache_success, cache_msg = self.cache_pipe.index(columns=columns, debug=debug, **kw)
43
- if not cache_success:
44
- warn(cache_msg)
35
+ self._clear_cache_key('_columns_indices', debug=debug)
36
+ self._clear_cache_key('_columns_indices_timestamp', debug=debug)
37
+ self._clear_cache_key('_columns_types', debug=debug)
38
+ self._clear_cache_key('_columns_types_timestamp', debug=debug)
45
39
 
46
40
  with Venv(get_connector_plugin(self.instance_connector)):
47
41
  if hasattr(self.instance_connector, 'create_pipe_indices'):
@@ -60,9 +54,9 @@ def create_indices(
60
54
  )
61
55
  )
62
56
 
63
- _ = self.__dict__.pop('_columns_indices', None)
64
- _ = self.__dict__.pop('_columns_indices_timestamp', None)
65
- _ = self.__dict__.pop('_columns_types_timestamp', None)
66
- _ = self.__dict__.pop('_columns_types', None)
57
+ self._clear_cache_key('_columns_indices', debug=debug)
58
+ self._clear_cache_key('_columns_indices_timestamp', debug=debug)
59
+ self._clear_cache_key('_columns_types', debug=debug)
60
+ self._clear_cache_key('_columns_types_timestamp', debug=debug)
67
61
 
68
62
  return result
@@ -166,7 +166,7 @@ def sync(
166
166
  })
167
167
 
168
168
  self._invalidate_cache(debug=debug)
169
- self._sync_ts = get_current_timestamp('ms')
169
+ self._cache_value('sync_ts', get_current_timestamp('ms'), debug=debug)
170
170
 
171
171
  def _sync(
172
172
  p: mrsm.Pipe,
@@ -477,15 +477,8 @@ def sync(
477
477
  ("s" if retries != 1 else "") + "!"
478
478
  )
479
479
 
480
- ### CHECKPOINT: Finished syncing. Handle caching.
480
+ ### CHECKPOINT: Finished syncing.
481
481
  _checkpoint(**kw)
482
- if p.cache_pipe is not None:
483
- if debug:
484
- dprint("Caching retrieved dataframe.", **kw)
485
- _sync_cache_tuple = p.cache_pipe.sync(df, debug=debug, **kw)
486
- if not _sync_cache_tuple[0]:
487
- warn(f"Failed to sync local cache for {self}.")
488
-
489
482
  p._invalidate_cache(debug=debug)
490
483
  return return_tuple
491
484
 
@@ -620,16 +613,16 @@ def exists(
620
613
  A `bool` corresponding to whether a pipe's underlying table exists.
621
614
 
622
615
  """
623
- import time
624
616
  from meerschaum.utils.venv import Venv
625
617
  from meerschaum.connectors import get_connector_plugin
626
618
  from meerschaum.utils.debug import dprint
627
- now = time.perf_counter()
619
+ from meerschaum.utils.dtypes import get_current_timestamp
620
+ now = get_current_timestamp('ms', as_int=True) / 1000
628
621
  cache_seconds = mrsm.get_config('pipes', 'sync', 'exists_cache_seconds')
629
622
 
630
- _exists = self.__dict__.get('_exists', None)
623
+ _exists = self._get_cached_value('_exists', debug=debug)
631
624
  if _exists:
632
- exists_timestamp = self.__dict__.get('_exists_timestamp', None)
625
+ exists_timestamp = self._get_cached_value('_exists_timestamp', debug=debug)
633
626
  if exists_timestamp is not None:
634
627
  delta = now - exists_timestamp
635
628
  if delta < cache_seconds:
@@ -644,8 +637,8 @@ def exists(
644
637
  else False
645
638
  )
646
639
 
647
- self.__dict__['_exists'] = _exists
648
- self.__dict__['_exists_timestamp'] = now
640
+ self._cache_value('_exists', _exists, debug=debug)
641
+ self._cache_value('_exists_timestamp', now, debug=debug)
649
642
  return _exists
650
643
 
651
644
 
@@ -1058,7 +1051,7 @@ def _persist_new_special_columns(
1058
1051
  Check for new special columns and update the parameters accordingly.
1059
1052
  """
1060
1053
  from meerschaum.utils.dataframe import get_special_cols
1061
- from meerschaum.utils.dtypes import dtype_is_special
1054
+ from meerschaum.utils.dtypes import is_dtype_special
1062
1055
  from meerschaum.utils.warnings import dprint
1063
1056
 
1064
1057
  special_cols = get_special_cols(df)
@@ -1066,18 +1059,19 @@ def _persist_new_special_columns(
1066
1059
  existing_special_cols = {
1067
1060
  col: typ
1068
1061
  for col, typ in dtypes.items()
1069
- if dtype_is_special(typ)
1062
+ if is_dtype_special(typ)
1070
1063
  }
1071
1064
  new_special_cols = {
1072
1065
  col: typ
1073
1066
  for col, typ in special_cols.items()
1074
1067
  if col not in existing_special_cols
1075
1068
  }
1069
+ self._cache_value('new_special_cols', new_special_cols, memory_only=True, debug=debug)
1076
1070
  if not new_special_cols:
1077
1071
  return True, "Success"
1078
1072
 
1079
1073
  if debug:
1080
1074
  dprint(f"New special columns:\n{new_special_cols}")
1081
1075
 
1082
- self._attributes_sync_time = None
1076
+ self._clear_cache_key('_attributes_sync_time', debug=debug)
1083
1077
  return self.update_parameters({'dtypes': new_special_cols}, debug=debug)
@@ -112,8 +112,10 @@ class Plugin:
112
112
  if '_module' not in self.__dict__ or self.__dict__.get('_module', None) is None:
113
113
  if self.__file__ is None:
114
114
  return None
115
+
115
116
  from meerschaum.plugins import import_plugins
116
117
  self._module = import_plugins(str(self), warn=False)
118
+
117
119
  return self._module
118
120
 
119
121
 
@@ -363,6 +365,10 @@ class Plugin:
363
365
  ### Determine where to permanently store the new plugin.
364
366
  plugin_installation_dir_path = PLUGINS_DIR_PATHS[0]
365
367
  for path in PLUGINS_DIR_PATHS:
368
+ if not path.exists():
369
+ warn(f"Plugins path does not exist: {path}", stack=False)
370
+ continue
371
+
366
372
  files_in_plugins_dir = os.listdir(path)
367
373
  if (
368
374
  self.name in files_in_plugins_dir
@@ -729,7 +735,7 @@ class Plugin:
729
735
  _d[len('plugin:'):] for _d in _deps
730
736
  if _d.startswith('plugin:') and len(_d) > len('plugin:')
731
737
  ]
732
- default_repo_keys = get_config('meerschaum', 'default_repository')
738
+ default_repo_keys = get_config('meerschaum', 'repository')
733
739
  skipped_repo_keys = set()
734
740
 
735
741
  for _plugin_name in plugin_names:
@@ -42,7 +42,7 @@ class Token:
42
42
  from meerschaum._internal.static import STATIC_CONFIG
43
43
  now = datetime.now(timezone.utc)
44
44
  default_expiration_days = mrsm.get_config(
45
- 'system', 'api', 'tokens', 'default_expiration_days',
45
+ 'api', 'tokens', 'default_expiration_days',
46
46
  ) or 366
47
47
  default_expiration = round_time(
48
48
  now + timedelta(days=default_expiration_days),
@@ -94,14 +94,13 @@ def verify_password(
94
94
  from meerschaum._internal.static import STATIC_CONFIG
95
95
  if password is None or password_hash is None:
96
96
  return False
97
- hash_config = STATIC_CONFIG['users']['password_hash']
98
97
  try:
99
98
  digest, rounds_str, encoded_salt, encoded_checksum = password_hash.split('$')[1:]
100
99
  algorithm_name = digest.split('-')[-1]
101
100
  salt = ab64_decode(encoded_salt)
102
101
  checksum = ab64_decode(encoded_checksum)
103
102
  rounds = int(rounds_str)
104
- except Exception as e:
103
+ except Exception:
105
104
  warn(f"Failed to extract context from password hash '{password_hash}'. Is it corrupted?")
106
105
  return False
107
106
 
@@ -10,28 +10,72 @@ from __future__ import annotations
10
10
  from abc import abstractmethod
11
11
 
12
12
  from meerschaum.connectors import Connector
13
- from meerschaum.utils.typing import List, Dict, SuccessTuple, TYPE_CHECKING, Optional, Any
13
+ from meerschaum.utils.typing import (
14
+ List, Dict, SuccessTuple, TYPE_CHECKING, Optional, Any, Union, Callable,
15
+ )
14
16
 
15
17
  if TYPE_CHECKING:
16
18
  from meerschaum.jobs import Job
19
+ from datetime import datetime
17
20
 
18
21
  class Executor(Connector):
19
22
  """
20
23
  Define the methods for managing jobs.
21
24
  """
22
25
 
26
+ @abstractmethod
27
+ def get_job_names(self, debug: bool = False) -> List[str]:
28
+ """
29
+ Return a list of existing jobs, including hidden ones.
30
+ """
31
+
23
32
  @abstractmethod
24
33
  def get_job_exists(self, name: str, debug: bool = False) -> bool:
25
34
  """
26
35
  Return whether a job exists.
27
36
  """
28
-
37
+
38
+ @abstractmethod
39
+ def get_jobs(self, debug: bool = False) -> Dict[str, Job]:
40
+ """
41
+ Return a dictionary of existing jobs.
42
+ """
43
+
44
+ @abstractmethod
45
+ def get_job_metadata(self, name: str, debug: bool = False) -> Dict[str, Any]:
46
+ """
47
+ Return a job's metadata.
48
+ """
49
+
50
+ @abstractmethod
51
+ def get_job_properties(self, name: str, debug: bool = False) -> Dict[str, Any]:
52
+ """
53
+ Return the underlying daemon's properties.
54
+ """
29
55
  @abstractmethod
30
- def get_jobs(self) -> Dict[str, Job]:
56
+ def get_job_status(self, name: str, debug: bool = False) -> str:
31
57
  """
32
- Return a dictionary of names -> Jobs.
58
+ Return the job's status.
33
59
  """
34
60
 
61
+ @abstractmethod
62
+ def get_job_began(self, name: str, debug: bool = False) -> Union[str, None]:
63
+ """
64
+ Return when a job began running.
65
+ """
66
+
67
+ @abstractmethod
68
+ def get_job_ended(self, name: str, debug: bool = False) -> Union[str, None]:
69
+ """
70
+ Return when a job stopped running.
71
+ """
72
+
73
+ @abstractmethod
74
+ def get_job_paused(self, name: str, debug: bool = False) -> Union[str, None]:
75
+ """
76
+ Return a job's `paused` timestamp, if it exists.
77
+ """
78
+
35
79
  @abstractmethod
36
80
  def create_job(
37
81
  self,
@@ -73,3 +117,43 @@ class Executor(Connector):
73
117
  """
74
118
  Return a job's log output.
75
119
  """
120
+
121
+ @abstractmethod
122
+ def get_job_stop_time(self, name: str, debug: bool = False) -> Union[datetime, None]:
123
+ """
124
+ Return the job's manual stop time.
125
+ """
126
+
127
+ @abstractmethod
128
+ async def monitor_logs_async(
129
+ self,
130
+ name: str,
131
+ callback_function: Callable[[Any], Any],
132
+ input_callback_function: Callable[[], str],
133
+ stop_callback_function: Callable[[SuccessTuple], str],
134
+ stop_on_exit: bool = False,
135
+ strip_timestamps: bool = False,
136
+ accept_input: bool = True,
137
+ debug: bool = False,
138
+ ):
139
+ """
140
+ Monitor a job's log files and await a callback with the changes.
141
+ """
142
+
143
+ @abstractmethod
144
+ def monitor_logs(self, *args, **kwargs):
145
+ """
146
+ Monitor a job's log files.
147
+ """
148
+
149
+ @abstractmethod
150
+ def get_job_is_blocking_on_stdin(self, name: str, debug: bool = False) -> bool:
151
+ """
152
+ Return whether a job is blocking on stdin.
153
+ """
154
+
155
+ @abstractmethod
156
+ def get_job_prompt_kwargs(self, name: str, debug: bool = False) -> Dict[str, Any]:
157
+ """
158
+ Return the kwargs to the blocking prompt.
159
+ """