meerschaum 2.2.7__py3-none-any.whl → 2.3.0.dev3__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 (52) hide show
  1. meerschaum/__init__.py +6 -1
  2. meerschaum/_internal/arguments/_parse_arguments.py +10 -3
  3. meerschaum/_internal/arguments/_parser.py +44 -15
  4. meerschaum/_internal/entry.py +22 -1
  5. meerschaum/_internal/shell/Shell.py +129 -31
  6. meerschaum/actions/__init__.py +8 -6
  7. meerschaum/actions/api.py +12 -12
  8. meerschaum/actions/attach.py +108 -0
  9. meerschaum/actions/delete.py +35 -26
  10. meerschaum/actions/show.py +119 -148
  11. meerschaum/actions/start.py +85 -75
  12. meerschaum/actions/stop.py +68 -39
  13. meerschaum/api/_events.py +18 -1
  14. meerschaum/api/_oauth2.py +2 -0
  15. meerschaum/api/_websockets.py +2 -2
  16. meerschaum/api/dash/jobs.py +5 -2
  17. meerschaum/api/routes/__init__.py +1 -0
  18. meerschaum/api/routes/_actions.py +122 -44
  19. meerschaum/api/routes/_jobs.py +371 -0
  20. meerschaum/api/routes/_pipes.py +5 -5
  21. meerschaum/config/_default.py +1 -0
  22. meerschaum/config/_paths.py +1 -0
  23. meerschaum/config/_shell.py +8 -3
  24. meerschaum/config/_version.py +1 -1
  25. meerschaum/config/static/__init__.py +10 -0
  26. meerschaum/connectors/__init__.py +9 -11
  27. meerschaum/connectors/api/APIConnector.py +18 -1
  28. meerschaum/connectors/api/_actions.py +60 -71
  29. meerschaum/connectors/api/_jobs.py +330 -0
  30. meerschaum/connectors/parse.py +23 -7
  31. meerschaum/plugins/__init__.py +89 -5
  32. meerschaum/utils/daemon/Daemon.py +255 -30
  33. meerschaum/utils/daemon/FileDescriptorInterceptor.py +5 -5
  34. meerschaum/utils/daemon/RotatingFile.py +10 -6
  35. meerschaum/utils/daemon/StdinFile.py +110 -0
  36. meerschaum/utils/daemon/__init__.py +13 -7
  37. meerschaum/utils/formatting/__init__.py +2 -1
  38. meerschaum/utils/formatting/_jobs.py +83 -54
  39. meerschaum/utils/formatting/_shell.py +6 -0
  40. meerschaum/utils/jobs/_Job.py +710 -0
  41. meerschaum/utils/jobs/__init__.py +245 -0
  42. meerschaum/utils/misc.py +18 -17
  43. meerschaum/utils/packages/_packages.py +2 -2
  44. meerschaum/utils/prompt.py +16 -8
  45. {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dev3.dist-info}/METADATA +9 -9
  46. {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dev3.dist-info}/RECORD +52 -46
  47. {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dev3.dist-info}/WHEEL +1 -1
  48. {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dev3.dist-info}/LICENSE +0 -0
  49. {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dev3.dist-info}/NOTICE +0 -0
  50. {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dev3.dist-info}/entry_points.txt +0 -0
  51. {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dev3.dist-info}/top_level.txt +0 -0
  52. {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dev3.dist-info}/zip-safe +0 -0
