meerschaum 2.9.5__py3-none-any.whl → 3.0.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.
- meerschaum/__init__.py +5 -2
- meerschaum/_internal/__init__.py +1 -0
- meerschaum/_internal/arguments/_parse_arguments.py +4 -4
- meerschaum/_internal/arguments/_parser.py +33 -4
- meerschaum/_internal/cli/__init__.py +6 -0
- meerschaum/_internal/cli/daemons.py +103 -0
- meerschaum/_internal/cli/entry.py +220 -0
- meerschaum/_internal/cli/workers.py +435 -0
- meerschaum/_internal/docs/index.py +48 -2
- meerschaum/_internal/entry.py +50 -14
- meerschaum/_internal/shell/Shell.py +121 -29
- meerschaum/_internal/shell/__init__.py +4 -1
- meerschaum/_internal/static.py +359 -0
- meerschaum/_internal/term/TermPageHandler.py +1 -2
- meerschaum/_internal/term/__init__.py +40 -6
- meerschaum/_internal/term/tools.py +33 -8
- meerschaum/actions/__init__.py +6 -4
- meerschaum/actions/api.py +53 -13
- meerschaum/actions/attach.py +1 -0
- meerschaum/actions/bootstrap.py +8 -8
- meerschaum/actions/delete.py +4 -2
- meerschaum/actions/edit.py +171 -25
- meerschaum/actions/login.py +8 -8
- meerschaum/actions/register.py +143 -6
- meerschaum/actions/reload.py +22 -5
- meerschaum/actions/restart.py +14 -0
- meerschaum/actions/show.py +184 -31
- meerschaum/actions/start.py +166 -17
- meerschaum/actions/stop.py +38 -2
- meerschaum/actions/sync.py +7 -2
- meerschaum/actions/tag.py +9 -8
- meerschaum/actions/verify.py +5 -8
- meerschaum/api/__init__.py +45 -15
- meerschaum/api/_events.py +46 -4
- meerschaum/api/_oauth2.py +162 -9
- meerschaum/api/_tokens.py +102 -0
- meerschaum/api/dash/__init__.py +0 -3
- meerschaum/api/dash/callbacks/__init__.py +1 -0
- meerschaum/api/dash/callbacks/custom.py +4 -3
- meerschaum/api/dash/callbacks/dashboard.py +198 -118
- meerschaum/api/dash/callbacks/jobs.py +14 -7
- meerschaum/api/dash/callbacks/login.py +10 -1
- meerschaum/api/dash/callbacks/pipes.py +194 -14
- meerschaum/api/dash/callbacks/plugins.py +0 -1
- meerschaum/api/dash/callbacks/register.py +10 -3
- meerschaum/api/dash/callbacks/settings/password_reset.py +2 -2
- meerschaum/api/dash/callbacks/tokens.py +389 -0
- meerschaum/api/dash/components.py +36 -15
- meerschaum/api/dash/jobs.py +1 -1
- meerschaum/api/dash/keys.py +35 -93
- meerschaum/api/dash/pages/__init__.py +2 -1
- meerschaum/api/dash/pages/dashboard.py +1 -20
- meerschaum/api/dash/pages/{job.py → jobs.py} +10 -7
- meerschaum/api/dash/pages/login.py +2 -2
- meerschaum/api/dash/pages/pipes.py +16 -5
- meerschaum/api/dash/pages/settings/password_reset.py +1 -1
- meerschaum/api/dash/pages/tokens.py +53 -0
- meerschaum/api/dash/pipes.py +382 -95
- meerschaum/api/dash/sessions.py +12 -0
- meerschaum/api/dash/tokens.py +603 -0
- meerschaum/api/dash/websockets.py +1 -1
- meerschaum/api/dash/webterm.py +18 -6
- meerschaum/api/models/__init__.py +23 -3
- meerschaum/api/models/_actions.py +22 -0
- meerschaum/api/models/_pipes.py +91 -7
- meerschaum/api/models/_tokens.py +81 -0
- meerschaum/api/resources/static/js/terminado.js +3 -0
- meerschaum/api/resources/static/js/xterm-addon-unicode11.js +2 -0
- meerschaum/api/resources/templates/termpage.html +13 -0
- meerschaum/api/routes/__init__.py +1 -0
- meerschaum/api/routes/_actions.py +3 -4
- meerschaum/api/routes/_connectors.py +3 -7
- meerschaum/api/routes/_jobs.py +26 -35
- meerschaum/api/routes/_login.py +120 -15
- meerschaum/api/routes/_misc.py +5 -10
- meerschaum/api/routes/_pipes.py +178 -143
- meerschaum/api/routes/_plugins.py +38 -28
- meerschaum/api/routes/_tokens.py +236 -0
- meerschaum/api/routes/_users.py +47 -35
- meerschaum/api/routes/_version.py +3 -3
- meerschaum/api/routes/_webterm.py +3 -3
- meerschaum/config/__init__.py +100 -30
- meerschaum/config/_default.py +132 -64
- meerschaum/config/_edit.py +38 -32
- meerschaum/config/_formatting.py +2 -0
- meerschaum/config/_patch.py +10 -8
- meerschaum/config/_paths.py +133 -13
- meerschaum/config/_read_config.py +87 -36
- meerschaum/config/_sync.py +6 -3
- meerschaum/config/_version.py +1 -1
- meerschaum/config/environment.py +262 -0
- meerschaum/config/stack/__init__.py +37 -15
- meerschaum/config/static.py +18 -0
- meerschaum/connectors/_Connector.py +11 -6
- meerschaum/connectors/__init__.py +41 -22
- meerschaum/connectors/api/_APIConnector.py +34 -6
- meerschaum/connectors/api/_actions.py +2 -2
- meerschaum/connectors/api/_jobs.py +12 -1
- meerschaum/connectors/api/_login.py +33 -7
- meerschaum/connectors/api/_misc.py +2 -2
- meerschaum/connectors/api/_pipes.py +23 -32
- meerschaum/connectors/api/_plugins.py +2 -2
- meerschaum/connectors/api/_request.py +1 -1
- meerschaum/connectors/api/_tokens.py +146 -0
- meerschaum/connectors/api/_users.py +70 -58
- meerschaum/connectors/instance/_InstanceConnector.py +83 -0
- meerschaum/connectors/instance/__init__.py +10 -0
- meerschaum/connectors/instance/_pipes.py +442 -0
- meerschaum/connectors/instance/_plugins.py +159 -0
- meerschaum/connectors/instance/_tokens.py +317 -0
- meerschaum/connectors/instance/_users.py +188 -0
- meerschaum/connectors/parse.py +5 -2
- meerschaum/connectors/sql/_SQLConnector.py +22 -5
- meerschaum/connectors/sql/_cli.py +12 -11
- meerschaum/connectors/sql/_create_engine.py +12 -168
- meerschaum/connectors/sql/_fetch.py +2 -18
- meerschaum/connectors/sql/_pipes.py +295 -278
- meerschaum/connectors/sql/_plugins.py +29 -0
- meerschaum/connectors/sql/_sql.py +46 -21
- meerschaum/connectors/sql/_users.py +36 -2
- meerschaum/connectors/sql/tables/__init__.py +254 -122
- meerschaum/connectors/valkey/_ValkeyConnector.py +5 -7
- meerschaum/connectors/valkey/_pipes.py +60 -31
- meerschaum/connectors/valkey/_plugins.py +2 -26
- meerschaum/core/Pipe/__init__.py +115 -85
- meerschaum/core/Pipe/_attributes.py +425 -124
- meerschaum/core/Pipe/_bootstrap.py +54 -24
- meerschaum/core/Pipe/_cache.py +555 -0
- meerschaum/core/Pipe/_clear.py +0 -11
- meerschaum/core/Pipe/_data.py +96 -68
- meerschaum/core/Pipe/_deduplicate.py +0 -13
- meerschaum/core/Pipe/_delete.py +12 -21
- meerschaum/core/Pipe/_drop.py +11 -23
- meerschaum/core/Pipe/_dtypes.py +49 -19
- meerschaum/core/Pipe/_edit.py +14 -4
- meerschaum/core/Pipe/_fetch.py +1 -1
- meerschaum/core/Pipe/_index.py +8 -14
- meerschaum/core/Pipe/_show.py +5 -5
- meerschaum/core/Pipe/_sync.py +123 -204
- meerschaum/core/Pipe/_verify.py +4 -4
- meerschaum/{plugins → core/Plugin}/_Plugin.py +16 -12
- meerschaum/core/Plugin/__init__.py +1 -1
- meerschaum/core/Token/_Token.py +220 -0
- meerschaum/core/Token/__init__.py +12 -0
- meerschaum/core/User/_User.py +35 -10
- meerschaum/core/User/__init__.py +9 -1
- meerschaum/core/__init__.py +1 -0
- meerschaum/jobs/_Executor.py +88 -4
- meerschaum/jobs/_Job.py +149 -38
- meerschaum/jobs/__init__.py +3 -2
- meerschaum/jobs/systemd.py +8 -3
- meerschaum/models/__init__.py +35 -0
- meerschaum/models/pipes.py +247 -0
- meerschaum/models/tokens.py +38 -0
- meerschaum/models/users.py +26 -0
- meerschaum/plugins/__init__.py +301 -88
- meerschaum/plugins/bootstrap.py +510 -4
- meerschaum/utils/_get_pipes.py +97 -30
- meerschaum/utils/daemon/Daemon.py +199 -43
- meerschaum/utils/daemon/FileDescriptorInterceptor.py +0 -1
- meerschaum/utils/daemon/RotatingFile.py +63 -36
- meerschaum/utils/daemon/StdinFile.py +53 -13
- meerschaum/utils/daemon/__init__.py +47 -6
- meerschaum/utils/daemon/_names.py +6 -3
- meerschaum/utils/dataframe.py +479 -81
- meerschaum/utils/debug.py +49 -19
- meerschaum/utils/dtypes/__init__.py +476 -34
- meerschaum/utils/dtypes/sql.py +369 -29
- meerschaum/utils/formatting/__init__.py +5 -2
- meerschaum/utils/formatting/_jobs.py +1 -1
- meerschaum/utils/formatting/_pipes.py +52 -50
- meerschaum/utils/formatting/_pprint.py +1 -0
- meerschaum/utils/formatting/_shell.py +44 -18
- meerschaum/utils/misc.py +268 -186
- meerschaum/utils/packages/__init__.py +25 -40
- meerschaum/utils/packages/_packages.py +42 -34
- meerschaum/utils/pipes.py +213 -0
- meerschaum/utils/process.py +2 -2
- meerschaum/utils/prompt.py +175 -144
- meerschaum/utils/schedule.py +2 -1
- meerschaum/utils/sql.py +134 -47
- meerschaum/utils/threading.py +42 -0
- meerschaum/utils/typing.py +1 -4
- meerschaum/utils/venv/_Venv.py +2 -2
- meerschaum/utils/venv/__init__.py +7 -7
- meerschaum/utils/warnings.py +19 -13
- {meerschaum-2.9.5.dist-info → meerschaum-3.0.0.dist-info}/METADATA +94 -96
- meerschaum-3.0.0.dist-info/RECORD +289 -0
- {meerschaum-2.9.5.dist-info → meerschaum-3.0.0.dist-info}/WHEEL +1 -1
- meerschaum-3.0.0.dist-info/licenses/NOTICE +2 -0
- meerschaum/api/models/_interfaces.py +0 -15
- meerschaum/api/models/_locations.py +0 -15
- meerschaum/api/models/_metrics.py +0 -15
- meerschaum/config/_environment.py +0 -145
- meerschaum/config/static/__init__.py +0 -186
- meerschaum-2.9.5.dist-info/RECORD +0 -263
- {meerschaum-2.9.5.dist-info → meerschaum-3.0.0.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.9.5.dist-info → meerschaum-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {meerschaum-2.9.5.dist-info → meerschaum-3.0.0.dist-info}/top_level.txt +0 -0
- {meerschaum-2.9.5.dist-info → meerschaum-3.0.0.dist-info}/zip-safe +0 -0
meerschaum/utils/misc.py
CHANGED
@@ -6,8 +6,11 @@ Miscellaneous functions go here
|
|
6
6
|
"""
|
7
7
|
|
8
8
|
from __future__ import annotations
|
9
|
+
|
9
10
|
import sys
|
10
|
-
|
11
|
+
import functools
|
12
|
+
from datetime import timedelta, datetime
|
13
|
+
|
11
14
|
from meerschaum.utils.typing import (
|
12
15
|
Union,
|
13
16
|
Any,
|
@@ -19,13 +22,8 @@ from meerschaum.utils.typing import (
|
|
19
22
|
Iterable,
|
20
23
|
PipesDict,
|
21
24
|
Tuple,
|
22
|
-
InstanceConnector,
|
23
|
-
Hashable,
|
24
|
-
Generator,
|
25
|
-
Iterator,
|
26
25
|
TYPE_CHECKING,
|
27
26
|
)
|
28
|
-
import meerschaum as mrsm
|
29
27
|
if TYPE_CHECKING:
|
30
28
|
import collections
|
31
29
|
|
@@ -42,15 +40,18 @@ __pdoc__: Dict[str, bool] = {
|
|
42
40
|
'df_is_chunk_generator': False,
|
43
41
|
'choices_docstring': False,
|
44
42
|
'_get_subaction_names': False,
|
43
|
+
'is_pipe_registered': False,
|
44
|
+
'replace_pipes_in_dict': False,
|
45
|
+
'round_time': False,
|
45
46
|
}
|
46
47
|
|
47
48
|
|
48
49
|
def add_method_to_class(
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
50
|
+
func: Callable[[Any], Any],
|
51
|
+
class_def: 'Class',
|
52
|
+
method_name: Optional[str] = None,
|
53
|
+
keep_self: Optional[bool] = None,
|
54
|
+
) -> Callable[[Any], Any]:
|
54
55
|
"""
|
55
56
|
Add function `func` to class `class_def`.
|
56
57
|
|
@@ -122,16 +123,33 @@ def is_int(s: str) -> bool:
|
|
122
123
|
|
123
124
|
"""
|
124
125
|
try:
|
125
|
-
float(s)
|
126
|
+
return float(s).is_integer()
|
126
127
|
except Exception:
|
127
128
|
return False
|
128
|
-
|
129
|
-
return float(s).is_integer()
|
130
129
|
|
131
130
|
|
132
|
-
def
|
133
|
-
|
134
|
-
|
131
|
+
def is_uuid(s: str) -> bool:
|
132
|
+
"""
|
133
|
+
Check if a string is a valid UUID.
|
134
|
+
|
135
|
+
Parameters
|
136
|
+
----------
|
137
|
+
s: str
|
138
|
+
The string to be checked.
|
139
|
+
|
140
|
+
Returns
|
141
|
+
-------
|
142
|
+
A bool indicating whether the string is a valid UUID.
|
143
|
+
"""
|
144
|
+
import uuid
|
145
|
+
try:
|
146
|
+
uuid.UUID(str(s))
|
147
|
+
return True
|
148
|
+
except Exception:
|
149
|
+
return False
|
150
|
+
|
151
|
+
|
152
|
+
def string_to_dict(params_string: str) -> Dict[str, Any]:
|
135
153
|
"""
|
136
154
|
Parse a string into a dictionary.
|
137
155
|
|
@@ -154,7 +172,7 @@ def string_to_dict(
|
|
154
172
|
{'a': 1, 'b': 2}
|
155
173
|
|
156
174
|
"""
|
157
|
-
if params_string
|
175
|
+
if not params_string:
|
158
176
|
return {}
|
159
177
|
|
160
178
|
import json
|
@@ -169,13 +187,60 @@ def string_to_dict(
|
|
169
187
|
and params_string[-2] == "}"
|
170
188
|
):
|
171
189
|
return json.loads(params_string[1:-1])
|
190
|
+
|
172
191
|
if str(params_string).startswith('{'):
|
173
192
|
return json.loads(params_string)
|
174
193
|
|
175
194
|
import ast
|
176
195
|
params_dict = {}
|
177
|
-
|
196
|
+
|
197
|
+
items = []
|
198
|
+
bracket_level = 0
|
199
|
+
brace_level = 0
|
200
|
+
current_item = ''
|
201
|
+
in_quotes = False
|
202
|
+
quote_char = ''
|
203
|
+
|
204
|
+
i = 0
|
205
|
+
while i < len(params_string):
|
206
|
+
char = params_string[i]
|
207
|
+
|
208
|
+
if in_quotes:
|
209
|
+
if char == quote_char and (i == 0 or params_string[i-1] != '\\'):
|
210
|
+
in_quotes = False
|
211
|
+
else:
|
212
|
+
if char in ('"', "'"):
|
213
|
+
in_quotes = True
|
214
|
+
quote_char = char
|
215
|
+
elif char == '[':
|
216
|
+
bracket_level += 1
|
217
|
+
elif char == ']':
|
218
|
+
bracket_level -= 1
|
219
|
+
elif char == '{':
|
220
|
+
brace_level += 1
|
221
|
+
elif char == '}':
|
222
|
+
brace_level -= 1
|
223
|
+
elif char == ',' and bracket_level == 0 and brace_level == 0:
|
224
|
+
items.append(current_item)
|
225
|
+
current_item = ''
|
226
|
+
i += 1
|
227
|
+
continue
|
228
|
+
|
229
|
+
current_item += char
|
230
|
+
i += 1
|
231
|
+
|
232
|
+
if current_item:
|
233
|
+
items.append(current_item)
|
234
|
+
|
235
|
+
for param in items:
|
236
|
+
param = param.strip()
|
237
|
+
if not param:
|
238
|
+
continue
|
239
|
+
|
178
240
|
_keys = param.split(":", maxsplit=1)
|
241
|
+
if len(_keys) != 2:
|
242
|
+
continue
|
243
|
+
|
179
244
|
keys = _keys[:-1]
|
180
245
|
try:
|
181
246
|
val = ast.literal_eval(_keys[-1])
|
@@ -197,12 +262,35 @@ def string_to_dict(
|
|
197
262
|
return params_dict
|
198
263
|
|
199
264
|
|
265
|
+
def to_simple_dict(doc: Dict[str, Any]) -> str:
|
266
|
+
"""
|
267
|
+
Serialize a document dictionary in simple-dict format.
|
268
|
+
"""
|
269
|
+
import json
|
270
|
+
import ast
|
271
|
+
from meerschaum.utils.dtypes import json_serialize_value
|
272
|
+
|
273
|
+
def serialize_value(value):
|
274
|
+
if isinstance(value, str):
|
275
|
+
try:
|
276
|
+
evaluated = ast.literal_eval(value)
|
277
|
+
if not isinstance(evaluated, str):
|
278
|
+
return json.dumps(value, separators=(',', ':'), default=json_serialize_value)
|
279
|
+
return value
|
280
|
+
except (ValueError, SyntaxError, TypeError, MemoryError):
|
281
|
+
return value
|
282
|
+
|
283
|
+
return json.dumps(value, separators=(',', ':'), default=json_serialize_value)
|
284
|
+
|
285
|
+
return ','.join(f"{key}:{serialize_value(val)}" for key, val in doc.items())
|
286
|
+
|
287
|
+
|
200
288
|
def parse_config_substitution(
|
201
289
|
value: str,
|
202
290
|
leading_key: str = 'MRSM',
|
203
291
|
begin_key: str = '{',
|
204
292
|
end_key: str = '}',
|
205
|
-
delimeter: str = ':'
|
293
|
+
delimeter: str = ':',
|
206
294
|
) -> List[Any]:
|
207
295
|
"""
|
208
296
|
Parse Meerschaum substitution syntax
|
@@ -259,37 +347,6 @@ def edit_file(
|
|
259
347
|
return rc == 0
|
260
348
|
|
261
349
|
|
262
|
-
def is_pipe_registered(
|
263
|
-
pipe: mrsm.Pipe,
|
264
|
-
pipes: PipesDict,
|
265
|
-
debug: bool = False
|
266
|
-
) -> bool:
|
267
|
-
"""
|
268
|
-
Check if a Pipe is inside the pipes dictionary.
|
269
|
-
|
270
|
-
Parameters
|
271
|
-
----------
|
272
|
-
pipe: meerschaum.Pipe
|
273
|
-
The pipe to see if it's in the dictionary.
|
274
|
-
|
275
|
-
pipes: PipesDict
|
276
|
-
The dictionary to search inside.
|
277
|
-
|
278
|
-
debug: bool, default False
|
279
|
-
Verbosity toggle.
|
280
|
-
|
281
|
-
Returns
|
282
|
-
-------
|
283
|
-
A bool indicating whether the pipe is inside the dictionary.
|
284
|
-
"""
|
285
|
-
from meerschaum.utils.debug import dprint
|
286
|
-
ck, mk, lk = pipe.connector_keys, pipe.metric_key, pipe.location_key
|
287
|
-
if debug:
|
288
|
-
dprint(f'{ck}, {mk}, {lk}')
|
289
|
-
dprint(f'{pipe}, {pipes}')
|
290
|
-
return ck in pipes and mk in pipes[ck] and lk in pipes[ck][mk]
|
291
|
-
|
292
|
-
|
293
350
|
def get_cols_lines(default_cols: int = 100, default_lines: int = 120) -> Tuple[int, int]:
|
294
351
|
"""
|
295
352
|
Determine the columns and lines in the terminal.
|
@@ -311,7 +368,7 @@ def get_cols_lines(default_cols: int = 100, default_lines: int = 120) -> Tuple[i
|
|
311
368
|
try:
|
312
369
|
size = os.get_terminal_size()
|
313
370
|
_cols, _lines = size.columns, size.lines
|
314
|
-
except Exception
|
371
|
+
except Exception:
|
315
372
|
_cols, _lines = (
|
316
373
|
int(os.environ.get('COLUMNS', str(default_cols))),
|
317
374
|
int(os.environ.get('LINES', str(default_lines))),
|
@@ -367,7 +424,7 @@ def sorted_dict(d: Dict[Any, Any]) -> Dict[Any, Any]:
|
|
367
424
|
"""
|
368
425
|
try:
|
369
426
|
return {key: value for key, value in sorted(d.items(), key=lambda item: item[1])}
|
370
|
-
except Exception
|
427
|
+
except Exception:
|
371
428
|
return d
|
372
429
|
|
373
430
|
def flatten_pipes_dict(pipes_dict: PipesDict) -> List[Pipe]:
|
@@ -391,81 +448,13 @@ def flatten_pipes_dict(pipes_dict: PipesDict) -> List[Pipe]:
|
|
391
448
|
return pipes_list
|
392
449
|
|
393
450
|
|
394
|
-
def round_time(
|
395
|
-
dt: Optional[datetime] = None,
|
396
|
-
date_delta: Optional[timedelta] = None,
|
397
|
-
to: 'str' = 'down'
|
398
|
-
) -> datetime:
|
399
|
-
"""
|
400
|
-
Round a datetime object to a multiple of a timedelta.
|
401
|
-
http://stackoverflow.com/questions/3463930/how-to-round-the-minute-of-a-datetime-object-python
|
402
|
-
|
403
|
-
NOTE: This function strips timezone information!
|
404
|
-
|
405
|
-
Parameters
|
406
|
-
----------
|
407
|
-
dt: Optional[datetime], default None
|
408
|
-
If `None`, grab the current UTC datetime.
|
409
|
-
|
410
|
-
date_delta: Optional[timedelta], default None
|
411
|
-
If `None`, use a delta of 1 minute.
|
412
|
-
|
413
|
-
to: 'str', default 'down'
|
414
|
-
Available options are `'up'`, `'down'`, and `'closest'`.
|
415
|
-
|
416
|
-
Returns
|
417
|
-
-------
|
418
|
-
A rounded `datetime` object.
|
419
|
-
|
420
|
-
Examples
|
421
|
-
--------
|
422
|
-
>>> round_time(datetime(2022, 1, 1, 12, 15, 57, 200))
|
423
|
-
datetime.datetime(2022, 1, 1, 12, 15)
|
424
|
-
>>> round_time(datetime(2022, 1, 1, 12, 15, 57, 200), to='up')
|
425
|
-
datetime.datetime(2022, 1, 1, 12, 16)
|
426
|
-
>>> round_time(datetime(2022, 1, 1, 12, 15, 57, 200), timedelta(hours=1))
|
427
|
-
datetime.datetime(2022, 1, 1, 12, 0)
|
428
|
-
>>> round_time(
|
429
|
-
... datetime(2022, 1, 1, 12, 15, 57, 200),
|
430
|
-
... timedelta(hours=1),
|
431
|
-
... to = 'closest'
|
432
|
-
... )
|
433
|
-
datetime.datetime(2022, 1, 1, 12, 0)
|
434
|
-
>>> round_time(
|
435
|
-
... datetime(2022, 1, 1, 12, 45, 57, 200),
|
436
|
-
... datetime.timedelta(hours=1),
|
437
|
-
... to = 'closest'
|
438
|
-
... )
|
439
|
-
datetime.datetime(2022, 1, 1, 13, 0)
|
440
|
-
|
441
|
-
"""
|
442
|
-
if date_delta is None:
|
443
|
-
date_delta = timedelta(minutes=1)
|
444
|
-
round_to = date_delta.total_seconds()
|
445
|
-
if dt is None:
|
446
|
-
dt = datetime.now(timezone.utc).replace(tzinfo=None)
|
447
|
-
seconds = (dt.replace(tzinfo=None) - dt.min.replace(tzinfo=None)).seconds
|
448
|
-
|
449
|
-
if seconds % round_to == 0 and dt.microsecond == 0:
|
450
|
-
rounding = (seconds + round_to / 2) // round_to * round_to
|
451
|
-
else:
|
452
|
-
if to == 'up':
|
453
|
-
rounding = (seconds + dt.microsecond/1000000 + round_to) // round_to * round_to
|
454
|
-
elif to == 'down':
|
455
|
-
rounding = seconds // round_to * round_to
|
456
|
-
else:
|
457
|
-
rounding = (seconds + round_to / 2) // round_to * round_to
|
458
|
-
|
459
|
-
return dt + timedelta(0, rounding - seconds, - dt.microsecond)
|
460
|
-
|
461
|
-
|
462
451
|
def timed_input(
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
452
|
+
seconds: int = 10,
|
453
|
+
timeout_message: str = "",
|
454
|
+
prompt: str = "",
|
455
|
+
icon: bool = False,
|
456
|
+
**kw
|
457
|
+
) -> Union[str, None]:
|
469
458
|
"""
|
470
459
|
Accept user input only for a brief period of time.
|
471
460
|
|
@@ -515,52 +504,6 @@ def timed_input(
|
|
515
504
|
signal.alarm(0) # cancel alarm
|
516
505
|
|
517
506
|
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
def replace_pipes_in_dict(
|
522
|
-
pipes : Optional[PipesDict] = None,
|
523
|
-
func: 'function' = str,
|
524
|
-
debug: bool = False,
|
525
|
-
**kw
|
526
|
-
) -> PipesDict:
|
527
|
-
"""
|
528
|
-
Replace the Pipes in a Pipes dict with the result of another function.
|
529
|
-
|
530
|
-
Parameters
|
531
|
-
----------
|
532
|
-
pipes: Optional[PipesDict], default None
|
533
|
-
The pipes dict to be processed.
|
534
|
-
|
535
|
-
func: Callable[[Any], Any], default str
|
536
|
-
The function to be applied to every pipe.
|
537
|
-
Defaults to the string constructor.
|
538
|
-
|
539
|
-
debug: bool, default False
|
540
|
-
Verbosity toggle.
|
541
|
-
|
542
|
-
|
543
|
-
Returns
|
544
|
-
-------
|
545
|
-
A dictionary where every pipe is replaced with the output of a function.
|
546
|
-
|
547
|
-
"""
|
548
|
-
import copy
|
549
|
-
def change_dict(d : Dict[Any, Any], func : 'function') -> None:
|
550
|
-
for k, v in d.items():
|
551
|
-
if isinstance(v, dict):
|
552
|
-
change_dict(v, func)
|
553
|
-
else:
|
554
|
-
d[k] = func(v)
|
555
|
-
|
556
|
-
if pipes is None:
|
557
|
-
from meerschaum import get_pipes
|
558
|
-
pipes = get_pipes(debug=debug, **kw)
|
559
|
-
|
560
|
-
result = copy.deepcopy(pipes)
|
561
|
-
change_dict(result, func)
|
562
|
-
return result
|
563
|
-
|
564
507
|
def enforce_gevent_monkey_patch():
|
565
508
|
"""
|
566
509
|
Check if gevent monkey patching is enabled, and if not, then apply patching.
|
@@ -634,10 +577,10 @@ def string_width(string: str, widest: bool = True) -> int:
|
|
634
577
|
return _widest()
|
635
578
|
|
636
579
|
def _pyinstaller_traverse_dir(
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
580
|
+
directory: str,
|
581
|
+
ignore_patterns: Iterable[str] = ('.pyc', 'dist', 'build', '.git', '.log'),
|
582
|
+
include_dotfiles: bool = False
|
583
|
+
) -> list:
|
641
584
|
"""
|
642
585
|
Recursively traverse a directory and return a list of its contents.
|
643
586
|
"""
|
@@ -677,6 +620,43 @@ def _pyinstaller_traverse_dir(
|
|
677
620
|
return paths
|
678
621
|
|
679
622
|
|
623
|
+
def get_val_from_dict_path(d: Dict[Any, Any], path: Tuple[Any, ...]) -> Any:
|
624
|
+
"""
|
625
|
+
Get a value from a dictionary with a tuple of keys.
|
626
|
+
|
627
|
+
Parameters
|
628
|
+
----------
|
629
|
+
d: Dict[Any, Any]
|
630
|
+
The dictionary to search.
|
631
|
+
|
632
|
+
path: Tuple[Any, ...]
|
633
|
+
The path of keys to traverse.
|
634
|
+
|
635
|
+
Returns
|
636
|
+
-------
|
637
|
+
The value from the end of the path.
|
638
|
+
"""
|
639
|
+
return functools.reduce(lambda di, key: di[key], path, d)
|
640
|
+
|
641
|
+
|
642
|
+
def set_val_in_dict_path(d: Dict[Any, Any], path: Tuple[Any, ...], val: Any) -> None:
|
643
|
+
"""
|
644
|
+
Set a value in a dictionary with a tuple of keys.
|
645
|
+
|
646
|
+
Parameters
|
647
|
+
----------
|
648
|
+
d: Dict[Any, Any]
|
649
|
+
The dictionary to search.
|
650
|
+
|
651
|
+
path: Tuple[Any, ...]
|
652
|
+
The path of keys to traverse.
|
653
|
+
|
654
|
+
val: Any
|
655
|
+
The value to set at the end of the path.
|
656
|
+
"""
|
657
|
+
get_val_from_dict_path(d, path[:-1])[path[-1]] = val
|
658
|
+
|
659
|
+
|
680
660
|
def replace_password(d: Dict[str, Any], replace_with: str = '*') -> Dict[str, Any]:
|
681
661
|
"""
|
682
662
|
Recursively replace passwords in a dictionary.
|
@@ -717,7 +697,7 @@ def replace_password(d: Dict[str, Any], replace_with: str = '*') -> Dict[str, An
|
|
717
697
|
from meerschaum.connectors.sql import SQLConnector
|
718
698
|
try:
|
719
699
|
uri_params = SQLConnector.parse_uri(v)
|
720
|
-
except Exception
|
700
|
+
except Exception:
|
721
701
|
uri_params = None
|
722
702
|
if not uri_params:
|
723
703
|
continue
|
@@ -877,6 +857,7 @@ def dict_from_od(od: collections.OrderedDict) -> Dict[Any, Any]:
|
|
877
857
|
_d[k] = dict_from_od(v)
|
878
858
|
return _d
|
879
859
|
|
860
|
+
|
880
861
|
def remove_ansi(s: str) -> str:
|
881
862
|
"""
|
882
863
|
Remove ANSI escape characters from a string.
|
@@ -1153,7 +1134,7 @@ def items_str(
|
|
1153
1134
|
return output
|
1154
1135
|
|
1155
1136
|
|
1156
|
-
def interval_str(delta: Union[timedelta, int]) -> str:
|
1137
|
+
def interval_str(delta: Union[timedelta, int], round_unit: bool = False) -> str:
|
1157
1138
|
"""
|
1158
1139
|
Return a human-readable string for a `timedelta` (or `int` minutes).
|
1159
1140
|
|
@@ -1162,20 +1143,57 @@ def interval_str(delta: Union[timedelta, int]) -> str:
|
|
1162
1143
|
delta: Union[timedelta, int]
|
1163
1144
|
The interval to print. If `delta` is an integer, assume it corresponds to minutes.
|
1164
1145
|
|
1146
|
+
round_unit: bool, default False
|
1147
|
+
If `True`, round the output to a single unit.
|
1148
|
+
|
1165
1149
|
Returns
|
1166
1150
|
-------
|
1167
1151
|
A formatted string, fit for human eyes.
|
1168
1152
|
"""
|
1169
1153
|
from meerschaum.utils.packages import attempt_import
|
1170
|
-
if is_int(delta):
|
1154
|
+
if is_int(str(delta)) and not round_unit:
|
1171
1155
|
return str(delta)
|
1172
|
-
|
1156
|
+
|
1157
|
+
humanfriendly = attempt_import('humanfriendly', lazy=False)
|
1173
1158
|
delta_seconds = (
|
1174
1159
|
delta.total_seconds()
|
1175
|
-
if
|
1160
|
+
if hasattr(delta, 'total_seconds')
|
1176
1161
|
else (delta * 60)
|
1177
1162
|
)
|
1178
|
-
|
1163
|
+
|
1164
|
+
is_negative = delta_seconds < 0
|
1165
|
+
delta_seconds = abs(delta_seconds)
|
1166
|
+
replace_units = {}
|
1167
|
+
|
1168
|
+
if round_unit:
|
1169
|
+
if delta_seconds < 1:
|
1170
|
+
delta_seconds = round(delta_seconds, 2)
|
1171
|
+
elif delta_seconds < 60:
|
1172
|
+
delta_seconds = int(delta_seconds)
|
1173
|
+
elif delta_seconds < 3600:
|
1174
|
+
delta_seconds = int(delta_seconds / 60) * 60
|
1175
|
+
elif delta_seconds < 86400:
|
1176
|
+
delta_seconds = int(delta_seconds / 3600) * 3600
|
1177
|
+
elif delta_seconds < (86400 * 7):
|
1178
|
+
delta_seconds = int(delta_seconds / 86400) * 86400
|
1179
|
+
elif delta_seconds < (86400 * 7 * 4):
|
1180
|
+
delta_seconds = int(delta_seconds / (86400 * 7)) * (86400 * 7)
|
1181
|
+
elif delta_seconds < (86400 * 7 * 4 * 13):
|
1182
|
+
delta_seconds = int(delta_seconds / (86400 * 7 * 4)) * (86400 * 7)
|
1183
|
+
replace_units['weeks'] = 'months'
|
1184
|
+
else:
|
1185
|
+
delta_seconds = int(delta_seconds / (86400 * 364)) * (86400 * 364)
|
1186
|
+
|
1187
|
+
delta_str = humanfriendly.format_timespan(delta_seconds)
|
1188
|
+
if ',' in delta_str and round_unit:
|
1189
|
+
delta_str = delta_str.split(',')[0]
|
1190
|
+
elif ' and ' in delta_str and round_unit:
|
1191
|
+
delta_str = delta_str.split(' and ')[0]
|
1192
|
+
|
1193
|
+
for parsed_unit, replacement_unit in replace_units.items():
|
1194
|
+
delta_str = delta_str.replace(parsed_unit, replacement_unit)
|
1195
|
+
|
1196
|
+
return delta_str + (' ago' if is_negative else '')
|
1179
1197
|
|
1180
1198
|
|
1181
1199
|
def is_docker_available() -> bool:
|
@@ -1214,11 +1232,15 @@ def is_systemd_available() -> bool:
|
|
1214
1232
|
import subprocess
|
1215
1233
|
try:
|
1216
1234
|
has_systemctl = subprocess.call(
|
1217
|
-
['systemctl', '
|
1235
|
+
['systemctl', 'whoami'],
|
1218
1236
|
stdout=subprocess.DEVNULL,
|
1219
1237
|
stderr=subprocess.STDOUT,
|
1220
1238
|
) == 0
|
1239
|
+
except FileNotFoundError:
|
1240
|
+
has_systemctl = False
|
1221
1241
|
except Exception:
|
1242
|
+
import traceback
|
1243
|
+
traceback.print_exc()
|
1222
1244
|
has_systemctl = False
|
1223
1245
|
return has_systemctl
|
1224
1246
|
|
@@ -1234,6 +1256,8 @@ def is_tmux_available() -> bool:
|
|
1234
1256
|
stdout=subprocess.DEVNULL,
|
1235
1257
|
stderr=subprocess.STDOUT
|
1236
1258
|
) == 0
|
1259
|
+
except FileNotFoundError:
|
1260
|
+
has_tmux = False
|
1237
1261
|
except Exception:
|
1238
1262
|
has_tmux = False
|
1239
1263
|
return has_tmux
|
@@ -1406,7 +1430,7 @@ def separate_negation_values(
|
|
1406
1430
|
If `None`, use the system default (`_`).
|
1407
1431
|
"""
|
1408
1432
|
if negation_prefix is None:
|
1409
|
-
from meerschaum.
|
1433
|
+
from meerschaum._internal.static import STATIC_CONFIG
|
1410
1434
|
negation_prefix = STATIC_CONFIG['system']['fetch_pipes_keys']['negation_prefix']
|
1411
1435
|
_in_vals, _ex_vals = [], []
|
1412
1436
|
for v in vals:
|
@@ -1442,7 +1466,7 @@ def get_in_ex_params(params: Optional[Dict[str, Any]]) -> Dict[str, Tuple[List[A
|
|
1442
1466
|
col: separate_negation_values(
|
1443
1467
|
(
|
1444
1468
|
val
|
1445
|
-
if isinstance(val, (list, tuple))
|
1469
|
+
if isinstance(val, (list, tuple, set)) or hasattr(val, 'astype')
|
1446
1470
|
else [val]
|
1447
1471
|
)
|
1448
1472
|
)
|
@@ -1610,6 +1634,36 @@ def safely_extract_tar(tarf: 'file', output_dir: Union[str, 'pathlib.Path']) ->
|
|
1610
1634
|
return safe_extract(tarf, output_dir)
|
1611
1635
|
|
1612
1636
|
|
1637
|
+
def to_snake_case(name: str) -> str:
|
1638
|
+
"""
|
1639
|
+
Return the given string in snake-case-style.
|
1640
|
+
|
1641
|
+
Parameters
|
1642
|
+
----------
|
1643
|
+
name: str
|
1644
|
+
The input text to convert to snake case.
|
1645
|
+
|
1646
|
+
Returns
|
1647
|
+
-------
|
1648
|
+
A snake-case version of `name`.
|
1649
|
+
|
1650
|
+
Examples
|
1651
|
+
--------
|
1652
|
+
>>> to_snake_case("HelloWorld!")
|
1653
|
+
'hello_world'
|
1654
|
+
>>> to_snake_case("This has spaces in it.")
|
1655
|
+
'this_has_spaces_in_it'
|
1656
|
+
>>> to_snake_case("already_in_snake_case")
|
1657
|
+
'already_in_snake_case'
|
1658
|
+
"""
|
1659
|
+
import re
|
1660
|
+
name = re.sub(r'(.)([A-Z][a-z]+)', r'\1_\2', name)
|
1661
|
+
name = re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', name)
|
1662
|
+
name = re.sub(r'[^\w\s]', '', name)
|
1663
|
+
name = re.sub(r'\s+', '_', name)
|
1664
|
+
return name.lower()
|
1665
|
+
|
1666
|
+
|
1613
1667
|
##################
|
1614
1668
|
# Legacy imports #
|
1615
1669
|
##################
|
@@ -1756,6 +1810,33 @@ def json_serialize_datetime(dt: datetime) -> Union[str, None]:
|
|
1756
1810
|
return serialize_datetime(dt)
|
1757
1811
|
|
1758
1812
|
|
1813
|
+
def replace_pipes_in_dict(*args, **kwargs):
|
1814
|
+
"""
|
1815
|
+
Placeholder function to prevent breaking legacy behavior.
|
1816
|
+
See `meerschaum.utils.pipes.replace_pipes_in_dict`.
|
1817
|
+
"""
|
1818
|
+
from meerschaum.utils.pipes import replace_pipes_in_dict
|
1819
|
+
return replace_pipes_in_dict(*args, **kwargs)
|
1820
|
+
|
1821
|
+
|
1822
|
+
def is_pipe_registered(*args, **kwargs):
|
1823
|
+
"""
|
1824
|
+
Placeholder function to prevent breaking legacy behavior.
|
1825
|
+
See `meerschaum.utils.pipes.is_pipe_registered`.
|
1826
|
+
"""
|
1827
|
+
from meerschaum.utils.pipes import is_pipe_registered
|
1828
|
+
return is_pipe_registered(*args, **kwargs)
|
1829
|
+
|
1830
|
+
|
1831
|
+
def round_time(*args, **kwargs):
|
1832
|
+
"""
|
1833
|
+
Placeholder function to prevent breaking legacy behavior.
|
1834
|
+
See `meerschaum.utils.dtypes.round_time`.
|
1835
|
+
"""
|
1836
|
+
from meerschaum.utils.dtypes import round_time
|
1837
|
+
return round_time(*args, **kwargs)
|
1838
|
+
|
1839
|
+
|
1759
1840
|
_current_module = sys.modules[__name__]
|
1760
1841
|
__all__ = tuple(
|
1761
1842
|
name
|
@@ -1763,4 +1844,5 @@ __all__ = tuple(
|
|
1763
1844
|
if callable(obj)
|
1764
1845
|
and name not in __pdoc__
|
1765
1846
|
and getattr(obj, '__module__', None) == _current_module.__name__
|
1847
|
+
and not name.startswith('_')
|
1766
1848
|
)
|