meerschaum 2.9.5__py3-none-any.whl → 3.0.0rc1__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 +17 -1
- meerschaum/_internal/entry.py +6 -6
- meerschaum/_internal/shell/Shell.py +1 -1
- meerschaum/_internal/static.py +372 -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/api/__init__.py +9 -2
- meerschaum/api/_events.py +39 -2
- meerschaum/api/_oauth2.py +118 -8
- meerschaum/api/_tokens.py +102 -0
- meerschaum/api/dash/__init__.py +0 -1
- meerschaum/api/dash/callbacks/custom.py +2 -2
- meerschaum/api/dash/callbacks/dashboard.py +102 -18
- 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 +134 -111
- 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 +32 -5
- 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 +24 -5
- 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 +15 -14
- 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 +6 -154
- meerschaum/connectors/sql/_fetch.py +2 -18
- meerschaum/connectors/sql/_pipes.py +42 -31
- meerschaum/connectors/sql/_plugins.py +29 -0
- meerschaum/connectors/sql/_sql.py +8 -1
- 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 +9 -10
- meerschaum/connectors/valkey/_plugins.py +2 -26
- meerschaum/core/Pipe/__init__.py +31 -14
- meerschaum/core/Pipe/_attributes.py +156 -58
- meerschaum/core/Pipe/_bootstrap.py +54 -24
- meerschaum/core/Pipe/_data.py +41 -1
- meerschaum/core/Pipe/_dtypes.py +29 -14
- meerschaum/core/Pipe/_edit.py +12 -4
- meerschaum/core/Pipe/_show.py +5 -5
- meerschaum/core/Pipe/_sync.py +48 -53
- meerschaum/core/Pipe/_verify.py +1 -1
- meerschaum/{plugins → core/Plugin}/_Plugin.py +9 -11
- meerschaum/core/Plugin/__init__.py +1 -1
- meerschaum/core/Token/_Token.py +221 -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 +95 -14
- meerschaum/utils/dtypes/__init__.py +91 -18
- meerschaum/utils/dtypes/sql.py +44 -0
- 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 +237 -80
- meerschaum/utils/packages/__init__.py +3 -6
- meerschaum/utils/packages/_packages.py +34 -32
- meerschaum/utils/pipes.py +181 -0
- meerschaum/utils/process.py +1 -1
- meerschaum/utils/prompt.py +3 -1
- meerschaum/utils/schedule.py +1 -0
- meerschaum/utils/sql.py +114 -37
- 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.0rc1.dist-info}/METADATA +88 -80
- meerschaum-3.0.0rc1.dist-info/RECORD +282 -0
- {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc1.dist-info}/WHEEL +1 -1
- 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.0rc1.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc1.dist-info}/licenses/LICENSE +0 -0
- {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc1.dist-info}/top_level.txt +0 -0
- {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc1.dist-info}/zip-safe +0 -0
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]:
|
@@ -460,12 +548,12 @@ def round_time(
|
|
460
548
|
|
461
549
|
|
462
550
|
def timed_input(
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
551
|
+
seconds: int = 10,
|
552
|
+
timeout_message: str = "",
|
553
|
+
prompt: str = "",
|
554
|
+
icon: bool = False,
|
555
|
+
**kw
|
556
|
+
) -> Union[str, None]:
|
469
557
|
"""
|
470
558
|
Accept user input only for a brief period of time.
|
471
559
|
|
@@ -515,52 +603,6 @@ def timed_input(
|
|
515
603
|
signal.alarm(0) # cancel alarm
|
516
604
|
|
517
605
|
|
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
606
|
def enforce_gevent_monkey_patch():
|
565
607
|
"""
|
566
608
|
Check if gevent monkey patching is enabled, and if not, then apply patching.
|
@@ -634,10 +676,10 @@ def string_width(string: str, widest: bool = True) -> int:
|
|
634
676
|
return _widest()
|
635
677
|
|
636
678
|
def _pyinstaller_traverse_dir(
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
679
|
+
directory: str,
|
680
|
+
ignore_patterns: Iterable[str] = ('.pyc', 'dist', 'build', '.git', '.log'),
|
681
|
+
include_dotfiles: bool = False
|
682
|
+
) -> list:
|
641
683
|
"""
|
642
684
|
Recursively traverse a directory and return a list of its contents.
|
643
685
|
"""
|
@@ -677,6 +719,43 @@ def _pyinstaller_traverse_dir(
|
|
677
719
|
return paths
|
678
720
|
|
679
721
|
|
722
|
+
def get_val_from_dict_path(d: Dict[Any, Any], path: Tuple[Any, ...]) -> Any:
|
723
|
+
"""
|
724
|
+
Get a value from a dictionary with a tuple of keys.
|
725
|
+
|
726
|
+
Parameters
|
727
|
+
----------
|
728
|
+
d: Dict[Any, Any]
|
729
|
+
The dictionary to search.
|
730
|
+
|
731
|
+
path: Tuple[Any, ...]
|
732
|
+
The path of keys to traverse.
|
733
|
+
|
734
|
+
Returns
|
735
|
+
-------
|
736
|
+
The value from the end of the path.
|
737
|
+
"""
|
738
|
+
return functools.reduce(lambda di, key: di[key], path, d)
|
739
|
+
|
740
|
+
|
741
|
+
def set_val_in_dict_path(d: Dict[Any, Any], path: Tuple[Any, ...], val: Any) -> None:
|
742
|
+
"""
|
743
|
+
Set a value in a dictionary with a tuple of keys.
|
744
|
+
|
745
|
+
Parameters
|
746
|
+
----------
|
747
|
+
d: Dict[Any, Any]
|
748
|
+
The dictionary to search.
|
749
|
+
|
750
|
+
path: Tuple[Any, ...]
|
751
|
+
The path of keys to traverse.
|
752
|
+
|
753
|
+
val: Any
|
754
|
+
The value to set at the end of the path.
|
755
|
+
"""
|
756
|
+
get_val_from_dict_path(d, path[:-1])[path[-1]] = val
|
757
|
+
|
758
|
+
|
680
759
|
def replace_password(d: Dict[str, Any], replace_with: str = '*') -> Dict[str, Any]:
|
681
760
|
"""
|
682
761
|
Recursively replace passwords in a dictionary.
|
@@ -717,7 +796,7 @@ def replace_password(d: Dict[str, Any], replace_with: str = '*') -> Dict[str, An
|
|
717
796
|
from meerschaum.connectors.sql import SQLConnector
|
718
797
|
try:
|
719
798
|
uri_params = SQLConnector.parse_uri(v)
|
720
|
-
except Exception
|
799
|
+
except Exception:
|
721
800
|
uri_params = None
|
722
801
|
if not uri_params:
|
723
802
|
continue
|
@@ -877,6 +956,7 @@ def dict_from_od(od: collections.OrderedDict) -> Dict[Any, Any]:
|
|
877
956
|
_d[k] = dict_from_od(v)
|
878
957
|
return _d
|
879
958
|
|
959
|
+
|
880
960
|
def remove_ansi(s: str) -> str:
|
881
961
|
"""
|
882
962
|
Remove ANSI escape characters from a string.
|
@@ -1153,7 +1233,7 @@ def items_str(
|
|
1153
1233
|
return output
|
1154
1234
|
|
1155
1235
|
|
1156
|
-
def interval_str(delta: Union[timedelta, int]) -> str:
|
1236
|
+
def interval_str(delta: Union[timedelta, int], round_unit: bool = False) -> str:
|
1157
1237
|
"""
|
1158
1238
|
Return a human-readable string for a `timedelta` (or `int` minutes).
|
1159
1239
|
|
@@ -1162,20 +1242,57 @@ def interval_str(delta: Union[timedelta, int]) -> str:
|
|
1162
1242
|
delta: Union[timedelta, int]
|
1163
1243
|
The interval to print. If `delta` is an integer, assume it corresponds to minutes.
|
1164
1244
|
|
1245
|
+
round_unit: bool, default False
|
1246
|
+
If `True`, round the output to a single unit.
|
1247
|
+
|
1165
1248
|
Returns
|
1166
1249
|
-------
|
1167
1250
|
A formatted string, fit for human eyes.
|
1168
1251
|
"""
|
1169
1252
|
from meerschaum.utils.packages import attempt_import
|
1170
|
-
if is_int(delta):
|
1253
|
+
if is_int(str(delta)) and not round_unit:
|
1171
1254
|
return str(delta)
|
1172
|
-
|
1255
|
+
|
1256
|
+
humanfriendly = attempt_import('humanfriendly', lazy=False)
|
1173
1257
|
delta_seconds = (
|
1174
1258
|
delta.total_seconds()
|
1175
|
-
if
|
1259
|
+
if hasattr(delta, 'total_seconds')
|
1176
1260
|
else (delta * 60)
|
1177
1261
|
)
|
1178
|
-
|
1262
|
+
|
1263
|
+
is_negative = delta_seconds < 0
|
1264
|
+
delta_seconds = abs(delta_seconds)
|
1265
|
+
replace_units = {}
|
1266
|
+
|
1267
|
+
if round_unit:
|
1268
|
+
if delta_seconds < 1:
|
1269
|
+
delta_seconds = round(delta_seconds, 2)
|
1270
|
+
elif delta_seconds < 60:
|
1271
|
+
delta_seconds = int(delta_seconds)
|
1272
|
+
elif delta_seconds < 3600:
|
1273
|
+
delta_seconds = int(delta_seconds / 60) * 60
|
1274
|
+
elif delta_seconds < 86400:
|
1275
|
+
delta_seconds = int(delta_seconds / 3600) * 3600
|
1276
|
+
elif delta_seconds < (86400 * 7):
|
1277
|
+
delta_seconds = int(delta_seconds / 86400) * 86400
|
1278
|
+
elif delta_seconds < (86400 * 7 * 4):
|
1279
|
+
delta_seconds = int(delta_seconds / (86400 * 7)) * (86400 * 7)
|
1280
|
+
elif delta_seconds < (86400 * 7 * 4 * 13):
|
1281
|
+
delta_seconds = int(delta_seconds / (86400 * 7 * 4)) * (86400 * 7)
|
1282
|
+
replace_units['weeks'] = 'months'
|
1283
|
+
else:
|
1284
|
+
delta_seconds = int(delta_seconds / (86400 * 364)) * (86400 * 364)
|
1285
|
+
|
1286
|
+
delta_str = humanfriendly.format_timespan(delta_seconds)
|
1287
|
+
if ',' in delta_str and round_unit:
|
1288
|
+
delta_str = delta_str.split(',')[0]
|
1289
|
+
elif ' and ' in delta_str and round_unit:
|
1290
|
+
delta_str = delta_str.split(' and ')[0]
|
1291
|
+
|
1292
|
+
for parsed_unit, replacement_unit in replace_units.items():
|
1293
|
+
delta_str = delta_str.replace(parsed_unit, replacement_unit)
|
1294
|
+
|
1295
|
+
return delta_str + (' ago' if is_negative else '')
|
1179
1296
|
|
1180
1297
|
|
1181
1298
|
def is_docker_available() -> bool:
|
@@ -1406,7 +1523,7 @@ def separate_negation_values(
|
|
1406
1523
|
If `None`, use the system default (`_`).
|
1407
1524
|
"""
|
1408
1525
|
if negation_prefix is None:
|
1409
|
-
from meerschaum.
|
1526
|
+
from meerschaum._internal.static import STATIC_CONFIG
|
1410
1527
|
negation_prefix = STATIC_CONFIG['system']['fetch_pipes_keys']['negation_prefix']
|
1411
1528
|
_in_vals, _ex_vals = [], []
|
1412
1529
|
for v in vals:
|
@@ -1442,7 +1559,7 @@ def get_in_ex_params(params: Optional[Dict[str, Any]]) -> Dict[str, Tuple[List[A
|
|
1442
1559
|
col: separate_negation_values(
|
1443
1560
|
(
|
1444
1561
|
val
|
1445
|
-
if isinstance(val, (list, tuple))
|
1562
|
+
if isinstance(val, (list, tuple, set)) or hasattr(val, 'astype')
|
1446
1563
|
else [val]
|
1447
1564
|
)
|
1448
1565
|
)
|
@@ -1610,6 +1727,36 @@ def safely_extract_tar(tarf: 'file', output_dir: Union[str, 'pathlib.Path']) ->
|
|
1610
1727
|
return safe_extract(tarf, output_dir)
|
1611
1728
|
|
1612
1729
|
|
1730
|
+
def to_snake_case(name: str) -> str:
|
1731
|
+
"""
|
1732
|
+
Return the given string in snake-case-style.
|
1733
|
+
|
1734
|
+
Parameters
|
1735
|
+
----------
|
1736
|
+
name: str
|
1737
|
+
The input text to convert to snake case.
|
1738
|
+
|
1739
|
+
Returns
|
1740
|
+
-------
|
1741
|
+
A snake-case version of `name`.
|
1742
|
+
|
1743
|
+
Examples
|
1744
|
+
--------
|
1745
|
+
>>> to_snake_case("HelloWorld!")
|
1746
|
+
'hello_world'
|
1747
|
+
>>> to_snake_case("This has spaces in it.")
|
1748
|
+
'this_has_spaces_in_it'
|
1749
|
+
>>> to_snake_case("already_in_snake_case")
|
1750
|
+
'already_in_snake_case'
|
1751
|
+
"""
|
1752
|
+
import re
|
1753
|
+
name = re.sub(r'(.)([A-Z][a-z]+)', r'\1_\2', name)
|
1754
|
+
name = re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', name)
|
1755
|
+
name = re.sub(r'[^\w\s]', '', name)
|
1756
|
+
name = re.sub(r'\s+', '_', name)
|
1757
|
+
return name.lower()
|
1758
|
+
|
1759
|
+
|
1613
1760
|
##################
|
1614
1761
|
# Legacy imports #
|
1615
1762
|
##################
|
@@ -1756,6 +1903,15 @@ def json_serialize_datetime(dt: datetime) -> Union[str, None]:
|
|
1756
1903
|
return serialize_datetime(dt)
|
1757
1904
|
|
1758
1905
|
|
1906
|
+
def replace_pipes_in_dict(*args, **kwargs):
|
1907
|
+
"""
|
1908
|
+
Placeholder function to prevent breaking legacy behavior.
|
1909
|
+
See `meerschaum.utils.pipes.replace_pipes_in_dict`.
|
1910
|
+
"""
|
1911
|
+
from meerschaum.utils.pipes import replace_pipes_in_dict
|
1912
|
+
return replace_pipes_in_dict(*args, **kwargs)
|
1913
|
+
|
1914
|
+
|
1759
1915
|
_current_module = sys.modules[__name__]
|
1760
1916
|
__all__ = tuple(
|
1761
1917
|
name
|
@@ -1763,4 +1919,5 @@ __all__ = tuple(
|
|
1763
1919
|
if callable(obj)
|
1764
1920
|
and name not in __pdoc__
|
1765
1921
|
and getattr(obj, '__module__', None) == _current_module.__name__
|
1922
|
+
and not name.startswith('_')
|
1766
1923
|
)
|
@@ -755,7 +755,7 @@ def get_pip(
|
|
755
755
|
import subprocess
|
756
756
|
from meerschaum.utils.misc import wget
|
757
757
|
from meerschaum.config._paths import CACHE_RESOURCES_PATH
|
758
|
-
from meerschaum.
|
758
|
+
from meerschaum._internal.static import STATIC_CONFIG
|
759
759
|
url = STATIC_CONFIG['system']['urls']['get-pip.py']
|
760
760
|
dest = CACHE_RESOURCES_PATH / 'get-pip.py'
|
761
761
|
try:
|
@@ -837,7 +837,7 @@ def pip_install(
|
|
837
837
|
|
838
838
|
"""
|
839
839
|
from meerschaum.config._paths import VIRTENV_RESOURCES_PATH
|
840
|
-
from meerschaum.
|
840
|
+
from meerschaum._internal.static import STATIC_CONFIG
|
841
841
|
from meerschaum.utils.warnings import warn
|
842
842
|
if args is None:
|
843
843
|
args = ['--upgrade'] if not _uninstall else []
|
@@ -1500,15 +1500,12 @@ def import_pandas(
|
|
1500
1500
|
def import_rich(
|
1501
1501
|
lazy: bool = True,
|
1502
1502
|
debug: bool = False,
|
1503
|
-
**kw
|
1503
|
+
**kw: Any
|
1504
1504
|
) -> 'ModuleType':
|
1505
1505
|
"""
|
1506
1506
|
Quality of life function for importing `rich`.
|
1507
1507
|
"""
|
1508
1508
|
from meerschaum.utils.formatting import ANSI, UNICODE
|
1509
|
-
if not ANSI and not UNICODE:
|
1510
|
-
return None
|
1511
|
-
|
1512
1509
|
## need typing_extensions for `from rich import box`
|
1513
1510
|
typing_extensions = attempt_import(
|
1514
1511
|
'typing_extensions', lazy=False, debug=debug
|
@@ -54,23 +54,25 @@ packages: Dict[str, Dict[str, str]] = {
|
|
54
54
|
'virtualenv' : 'virtualenv>=20.1.0',
|
55
55
|
'attrs' : 'attrs>=24.2.0',
|
56
56
|
'uv' : 'uv>=0.2.11',
|
57
|
+
'pydantic' : 'pydantic>=2.11.7',
|
58
|
+
'annotated-types' : 'annotated-types>=0.7.0',
|
57
59
|
},
|
58
60
|
'_internal' : {
|
59
61
|
'apscheduler' : (
|
60
62
|
f"{_MRSM_PACKAGE_ARCHIVES_PREFIX}"
|
61
|
-
"
|
63
|
+
"apscheduler-4.0.0a6.post8+mrsm-py3-none-any.whl>=4.0.0a6"
|
62
64
|
),
|
63
|
-
'dataclass_wizard' : 'dataclass-wizard>=0.
|
65
|
+
'dataclass_wizard' : 'dataclass-wizard>=0.35.0',
|
64
66
|
},
|
65
67
|
'jobs': {
|
66
|
-
'dill' : 'dill>=0.
|
67
|
-
'daemon' : 'python-daemon>=
|
68
|
-
'watchfiles' : 'watchfiles>=
|
69
|
-
'psutil' : 'psutil>=
|
68
|
+
'dill' : 'dill>=0.4.0',
|
69
|
+
'daemon' : 'python-daemon>=3.1.2',
|
70
|
+
'watchfiles' : 'watchfiles>=1.1.0',
|
71
|
+
'psutil' : 'psutil>=7.0.0',
|
70
72
|
},
|
71
73
|
'drivers': {
|
72
74
|
'cryptography' : 'cryptography>=38.0.1',
|
73
|
-
'psycopg' : 'psycopg[binary]>=3.2.
|
75
|
+
'psycopg' : 'psycopg[binary]>=3.2.9',
|
74
76
|
'pymysql' : 'PyMySQL>=0.9.0',
|
75
77
|
'aiomysql' : 'aiomysql>=0.0.21',
|
76
78
|
'sqlalchemy_cockroachdb' : 'sqlalchemy-cockroachdb>=2.0.0',
|
@@ -122,7 +124,7 @@ packages: Dict[str, Dict[str, str]] = {
|
|
122
124
|
'mkdocs_section_index' : 'mkdocs-section-index>=0.3.3',
|
123
125
|
'mkdocs_linkcheck' : 'mkdocs-linkcheck>=1.0.6',
|
124
126
|
'mkdocs_redirects' : 'mkdocs-redirects>=1.0.4',
|
125
|
-
'jinja2' : 'jinja2
|
127
|
+
'jinja2' : 'jinja2>=3.1.6',
|
126
128
|
},
|
127
129
|
'gui': {
|
128
130
|
'webview' : 'pywebview>=3.6.3',
|
@@ -137,43 +139,43 @@ packages: Dict[str, Dict[str, str]] = {
|
|
137
139
|
},
|
138
140
|
}
|
139
141
|
packages['sql'] = {
|
140
|
-
'numpy' : 'numpy>=
|
141
|
-
'pandas' : 'pandas[parquet]>=2.
|
142
|
-
'pyarrow' : 'pyarrow>=
|
142
|
+
'numpy' : 'numpy>=2.3.1',
|
143
|
+
'pandas' : 'pandas[parquet]>=2.3.1',
|
144
|
+
'pyarrow' : 'pyarrow>=20.0.0',
|
143
145
|
'dask' : 'dask[complete]>=2024.12.1',
|
144
146
|
'partd' : 'partd>=1.4.2',
|
145
147
|
'pytz' : 'pytz',
|
146
|
-
'joblib' : 'joblib>=
|
147
|
-
'sqlalchemy' : 'SQLAlchemy>=2.0.
|
148
|
+
'joblib' : 'joblib>=1.5.1',
|
149
|
+
'sqlalchemy' : 'SQLAlchemy>=2.0.41',
|
148
150
|
'geoalchemy' : 'GeoAlchemy2>=0.17.1',
|
149
|
-
'databases' : 'databases>=0.
|
150
|
-
'aiosqlite' : 'aiosqlite>=0.
|
151
|
-
'asyncpg' : 'asyncpg>=0.
|
151
|
+
'databases' : 'databases>=0.9.0',
|
152
|
+
'aiosqlite' : 'aiosqlite>=0.21.0',
|
153
|
+
'asyncpg' : 'asyncpg>=0.30.0',
|
152
154
|
}
|
153
155
|
packages['sql'].update(packages['drivers'])
|
154
156
|
packages['sql'].update(packages['core'])
|
155
157
|
packages['sql'].update(packages['gis'])
|
156
158
|
packages['dash'] = {
|
157
|
-
'flask_compress' : 'Flask-Compress>=1.
|
158
|
-
'dash' : 'dash>=
|
159
|
+
'flask_compress' : 'Flask-Compress>=1.17.0',
|
160
|
+
'dash' : 'dash>=3.1.1',
|
159
161
|
'dash_bootstrap_components' : 'dash-bootstrap-components>=1.7.1',
|
160
162
|
'dash_ace' : 'dash-ace>=0.2.1',
|
161
|
-
'dash_extensions' : 'dash-extensions>=
|
162
|
-
'dash_daq' : 'dash-daq>=0.
|
163
|
-
'terminado' : 'terminado>=0.
|
164
|
-
'tornado' : 'tornado>=6.1
|
163
|
+
'dash_extensions' : 'dash-extensions>=2.0.4',
|
164
|
+
'dash_daq' : 'dash-daq>=0.6.0',
|
165
|
+
'terminado' : 'terminado>=0.18.1',
|
166
|
+
'tornado' : 'tornado>=6.5.1',
|
165
167
|
}
|
166
168
|
packages['api'] = {
|
167
|
-
'uvicorn' : 'uvicorn[standard]>=0.
|
168
|
-
'gunicorn' : 'gunicorn>=
|
169
|
-
'dotenv' : 'python-dotenv>=
|
170
|
-
'websockets' : 'websockets>=
|
171
|
-
'fastapi' : 'fastapi>=0.
|
172
|
-
'fastapi_login' : 'fastapi-login>=1.
|
173
|
-
'multipart' : 'python-multipart>=0.0.
|
174
|
-
'httpx' : 'httpx>=0.
|
175
|
-
'httpcore' : 'httpcore>=1.0.
|
176
|
-
'valkey' : 'valkey>=6.
|
169
|
+
'uvicorn' : 'uvicorn[standard]>=0.35.0',
|
170
|
+
'gunicorn' : 'gunicorn>=23.0.0',
|
171
|
+
'dotenv' : 'python-dotenv>=1.1.1',
|
172
|
+
'websockets' : 'websockets>=15.0.1',
|
173
|
+
'fastapi' : 'fastapi>=0.116.0',
|
174
|
+
'fastapi_login' : 'fastapi-login>=1.10.3',
|
175
|
+
'multipart' : 'python-multipart>=0.0.20',
|
176
|
+
'httpx' : 'httpx>=0.28.1',
|
177
|
+
'httpcore' : 'httpcore>=1.0.9',
|
178
|
+
'valkey' : 'valkey>=6.1.0',
|
177
179
|
}
|
178
180
|
packages['api'].update(packages['sql'])
|
179
181
|
packages['api'].update(packages['formatting'])
|