@@ -0,0 +1,245 @@
1
+ #! /usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # vim:fenc=utf-8
4
+
5
+ """
6
+ Higher-level utilities for managing `meerschaum.utils.daemon.Daemon`.
7
+ """
8
+
9
+ import pathlib
10
+
11
+ import meerschaum as mrsm
12
+ from meerschaum.utils.jobs._Job import Job, StopMonitoringLogs
13
+ from meerschaum.utils.typing import Dict, Optional, List, Callable, Any
14
+
15
+ __all__ = (
16
+ 'Job',
17
+ 'get_jobs',
18
+ 'get_filtered_jobs',
19
+ 'get_restart_jobs',
20
+ 'get_running_jobs',
21
+ 'get_stopped_jobs',
22
+ 'get_paused_jobs',
23
+ 'get_restart_jobs',
24
+ 'check_restart_jobs',
25
+ 'start_check_jobs_thread',
26
+ 'stop_check_jobs_thread',
27
+ )
28
+
29
+
30
+ def get_jobs(
31
+ executor_keys: Optional[str] = None,
32
+ debug: bool = False,
33
+ ) -> Dict[str, Job]:
34
+ """
35
+ Return a dictionary of the existing jobs.
36
+
37
+ Parameters
38
+ ----------
39
+ executor_keys: Optional[str], default None
40
+ If provided, return remote jobs on the given API instance.
41
+ Otherwise return local jobs.
42
+
43
+ Returns
44
+ -------
45
+ A dictionary mapping job names to jobs.
46
+ """
47
+ from meerschaum.connectors.parse import parse_connector_keys
48
+ if executor_keys == 'local':
49
+ executor_keys = None
50
+
51
+ if executor_keys is not None:
52
+ try:
53
+ _ = parse_connector_keys(executor_keys, construct=False)
54
+ conn = mrsm.get_connector(executor_keys)
55
+ return conn.get_jobs(debug=debug)
56
+ except Exception:
57
+ return {}
58
+
59
+ from meerschaum.utils.daemon import get_daemons
60
+ daemons = get_daemons()
61
+ return {
62
+ daemon.daemon_id: Job(name=daemon.daemon_id)
63
+ for daemon in daemons
64
+ }
65
+
66
+
67
+ def get_filtered_jobs(
68
+ executor_keys: Optional[str] = None,
69
+ filter_list: Optional[List[str]] = None,
70
+ warn: bool = False,
71
+ debug: bool = False,
72
+ ) -> Dict[str, Job]:
73
+ """
74
+ Return a list of jobs filtered by the user.
75
+ """
76
+ from meerschaum.utils.warnings import warn as _warn
77
+ jobs = get_jobs(executor_keys, debug=debug)
78
+
79
+ if not filter_list:
80
+ return {
81
+ name: job
82
+ for name, job in jobs.items()
83
+ if not job.hidden
84
+ }
85
+
86
+ jobs_to_return = {}
87
+ for name in filter_list:
88
+ job = jobs.get(name, None)
89
+ if job is None:
90
+ if warn:
91
+ _warn(
92
+ f"Job '{name}' does not exist.",
93
+ stack=False,
94
+ )
95
+ continue
96
+ jobs_to_return[name] = job
97
+
98
+ return jobs_to_return
99
+
100
+
101
+ def get_restart_jobs(
102
+ executor_keys: Optional[str] = None,
103
+ jobs: Optional[Dict[str, Job]] = None,
104
+ debug: bool = False,
105
+ ) -> Dict[str, Job]:
106
+ """
107
+ Return jobs which were created with `--restart` or `--loop`.
108
+ """
109
+ if jobs is None:
110
+ jobs = get_jobs(executor_keys, debug=debug)
111
+
112
+ return {
113
+ name: job
114
+ for name, job in jobs.items()
115
+ if job.restart
116
+ }
117
+
118
+
119
+ def get_running_jobs(
120
+ executor_keys: Optional[str] = None,
121
+ jobs: Optional[Dict[str, Job]] = None,
122
+ debug: bool = False,
123
+ ) -> Dict[str, Job]:
124
+ """
125
+ Return a dictionary of running jobs.
126
+ """
127
+ if jobs is None:
128
+ jobs = get_jobs(executor_keys, debug=debug)
129
+
130
+ return {
131
+ name: job
132
+ for name, job in jobs.items()
133
+ if job.status == 'running'
134
+ }
135
+
136
+
137
+ def get_paused_jobs(
138
+ executor_keys: Optional[str] = None,
139
+ jobs: Optional[Dict[str, Job]] = None,
140
+ debug: bool = False,
141
+ ) -> Dict[str, Job]:
142
+ """
143
+ Return a dictionary of paused jobs.
144
+ """
145
+ if jobs is None:
146
+ jobs = get_jobs(executor_keys, debug=debug)
147
+
148
+ return {
149
+ name: job
150
+ for name, job in jobs.items()
151
+ if job.status == 'paused'
152
+ }
153
+
154
+
155
+ def get_stopped_jobs(
156
+ executor_keys: Optional[str] = None,
157
+ jobs: Optional[Dict[str, Job]] = None,
158
+ debug: bool = False,
159
+ ) -> Dict[str, Job]:
160
+ """
161
+ Return a dictionary of stopped jobs.
162
+ """
163
+ if jobs is None:
164
+ jobs = get_jobs(executor_keys, debug=debug)
165
+
166
+ return {
167
+ name: job
168
+ for name, job in jobs.items()
169
+ if job.status == 'stopped'
170
+ }
171
+
172
+
173
+ def check_restart_jobs(
174
+ executor_keys: Optional[str] = None,
175
+ silent: bool = False,
176
+ ) -> None:
177
+ """
178
+ Restart any stopped jobs which were created with `--restart`.
179
+
180
+ Parameters
181
+ ----------
182
+ executor_keys: Optional[str], default None
183
+ If provided, check jobs on the given remote API instance.
184
+ Otherwise check local jobs.
185
+
186
+ silent: bool, default False
187
+ If `True`, do not print the restart success message.
188
+ """
189
+ jobs = get_jobs(executor_keys)
190
+ for name, job in jobs.items():
191
+ success, msg = job.check_restart()
192
+ if not silent:
193
+ mrsm.pprint((success, msg))
194
+
195
+
196
+ def _check_restart_jobs_against_lock(*args, **kwargs):
197
+ from meerschaum.config.paths import CHECK_JOBS_LOCK_PATH
198
+ fasteners = mrsm.attempt_import('fasteners')
199
+ lock = fasteners.InterProcessLock(CHECK_JOBS_LOCK_PATH)
200
+ with lock:
201
+ check_restart_jobs(*args, **kwargs)
202
+
203
+
204
+ _check_loop_stop_thread = None
205
+ def start_check_jobs_thread():
206
+ """
207
+ Start a thread to regularly monitor jobs.
208
+ """
209
+ import atexit
210
+ from functools import partial
211
+ from meerschaum.utils.threading import RepeatTimer
212
+ from meerschaum.config.static import STATIC_CONFIG
213
+
214
+ global _check_loop_stop_thread
215
+ sleep_seconds = STATIC_CONFIG['jobs']['check_restart_seconds']
216
+
217
+ _check_loop_stop_thread = RepeatTimer(
218
+ sleep_seconds,
219
+ partial(
220
+ _check_restart_jobs_against_lock,
221
+ silent=True,
222
+ )
223
+ )
224
+ _check_loop_stop_thread.daemon = False
225
+ atexit.register(stop_check_jobs_thread)
226
+
227
+ _check_loop_stop_thread.start()
228
+
229
+
230
+ def stop_check_jobs_thread():
231
+ """
232
+ Stop the job monitoring thread.
233
+ """
234
+ from meerschaum.config.paths import CHECK_JOBS_LOCK_PATH
235
+ from meerschaum.utils.warnings import warn
236
+ if _check_loop_stop_thread is None:
237
+ return
238
+
239
+ _check_loop_stop_thread.cancel()
240
+
241
+ try:
242
+ if CHECK_JOBS_LOCK_PATH.exists():
243
+ CHECK_JOBS_LOCK_PATH.unlink()
244
+ except Exception as e:
245
+ warn(f"Failed to remove check jobs lock file:\n{e}")
meerschaum/utils/misc.py CHANGED
@@ -875,7 +875,7 @@ def dict_from_od(od: collections.OrderedDict) -> Dict[Any, Any]:
875
875
  _d[k] = dict_from_od(v)
