meerschaum 2.9.5__py3-none-any.whl → 3.0.0rc2__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 +19 -2
- meerschaum/_internal/docs/index.py +49 -2
- meerschaum/_internal/entry.py +6 -6
- meerschaum/_internal/shell/Shell.py +1 -1
- meerschaum/_internal/static.py +356 -0
- meerschaum/actions/api.py +12 -2
- meerschaum/actions/bootstrap.py +7 -7
- meerschaum/actions/edit.py +142 -18
- meerschaum/actions/register.py +137 -6
- meerschaum/actions/show.py +117 -29
- meerschaum/actions/stop.py +4 -1
- meerschaum/actions/sync.py +1 -1
- meerschaum/actions/tag.py +9 -8
- meerschaum/actions/verify.py +5 -8
- meerschaum/api/__init__.py +11 -3
- meerschaum/api/_events.py +39 -2
- meerschaum/api/_oauth2.py +118 -8
- meerschaum/api/_tokens.py +102 -0
- meerschaum/api/dash/__init__.py +0 -3
- meerschaum/api/dash/callbacks/custom.py +2 -2
- meerschaum/api/dash/callbacks/dashboard.py +103 -19
- meerschaum/api/dash/callbacks/plugins.py +0 -1
- meerschaum/api/dash/callbacks/register.py +1 -1
- meerschaum/api/dash/callbacks/settings/__init__.py +1 -0
- meerschaum/api/dash/callbacks/settings/password_reset.py +2 -2
- meerschaum/api/dash/callbacks/settings/tokens.py +388 -0
- meerschaum/api/dash/components.py +30 -8
- meerschaum/api/dash/keys.py +19 -93
- meerschaum/api/dash/pages/dashboard.py +1 -20
- meerschaum/api/dash/pages/settings/__init__.py +1 -0
- meerschaum/api/dash/pages/settings/password_reset.py +1 -1
- meerschaum/api/dash/pages/settings/tokens.py +55 -0
- meerschaum/api/dash/pipes.py +94 -59
- meerschaum/api/dash/sessions.py +12 -0
- meerschaum/api/dash/tokens.py +606 -0
- meerschaum/api/dash/websockets.py +1 -1
- meerschaum/api/dash/webterm.py +4 -0
- meerschaum/api/models/__init__.py +23 -3
- meerschaum/api/models/_actions.py +22 -0
- meerschaum/api/models/_pipes.py +85 -7
- meerschaum/api/models/_tokens.py +81 -0
- meerschaum/api/resources/templates/termpage.html +12 -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 +14 -35
- meerschaum/api/routes/_login.py +49 -12
- meerschaum/api/routes/_misc.py +5 -10
- meerschaum/api/routes/_pipes.py +173 -140
- 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/config/__init__.py +43 -20
- meerschaum/config/_default.py +43 -6
- meerschaum/config/_edit.py +28 -24
- meerschaum/config/_environment.py +1 -1
- meerschaum/config/_patch.py +6 -6
- meerschaum/config/_paths.py +5 -1
- meerschaum/config/_read_config.py +65 -34
- meerschaum/config/_sync.py +6 -3
- meerschaum/config/_version.py +1 -1
- meerschaum/config/stack/__init__.py +31 -11
- meerschaum/config/static.py +18 -0
- meerschaum/connectors/_Connector.py +10 -4
- meerschaum/connectors/__init__.py +4 -20
- meerschaum/connectors/api/_APIConnector.py +34 -6
- meerschaum/connectors/api/_actions.py +2 -2
- meerschaum/connectors/api/_jobs.py +1 -1
- meerschaum/connectors/api/_login.py +33 -7
- meerschaum/connectors/api/_misc.py +2 -2
- meerschaum/connectors/api/_pipes.py +16 -31
- 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 +151 -0
- meerschaum/connectors/instance/_tokens.py +296 -0
- meerschaum/connectors/instance/_users.py +181 -0
- meerschaum/connectors/parse.py +4 -1
- meerschaum/connectors/sql/_SQLConnector.py +8 -5
- meerschaum/connectors/sql/_cli.py +12 -11
- meerschaum/connectors/sql/_create_engine.py +9 -168
- meerschaum/connectors/sql/_fetch.py +2 -18
- meerschaum/connectors/sql/_pipes.py +156 -190
- meerschaum/connectors/sql/_plugins.py +29 -0
- meerschaum/connectors/sql/_sql.py +46 -21
- meerschaum/connectors/sql/_users.py +29 -2
- meerschaum/connectors/sql/tables/__init__.py +1 -1
- meerschaum/connectors/valkey/_ValkeyConnector.py +2 -4
- meerschaum/connectors/valkey/_pipes.py +53 -26
- meerschaum/connectors/valkey/_plugins.py +2 -26
- meerschaum/core/Pipe/__init__.py +59 -19
- meerschaum/core/Pipe/_attributes.py +412 -90
- meerschaum/core/Pipe/_bootstrap.py +54 -24
- meerschaum/core/Pipe/_data.py +96 -18
- meerschaum/core/Pipe/_dtypes.py +48 -18
- meerschaum/core/Pipe/_edit.py +14 -4
- meerschaum/core/Pipe/_fetch.py +1 -1
- meerschaum/core/Pipe/_show.py +5 -5
- meerschaum/core/Pipe/_sync.py +118 -193
- meerschaum/core/Pipe/_verify.py +4 -4
- meerschaum/{plugins → core/Plugin}/_Plugin.py +9 -11
- 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 +34 -8
- meerschaum/core/User/__init__.py +9 -1
- meerschaum/core/__init__.py +1 -0
- meerschaum/jobs/_Job.py +3 -2
- meerschaum/jobs/__init__.py +3 -2
- meerschaum/jobs/systemd.py +1 -1
- 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 +22 -7
- meerschaum/plugins/bootstrap.py +2 -1
- meerschaum/utils/_get_pipes.py +68 -27
- meerschaum/utils/daemon/Daemon.py +2 -1
- meerschaum/utils/daemon/__init__.py +30 -2
- meerschaum/utils/dataframe.py +473 -81
- meerschaum/utils/debug.py +15 -15
- meerschaum/utils/dtypes/__init__.py +473 -34
- meerschaum/utils/dtypes/sql.py +368 -28
- meerschaum/utils/formatting/__init__.py +1 -1
- meerschaum/utils/formatting/_pipes.py +5 -4
- meerschaum/utils/formatting/_shell.py +11 -9
- meerschaum/utils/misc.py +246 -148
- meerschaum/utils/packages/__init__.py +10 -27
- meerschaum/utils/packages/_packages.py +41 -34
- meerschaum/utils/pipes.py +181 -0
- meerschaum/utils/process.py +1 -1
- meerschaum/utils/prompt.py +3 -1
- meerschaum/utils/schedule.py +2 -1
- meerschaum/utils/sql.py +121 -44
- meerschaum/utils/typing.py +1 -4
- meerschaum/utils/venv/_Venv.py +2 -2
- meerschaum/utils/venv/__init__.py +5 -7
- {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc2.dist-info}/METADATA +92 -96
- meerschaum-3.0.0rc2.dist-info/RECORD +283 -0
- {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc2.dist-info}/WHEEL +1 -1
- meerschaum-3.0.0rc2.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/static/__init__.py +0 -186
- meerschaum-2.9.5.dist-info/RECORD +0 -263
- {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc2.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc2.dist-info}/licenses/LICENSE +0 -0
- {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc2.dist-info}/top_level.txt +0 -0
- {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc2.dist-info}/zip-safe +0 -0
@@ -247,7 +247,7 @@ def print_tuple(
|
|
247
247
|
If `True`, use the default emoji and color scheme.
|
248
248
|
|
249
249
|
"""
|
250
|
-
from meerschaum.
|
250
|
+
from meerschaum._internal.static import STATIC_CONFIG
|
251
251
|
do_print = True
|
252
252
|
|
253
253
|
omit_messages = STATIC_CONFIG['system']['success']['ignore']
|
@@ -12,6 +12,7 @@ import meerschaum as mrsm
|
|
12
12
|
from meerschaum.utils.typing import PipesDict, Dict, Union, Optional, SuccessTuple, Any, List
|
13
13
|
from meerschaum.config import get_config
|
14
14
|
|
15
|
+
|
15
16
|
def pprint_pipes(pipes: PipesDict) -> None:
|
16
17
|
"""Print a stylized tree of a Pipes dictionary.
|
17
18
|
Supports ANSI and UNICODE global settings."""
|
@@ -379,10 +380,10 @@ def highlight_pipes(message: str) -> str:
|
|
379
380
|
|
380
381
|
|
381
382
|
def format_pipe_success_tuple(
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
383
|
+
pipe: mrsm.Pipe,
|
384
|
+
success_tuple: SuccessTuple,
|
385
|
+
nopretty: bool = False,
|
386
|
+
) -> str:
|
386
387
|
"""
|
387
388
|
Return a formatted string of a pipe and its resulting SuccessTuple.
|
388
389
|
|
@@ -11,15 +11,12 @@ from meerschaum.utils.threading import Lock
|
|
11
11
|
_locks = {'_tried_clear_command': Lock()}
|
12
12
|
|
13
13
|
|
14
|
-
def make_header(
|
15
|
-
message: str,
|
16
|
-
ruler: str = '─',
|
17
|
-
) -> str:
|
14
|
+
def make_header(message: str, ruler: str = '─', left_pad: int = 2) -> str:
|
18
15
|
"""Format a message string with a ruler.
|
19
16
|
Length of the ruler is the length of the longest word.
|
20
17
|
|
21
18
|
Example:
|
22
|
-
'My\nheader' -> 'My\
|
19
|
+
'My\nheader' -> ' My\n header\n ──────'
|
23
20
|
"""
|
24
21
|
|
25
22
|
from meerschaum.utils.formatting import ANSI, UNICODE, colored
|
@@ -32,10 +29,15 @@ def make_header(
|
|
32
29
|
if length > max_length:
|
33
30
|
max_length = length
|
34
31
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
32
|
+
left_buffer = left_pad * ' '
|
33
|
+
|
34
|
+
return (
|
35
|
+
left_buffer
|
36
|
+
+ message.replace('\n', '\n' + left_buffer)
|
37
|
+
+ "\n"
|
38
|
+
+ left_buffer
|
39
|
+
+ (ruler * max_length)
|
40
|
+
)
|
39
41
|
|
40
42
|
|
41
43
|
_tried_clear_command = None
|
meerschaum/utils/misc.py
CHANGED
@@ -7,6 +7,7 @@ Miscellaneous functions go here
|
|
7
7
|
|
8
8
|
from __future__ import annotations
|
9
9
|
import sys
|
10
|
+
import functools
|
10
11
|
from datetime import timedelta, datetime, timezone
|
11
12
|
from meerschaum.utils.typing import (
|
12
13
|
Union,
|
@@ -46,11 +47,11 @@ __pdoc__: Dict[str, bool] = {
|
|
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
|
@@ -311,7 +399,7 @@ def get_cols_lines(default_cols: int = 100, default_lines: int = 120) -> Tuple[i
|
|
311
399
|
try:
|
312
400
|
size = os.get_terminal_size()
|
313
401
|
_cols, _lines = size.columns, size.lines
|
314
|
-
except Exception
|
402
|
+
except Exception:
|
315
403
|
_cols, _lines = (
|
316
404
|
int(os.environ.get('COLUMNS', str(default_cols))),
|
317
405
|
int(os.environ.get('LINES', str(default_lines))),
|
@@ -367,7 +455,7 @@ def sorted_dict(d: Dict[Any, Any]) -> Dict[Any, Any]:
|
|
367
455
|
"""
|
368
456
|
try:
|
369
457
|
return {key: value for key, value in sorted(d.items(), key=lambda item: item[1])}
|
370
|
-
except Exception
|
458
|
+
except Exception:
|
371
459
|
return d
|
372
460
|
|
373
461
|
def flatten_pipes_dict(pipes_dict: PipesDict) -> List[Pipe]:
|
@@ -391,81 +479,13 @@ def flatten_pipes_dict(pipes_dict: PipesDict) -> List[Pipe]:
|
|
391
479
|
return pipes_list
|
392
480
|
|
393
481
|
|
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
482
|
def timed_input(
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
483
|
+
seconds: int = 10,
|
484
|
+
timeout_message: str = "",
|
485
|
+
prompt: str = "",
|
486
|
+
icon: bool = False,
|
487
|
+
**kw
|
488
|
+
) -> Union[str, None]:
|
469
489
|
"""
|
470
490
|
Accept user input only for a brief period of time.
|
471
491
|
|
@@ -515,52 +535,6 @@ def timed_input(
|
|
515
535
|
signal.alarm(0) # cancel alarm
|
516
536
|
|
517
537
|
|
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
538
|
def enforce_gevent_monkey_patch():
|
565
539
|
"""
|
566
540
|
Check if gevent monkey patching is enabled, and if not, then apply patching.
|
@@ -634,10 +608,10 @@ def string_width(string: str, widest: bool = True) -> int:
|
|
634
608
|
return _widest()
|
635
609
|
|
636
610
|
def _pyinstaller_traverse_dir(
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
611
|
+
directory: str,
|
612
|
+
ignore_patterns: Iterable[str] = ('.pyc', 'dist', 'build', '.git', '.log'),
|
613
|
+
include_dotfiles: bool = False
|
614
|
+
) -> list:
|
641
615
|
"""
|
642
616
|
Recursively traverse a directory and return a list of its contents.
|
643
617
|
"""
|
@@ -677,6 +651,43 @@ def _pyinstaller_traverse_dir(
|
|
677
651
|
return paths
|
678
652
|
|
679
653
|
|
654
|
+
def get_val_from_dict_path(d: Dict[Any, Any], path: Tuple[Any, ...]) -> Any:
|
655
|
+
"""
|
656
|
+
Get a value from a dictionary with a tuple of keys.
|
657
|
+
|
658
|
+
Parameters
|
659
|
+
----------
|
660
|
+
d: Dict[Any, Any]
|
661
|
+
The dictionary to search.
|
662
|
+
|
663
|
+
path: Tuple[Any, ...]
|
664
|
+
The path of keys to traverse.
|
665
|
+
|
666
|
+
Returns
|
667
|
+
-------
|
668
|
+
The value from the end of the path.
|
669
|
+
"""
|
670
|
+
return functools.reduce(lambda di, key: di[key], path, d)
|
671
|
+
|
672
|
+
|
673
|
+
def set_val_in_dict_path(d: Dict[Any, Any], path: Tuple[Any, ...], val: Any) -> None:
|
674
|
+
"""
|
675
|
+
Set a value in a dictionary with a tuple of keys.
|
676
|
+
|
677
|
+
Parameters
|
678
|
+
----------
|
679
|
+
d: Dict[Any, Any]
|
680
|
+
The dictionary to search.
|
681
|
+
|
682
|
+
path: Tuple[Any, ...]
|
683
|
+
The path of keys to traverse.
|
684
|
+
|
685
|
+
val: Any
|
686
|
+
The value to set at the end of the path.
|
687
|
+
"""
|
688
|
+
get_val_from_dict_path(d, path[:-1])[path[-1]] = val
|
689
|
+
|
690
|
+
|
680
691
|
def replace_password(d: Dict[str, Any], replace_with: str = '*') -> Dict[str, Any]:
|
681
692
|
"""
|
682
693
|
Recursively replace passwords in a dictionary.
|
@@ -717,7 +728,7 @@ def replace_password(d: Dict[str, Any], replace_with: str = '*') -> Dict[str, An
|
|
717
728
|
from meerschaum.connectors.sql import SQLConnector
|
718
729
|
try:
|
719
730
|
uri_params = SQLConnector.parse_uri(v)
|
720
|
-
except Exception
|
731
|
+
except Exception:
|
721
732
|
uri_params = None
|
722
733
|
if not uri_params:
|
723
734
|
continue
|
@@ -877,6 +888,7 @@ def dict_from_od(od: collections.OrderedDict) -> Dict[Any, Any]:
|
|
877
888
|
_d[k] = dict_from_od(v)
|
878
889
|
return _d
|
879
890
|
|
891
|
+
|
880
892
|
def remove_ansi(s: str) -> str:
|
881
893
|
"""
|
882
894
|
Remove ANSI escape characters from a string.
|
@@ -1153,7 +1165,7 @@ def items_str(
|
|
1153
1165
|
return output
|
1154
1166
|
|
1155
1167
|
|
1156
|
-
def interval_str(delta: Union[timedelta, int]) -> str:
|
1168
|
+
def interval_str(delta: Union[timedelta, int], round_unit: bool = False) -> str:
|
1157
1169
|
"""
|
1158
1170
|
Return a human-readable string for a `timedelta` (or `int` minutes).
|
1159
1171
|
|
@@ -1162,20 +1174,57 @@ def interval_str(delta: Union[timedelta, int]) -> str:
|
|
1162
1174
|
delta: Union[timedelta, int]
|
1163
1175
|
The interval to print. If `delta` is an integer, assume it corresponds to minutes.
|
1164
1176
|
|
1177
|
+
round_unit: bool, default False
|
1178
|
+
If `True`, round the output to a single unit.
|
1179
|
+
|
1165
1180
|
Returns
|
1166
1181
|
-------
|
1167
1182
|
A formatted string, fit for human eyes.
|
1168
1183
|
"""
|
1169
1184
|
from meerschaum.utils.packages import attempt_import
|
1170
|
-
if is_int(delta):
|
1185
|
+
if is_int(str(delta)) and not round_unit:
|
1171
1186
|
return str(delta)
|
1172
|
-
|
1187
|
+
|
1188
|
+
humanfriendly = attempt_import('humanfriendly', lazy=False)
|
1173
1189
|
delta_seconds = (
|
1174
1190
|
delta.total_seconds()
|
1175
|
-
if
|
1191
|
+
if hasattr(delta, 'total_seconds')
|
1176
1192
|
else (delta * 60)
|
1177
1193
|
)
|
1178
|
-
|
1194
|
+
|
1195
|
+
is_negative = delta_seconds < 0
|
1196
|
+
delta_seconds = abs(delta_seconds)
|
1197
|
+
replace_units = {}
|
1198
|
+
|
1199
|
+
if round_unit:
|
1200
|
+
if delta_seconds < 1:
|
1201
|
+
delta_seconds = round(delta_seconds, 2)
|
1202
|
+
elif delta_seconds < 60:
|
1203
|
+
delta_seconds = int(delta_seconds)
|
1204
|
+
elif delta_seconds < 3600:
|
1205
|
+
delta_seconds = int(delta_seconds / 60) * 60
|
1206
|
+
elif delta_seconds < 86400:
|
1207
|
+
delta_seconds = int(delta_seconds / 3600) * 3600
|
1208
|
+
elif delta_seconds < (86400 * 7):
|
1209
|
+
delta_seconds = int(delta_seconds / 86400) * 86400
|
1210
|
+
elif delta_seconds < (86400 * 7 * 4):
|
1211
|
+
delta_seconds = int(delta_seconds / (86400 * 7)) * (86400 * 7)
|
1212
|
+
elif delta_seconds < (86400 * 7 * 4 * 13):
|
1213
|
+
delta_seconds = int(delta_seconds / (86400 * 7 * 4)) * (86400 * 7)
|
1214
|
+
replace_units['weeks'] = 'months'
|
1215
|
+
else:
|
1216
|
+
delta_seconds = int(delta_seconds / (86400 * 364)) * (86400 * 364)
|
1217
|
+
|
1218
|
+
delta_str = humanfriendly.format_timespan(delta_seconds)
|
1219
|
+
if ',' in delta_str and round_unit:
|
1220
|
+
delta_str = delta_str.split(',')[0]
|
1221
|
+
elif ' and ' in delta_str and round_unit:
|
1222
|
+
delta_str = delta_str.split(' and ')[0]
|
1223
|
+
|
1224
|
+
for parsed_unit, replacement_unit in replace_units.items():
|
1225
|
+
delta_str = delta_str.replace(parsed_unit, replacement_unit)
|
1226
|
+
|
1227
|
+
return delta_str + (' ago' if is_negative else '')
|
1179
1228
|
|
1180
1229
|
|
1181
1230
|
def is_docker_available() -> bool:
|
@@ -1406,7 +1455,7 @@ def separate_negation_values(
|
|
1406
1455
|
If `None`, use the system default (`_`).
|
1407
1456
|
"""
|
1408
1457
|
if negation_prefix is None:
|
1409
|
-
from meerschaum.
|
1458
|
+
from meerschaum._internal.static import STATIC_CONFIG
|
1410
1459
|
negation_prefix = STATIC_CONFIG['system']['fetch_pipes_keys']['negation_prefix']
|
1411
1460
|
_in_vals, _ex_vals = [], []
|
1412
1461
|
for v in vals:
|
@@ -1442,7 +1491,7 @@ def get_in_ex_params(params: Optional[Dict[str, Any]]) -> Dict[str, Tuple[List[A
|
|
1442
1491
|
col: separate_negation_values(
|
1443
1492
|
(
|
1444
1493
|
val
|
1445
|
-
if isinstance(val, (list, tuple))
|
1494
|
+
if isinstance(val, (list, tuple, set)) or hasattr(val, 'astype')
|
1446
1495
|
else [val]
|
1447
1496
|
)
|
1448
1497
|
)
|
@@ -1610,6 +1659,36 @@ def safely_extract_tar(tarf: 'file', output_dir: Union[str, 'pathlib.Path']) ->
|
|
1610
1659
|
return safe_extract(tarf, output_dir)
|
1611
1660
|
|
1612
1661
|
|
1662
|
+
def to_snake_case(name: str) -> str:
|
1663
|
+
"""
|
1664
|
+
Return the given string in snake-case-style.
|
1665
|
+
|
1666
|
+
Parameters
|
1667
|
+
----------
|
1668
|
+
name: str
|
1669
|
+
The input text to convert to snake case.
|
1670
|
+
|
1671
|
+
Returns
|
1672
|
+
-------
|
1673
|
+
A snake-case version of `name`.
|
1674
|
+
|
1675
|
+
Examples
|
1676
|
+
--------
|
1677
|
+
>>> to_snake_case("HelloWorld!")
|
1678
|
+
'hello_world'
|
1679
|
+
>>> to_snake_case("This has spaces in it.")
|
1680
|
+
'this_has_spaces_in_it'
|
1681
|
+
>>> to_snake_case("already_in_snake_case")
|
1682
|
+
'already_in_snake_case'
|
1683
|
+
"""
|
1684
|
+
import re
|
1685
|
+
name = re.sub(r'(.)([A-Z][a-z]+)', r'\1_\2', name)
|
1686
|
+
name = re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', name)
|
1687
|
+
name = re.sub(r'[^\w\s]', '', name)
|
1688
|
+
name = re.sub(r'\s+', '_', name)
|
1689
|
+
return name.lower()
|
1690
|
+
|
1691
|
+
|
1613
1692
|
##################
|
1614
1693
|
# Legacy imports #
|
1615
1694
|
##################
|
@@ -1756,6 +1835,24 @@ def json_serialize_datetime(dt: datetime) -> Union[str, None]:
|
|
1756
1835
|
return serialize_datetime(dt)
|
1757
1836
|
|
1758
1837
|
|
1838
|
+
def replace_pipes_in_dict(*args, **kwargs):
|
1839
|
+
"""
|
1840
|
+
Placeholder function to prevent breaking legacy behavior.
|
1841
|
+
See `meerschaum.utils.pipes.replace_pipes_in_dict`.
|
1842
|
+
"""
|
1843
|
+
from meerschaum.utils.pipes import replace_pipes_in_dict
|
1844
|
+
return replace_pipes_in_dict(*args, **kwargs)
|
1845
|
+
|
1846
|
+
|
1847
|
+
def round_time(*args, **kwargs):
|
1848
|
+
"""
|
1849
|
+
Placeholder function to prevent breaking legacy behavior.
|
1850
|
+
See `meerschaum.utils.dtypes.round_time`.
|
1851
|
+
"""
|
1852
|
+
from meerschaum.utils.dtypes import round_time
|
1853
|
+
return round_time(*args, **kwargs)
|
1854
|
+
|
1855
|
+
|
1759
1856
|
_current_module = sys.modules[__name__]
|
1760
1857
|
__all__ = tuple(
|
1761
1858
|
name
|
@@ -1763,4 +1860,5 @@ __all__ = tuple(
|
|
1763
1860
|
if callable(obj)
|
1764
1861
|
and name not in __pdoc__
|
1765
1862
|
and getattr(obj, '__module__', None) == _current_module.__name__
|
1863
|
+
and not name.startswith('_')
|
1766
1864
|
)
|