876
876
  return _d
877
877
 
878
- def remove_ansi(s : str) -> str:
878
+ def remove_ansi(s: str) -> str:
879
879
  """
880
880
  Remove ANSI escape characters from a string.
881
881
 
@@ -899,10 +899,10 @@ def remove_ansi(s : str) -> str:
899
899
 
900
900
 
901
901
  def get_connector_labels(
902
- *types: str,
903
- search_term: str = '',
904
- ignore_exact_match = True,
905
- ) -> List[str]:
902
+ *types: str,
903
+ search_term: str = '',
904
+ ignore_exact_match = True,
905
+ ) -> List[str]:
906
906
  """
907
907
  Read connector labels from the configuration dictionary.
908
908
 
@@ -1063,7 +1063,7 @@ def async_wrap(func):
1063
1063
  loop = asyncio.get_event_loop()
1064
1064
  pfunc = partial(func, *args, **kwargs)
1065
1065
  return await loop.run_in_executor(executor, pfunc)
1066
- return run
1066
+ return run
1067
1067
 
1068
1068
 
1069
1069
  def debug_trace(browser: bool = True):
@@ -1077,17 +1077,17 @@ def debug_trace(browser: bool = True):
1077
1077
 
1078
1078
 
1079
1079
  def items_str(
1080
- items: List[Any],
1081
- quotes: bool = True,
1082
- quote_str: str = "'",
1083
- commas: bool = True,
1084
- comma_str: str = ',',
1085
- and_: bool = True,
1086
- and_str: str = 'and',
1087
- oxford_comma: bool = True,
1088
- spaces: bool = True,
1089
- space_str = ' ',
1090
- ) -> str:
1080
+ items: List[Any],
1081
+ quotes: bool = True,
1082
+ quote_str: str = "'",
1083
+ commas: bool = True,
1084
+ comma_str: str = ',',
1085
+ and_: bool = True,
1086
+ and_str: str = 'and',
1087
+ oxford_comma: bool = True,
1088
+ spaces: bool = True,
1089
+ space_str = ' ',
1090
+ ) -> str:
1091
1091
  """
1092
1092
  Return a formatted string if list items separated by commas.
1093
1093
 
@@ -1522,6 +1522,7 @@ def safely_extract_tar(tarf: 'file', output_dir: Union[str, 'pathlib.Path']) ->
1522
1522
 
1523
1523
  return safe_extract(tarf, output_dir)
1524
1524
 
1525
+
1525
1526
  ##################
1526
1527
  # Legacy imports #
1527
1528
  ##################
@@ -48,7 +48,7 @@ packages: Dict[str, Dict[str, str]] = {
48
48
  'prompt_toolkit' : 'prompt-toolkit>=3.0.39',
49
49
  'more_itertools' : 'more-itertools>=8.7.0',
50
50
  'daemon' : 'python-daemon>=0.2.3',
51
- 'fasteners' : 'fasteners>=0.18.0',
51
+ 'fasteners' : 'fasteners>=0.19.0',
52
52
  'psutil' : 'psutil>=5.8.0',
53
53
  'watchfiles' : 'watchfiles>=0.21.0',
54
54
  'dill' : 'dill>=0.3.3',
@@ -58,7 +58,7 @@ packages: Dict[str, Dict[str, str]] = {
58
58
  },
59
59
  'drivers': {
60
60
  'cryptography' : 'cryptography>=38.0.1',
61
- 'psycopg' : 'psycopg[binary]>=3.1.18',
61
+ 'psycopg' : 'psycopg[binary]>=3.2.1',
62
62
  'pymysql' : 'PyMySQL>=0.9.0',
63
63
  'aiomysql' : 'aiomysql>=0.0.21',
64
64
  'sqlalchemy_cockroachdb' : 'sqlalchemy-cockroachdb>=2.0.0',
@@ -66,6 +66,7 @@ def prompt(
66
66
  from meerschaum.config import get_config
67
67
  from meerschaum.config.static import _static_config
68
68
  from meerschaum.utils.misc import filter_keywords
69
+ from meerschaum.utils.daemon import running_in_daemon
69
70
  noask = check_noask(noask)
70
71
  if not noask:
71
72
  prompt_toolkit = attempt_import('prompt_toolkit')
@@ -102,14 +103,21 @@ def prompt(
102
103
  question += '\n' + other_lines
103
104
  question += ' '
104
105
 
105
- answer = (
106
- prompt_toolkit.prompt(
107
- prompt_toolkit.formatted_text.ANSI(question),
108
- wrap_lines = wrap_lines,
109
- default = default_editable or '',
110
- **filter_keywords(prompt_toolkit.prompt, **kw)
111
- ) if not noask else ''
112
- )
106
+ if not running_in_daemon():
107
+ answer = (
108
+ prompt_toolkit.prompt(
109
+ prompt_toolkit.formatted_text.ANSI(question),
110
+ wrap_lines = wrap_lines,
111
+ default = default_editable or '',
112
+ **filter_keywords(prompt_toolkit.prompt, **kw)
113
+ ) if not noask else ''
114
+ )
115
+ else:
116
+ print(question, end='', flush=True)
117
+ try:
118
+ answer = input()
119
+ except EOFError:
120
+ answer = ''
113
121
  if noask:
114
122
  print(question)
115
123
  if answer == '' and default is not None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: meerschaum
3
- Version: 2.2.7
3
+ Version: 2.3.0.dev3
4
4
  Summary: Sync Time-Series Pipes with Meerschaum
5
5
  Home-page: https://meerschaum.io
6
6
  Author: Bennett Meares
@@ -51,7 +51,7 @@ Requires-Dist: databases >=0.4.0 ; extra == 'api'
51
51
  Requires-Dist: aiosqlite >=0.16.0 ; extra == 'api'
52
52
  Requires-Dist: asyncpg >=0.21.0 ; extra == 'api'
53
53
  Requires-Dist: cryptography >=38.0.1 ; extra == 'api'
54
- Requires-Dist: psycopg[binary] >=3.1.18 ; extra == 'api'
54
+ Requires-Dist: psycopg[binary] >=3.2.1 ; extra == 'api'
55
55
  Requires-Dist: PyMySQL >=0.9.0 ; extra == 'api'
56
56
  Requires-Dist: aiomysql >=0.0.21 ; extra == 'api'
57
57
  Requires-Dist: sqlalchemy-cockroachdb >=2.0.0 ; extra == 'api'
@@ -74,7 +74,7 @@ Requires-Dist: packaging >=21.3.0 ; extra == 'api'
74
74
  Requires-Dist: prompt-toolkit >=3.0.39 ; extra == 'api'
75
75
  Requires-Dist: more-itertools >=8.7.0 ; extra == 'api'
76
76
  Requires-Dist: python-daemon >=0.2.3 ; extra == 'api'
77
- Requires-Dist: fasteners >=0.18.0 ; extra == 'api'
77
+ Requires-Dist: fasteners >=0.19.0 ; extra == 'api'
78
78
  Requires-Dist: psutil >=5.8.0 ; extra == 'api'
79
79
  Requires-Dist: watchfiles >=0.21.0 ; extra == 'api'
80
80
  Requires-Dist: dill >=0.3.3 ; extra == 'api'
@@ -124,7 +124,7 @@ Requires-Dist: packaging >=21.3.0 ; extra == 'core'
124
124
  Requires-Dist: prompt-toolkit >=3.0.39 ; extra == 'core'
125
125
  Requires-Dist: more-itertools >=8.7.0 ; extra == 'core'
126
126
  Requires-Dist: python-daemon >=0.2.3 ; extra == 'core'
127
- Requires-Dist: fasteners >=0.18.0 ; extra == 'core'
127
+ Requires-Dist: fasteners >=0.19.0 ; extra == 'core'
128
128
  Requires-Dist: psutil >=5.8.0 ; extra == 'core'
129
129
  Requires-Dist: watchfiles >=0.21.0 ; extra == 'core'
130
130
  Requires-Dist: dill >=0.3.3 ; extra == 'core'
@@ -161,7 +161,7 @@ Requires-Dist: mkdocs-redirects >=1.0.4 ; extra == 'docs'
161
161
  Requires-Dist: jinja2 ==3.0.3 ; extra == 'docs'
162
162
  Provides-Extra: drivers
163
163
  Requires-Dist: cryptography >=38.0.1 ; extra == 'drivers'
164
- Requires-Dist: psycopg[binary] >=3.1.18 ; extra == 'drivers'
164
+ Requires-Dist: psycopg[binary] >=3.2.1 ; extra == 'drivers'
165
165
  Requires-Dist: PyMySQL >=0.9.0 ; extra == 'drivers'
166
166
  Requires-Dist: aiomysql >=0.0.21 ; extra == 'drivers'
167
167
  Requires-Dist: sqlalchemy-cockroachdb >=2.0.0 ; extra == 'drivers'
@@ -211,7 +211,7 @@ Requires-Dist: packaging >=21.3.0 ; extra == 'full'
211
211
  Requires-Dist: prompt-toolkit >=3.0.39 ; extra == 'full'
212
212
  Requires-Dist: more-itertools >=8.7.0 ; extra == 'full'
213
213
  Requires-Dist: python-daemon >=0.2.3 ; extra == 'full'
214
- Requires-Dist: fasteners >=0.18.0 ; extra == 'full'
214
+ Requires-Dist: fasteners >=0.19.0 ; extra == 'full'
215
215
  Requires-Dist: psutil >=5.8.0 ; extra == 'full'
216
216
  Requires-Dist: watchfiles >=0.21.0 ; extra == 'full'
217
217
  Requires-Dist: dill >=0.3.3 ; extra == 'full'
@@ -219,7 +219,7 @@ Requires-Dist: virtualenv >=20.1.0 ; extra == 'full'
219
219
  Requires-Dist: APScheduler >=4.0.0a5 ; extra == 'full'
220
220
  Requires-Dist: uv >=0.2.11 ; extra == 'full'
221
221
  Requires-Dist: cryptography >=38.0.1 ; extra == 'full'
222
- Requires-Dist: psycopg[binary] >=3.1.18 ; extra == 'full'
222
+ Requires-Dist: psycopg[binary] >=3.2.1 ; extra == 'full'
223
223
  Requires-Dist: PyMySQL >=0.9.0 ; extra == 'full'
224
224
  Requires-Dist: aiomysql >=0.0.21 ; extra == 'full'
225
225
  Requires-Dist: sqlalchemy-cockroachdb >=2.0.0 ; extra == 'full'
@@ -273,7 +273,7 @@ Requires-Dist: databases >=0.4.0 ; extra == 'sql'
273
273
  Requires-Dist: aiosqlite >=0.16.0 ; extra == 'sql'
274
274
  Requires-Dist: asyncpg >=0.21.0 ; extra == 'sql'
275
275
  Requires-Dist: cryptography >=38.0.1 ; extra == 'sql'
276
- Requires-Dist: psycopg[binary] >=3.1.18 ; extra == 'sql'
276
+ Requires-Dist: psycopg[binary] >=3.2.1 ; extra == 'sql'
277
277
  Requires-Dist: PyMySQL >=0.9.0 ; extra == 'sql'
278
278
  Requires-Dist: aiomysql >=0.0.21 ; extra == 'sql'
279
279
  Requires-Dist: sqlalchemy-cockroachdb >=2.0.0 ; extra == 'sql'
@@ -296,7 +296,7 @@ Requires-Dist: packaging >=21.3.0 ; extra == 'sql'
296
296
  Requires-Dist: prompt-toolkit >=3.0.39 ; extra == 'sql'
297
297
  Requires-Dist: more-itertools >=8.7.0 ; extra == 'sql'
298
298
  Requires-Dist: python-daemon >=0.2.3 ; extra == 'sql'
299
- Requires-Dist: fasteners >=0.18.0 ; extra == 'sql'
299
+ Requires-Dist: fasteners >=0.19.0 ; extra == 'sql'
300
300
  Requires-Dist: psutil >=5.8.0 ; extra == 'sql'
301
301
  Requires-Dist: watchfiles >=0.21.0 ; extra == 'sql'
302
302
  Requires-Dist: dill >=0.3.3 ; extra == 'sql'