meerschaum 2.4.7__py3-none-any.whl → 2.4.9__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/_internal/arguments/_parser.py +36 -3
- meerschaum/actions/show.py +15 -4
- meerschaum/actions/sql.py +1 -1
- meerschaum/api/routes/_pipes.py +38 -38
- meerschaum/config/_version.py +1 -1
- meerschaum/connectors/api/_pipes.py +3 -3
- meerschaum/connectors/sql/_SQLConnector.py +1 -1
- meerschaum/connectors/sql/_instance.py +12 -12
- meerschaum/connectors/sql/_pipes.py +75 -52
- meerschaum/connectors/sql/_sql.py +3 -1
- meerschaum/core/Pipe/_data.py +12 -13
- meerschaum/core/Pipe/_sync.py +1 -1
- meerschaum/utils/dataframe.py +34 -23
- meerschaum/utils/dtypes/sql.py +32 -18
- meerschaum/utils/formatting/_pipes.py +4 -4
- meerschaum/utils/misc.py +4 -4
- meerschaum/utils/packages/_packages.py +2 -1
- meerschaum/utils/sql.py +11 -7
- {meerschaum-2.4.7.dist-info → meerschaum-2.4.9.dist-info}/METADATA +7 -4
- {meerschaum-2.4.7.dist-info → meerschaum-2.4.9.dist-info}/RECORD +26 -26
- {meerschaum-2.4.7.dist-info → meerschaum-2.4.9.dist-info}/LICENSE +0 -0
- {meerschaum-2.4.7.dist-info → meerschaum-2.4.9.dist-info}/NOTICE +0 -0
- {meerschaum-2.4.7.dist-info → meerschaum-2.4.9.dist-info}/WHEEL +0 -0
- {meerschaum-2.4.7.dist-info → meerschaum-2.4.9.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.4.7.dist-info → meerschaum-2.4.9.dist-info}/top_level.txt +0 -0
- {meerschaum-2.4.7.dist-info → meerschaum-2.4.9.dist-info}/zip-safe +0 -0
@@ -10,6 +10,7 @@ from __future__ import annotations
|
|
10
10
|
import sys
|
11
11
|
import argparse
|
12
12
|
import json
|
13
|
+
import re
|
13
14
|
from datetime import datetime, timedelta, timezone
|
14
15
|
from meerschaum.utils.typing import Union, Dict, List, Any, Tuple, Callable
|
15
16
|
from meerschaum.utils.misc import string_to_dict
|
@@ -39,7 +40,7 @@ class ArgumentParser(argparse.ArgumentParser):
|
|
39
40
|
|
40
41
|
def parse_datetime(dt_str: str) -> Union[datetime, int, str]:
|
41
42
|
"""Parse a string into a datetime."""
|
42
|
-
from meerschaum.utils.misc import is_int
|
43
|
+
from meerschaum.utils.misc import is_int, round_time
|
43
44
|
if is_int(dt_str):
|
44
45
|
return int(dt_str)
|
45
46
|
|
@@ -47,11 +48,43 @@ def parse_datetime(dt_str: str) -> Union[datetime, int, str]:
|
|
47
48
|
return 'None'
|
48
49
|
|
49
50
|
from meerschaum.utils.packages import attempt_import
|
50
|
-
dateutil_parser = attempt_import(
|
51
|
+
dateutil_parser, dateutil_relativedelta = attempt_import(
|
52
|
+
'dateutil.parser', 'dateutil.relativedelta'
|
53
|
+
)
|
54
|
+
relativedelta = dateutil_relativedelta.relativedelta
|
55
|
+
|
56
|
+
ago_pattern = r'(\d+)\s+(days?|minutes?|seconds?|hours?|weeks?|months?|years?)\s+ago'
|
57
|
+
round_pattern = r'(\d+)\s+(days?|minutes?|seconds?|hours?|weeks?|years?)'
|
58
|
+
now = datetime.now(timezone.utc).replace(tzinfo=None)
|
59
|
+
ago_matches = re.findall(ago_pattern, dt_str.lower())
|
51
60
|
|
52
61
|
try:
|
53
62
|
if dt_str.lower() == 'now':
|
54
|
-
dt =
|
63
|
+
dt = now
|
64
|
+
elif ago_matches:
|
65
|
+
val_str, unit_str = ago_matches[0]
|
66
|
+
if not unit_str.endswith('s'):
|
67
|
+
unit_str += 's'
|
68
|
+
val = int(val_str) if is_int(val_str) else float(val_str)
|
69
|
+
ago_delta = relativedelta(**{unit_str: val})
|
70
|
+
round_part = dt_str.lower().split('ago ')[-1]
|
71
|
+
round_delta = None
|
72
|
+
if round_part:
|
73
|
+
round_matches = re.findall(round_pattern, round_part)
|
74
|
+
if round_matches:
|
75
|
+
round_val_str, round_unit_str = round_matches[0]
|
76
|
+
if not round_unit_str.endswith('s'):
|
77
|
+
round_unit_str += 's'
|
78
|
+
round_val = (
|
79
|
+
int(round_val_str)
|
80
|
+
if is_int(round_val_str)
|
81
|
+
else float(round_val_str)
|
82
|
+
)
|
83
|
+
round_delta = timedelta(**{round_unit_str: round_val})
|
84
|
+
|
85
|
+
dt = now - ago_delta
|
86
|
+
if round_delta is not None:
|
87
|
+
dt = round_time(dt, round_delta)
|
55
88
|
else:
|
56
89
|
dt = dateutil_parser.parse(dt_str)
|
57
90
|
except Exception as e:
|
meerschaum/actions/show.py
CHANGED
@@ -7,6 +7,8 @@ This module contains functions for printing elements.
|
|
7
7
|
"""
|
8
8
|
|
9
9
|
from __future__ import annotations
|
10
|
+
|
11
|
+
from datetime import datetime
|
10
12
|
import meerschaum as mrsm
|
11
13
|
from meerschaum.utils.typing import SuccessTuple, Union, Sequence, Any, Optional, List, Dict, Tuple
|
12
14
|
|
@@ -274,8 +276,8 @@ def _show_arguments(
|
|
274
276
|
def _show_data(
|
275
277
|
action: Optional[List[str]] = None,
|
276
278
|
gui: bool = False,
|
277
|
-
begin:
|
278
|
-
end:
|
279
|
+
begin: Union[datetime, int, None] = None,
|
280
|
+
end: Union[datetime, int, None] = None,
|
279
281
|
params: Optional[Dict[str, Any]] = None,
|
280
282
|
chunksize: Optional[int] = -1,
|
281
283
|
nopretty: bool = False,
|
@@ -401,12 +403,15 @@ def _show_columns(
|
|
401
403
|
def _show_rowcounts(
|
402
404
|
action: Optional[List[str]] = None,
|
403
405
|
workers: Optional[int] = None,
|
406
|
+
begin: Union[datetime, int, None] = None,
|
407
|
+
end: Union[datetime, int, None] = None,
|
408
|
+
params: Optional[Dict[str, Any]] = None,
|
404
409
|
debug: bool = False,
|
405
410
|
**kw: Any
|
406
411
|
) -> SuccessTuple:
|
407
412
|
"""
|
408
413
|
Show the rowcounts for pipes.
|
409
|
-
|
414
|
+
|
410
415
|
To see remote rowcounts (execute `COUNT(*)` on the source server),
|
411
416
|
execute `show rowcounts remote`.
|
412
417
|
"""
|
@@ -421,7 +426,13 @@ def _show_rowcounts(
|
|
421
426
|
pipes = get_pipes(as_list=True, debug=debug, **kw)
|
422
427
|
pool = get_pool(workers=workers)
|
423
428
|
def _get_rc(_pipe):
|
424
|
-
return _pipe.get_rowcount(
|
429
|
+
return _pipe.get_rowcount(
|
430
|
+
begin=begin,
|
431
|
+
end=end,
|
432
|
+
params=params,
|
433
|
+
remote=remote,
|
434
|
+
debug=debug
|
435
|
+
)
|
425
436
|
|
426
437
|
rowcounts = pool.map(_get_rc, pipes) if pool is not None else [_get_rc(p) for p in pipes]
|
427
438
|
|
meerschaum/actions/sql.py
CHANGED
meerschaum/api/routes/_pipes.py
CHANGED
@@ -408,8 +408,8 @@ def get_pipe_data(
|
|
408
408
|
_params = None
|
409
409
|
if not isinstance(_params, dict):
|
410
410
|
raise fastapi.HTTPException(
|
411
|
-
status_code
|
412
|
-
detail
|
411
|
+
status_code=409,
|
412
|
+
detail="Params must be a valid JSON-encoded dictionary.",
|
413
413
|
)
|
414
414
|
|
415
415
|
_select_columns = []
|
@@ -422,8 +422,8 @@ def get_pipe_data(
|
|
422
422
|
_select_columns = None
|
423
423
|
if not isinstance(_select_columns, list):
|
424
424
|
raise fastapi.HTTPException(
|
425
|
-
status_code
|
426
|
-
detail
|
425
|
+
status_code=409,
|
426
|
+
detail="Selected columns must be a JSON-encoded list."
|
427
427
|
)
|
428
428
|
|
429
429
|
_omit_columns = []
|
@@ -436,35 +436,35 @@ def get_pipe_data(
|
|
436
436
|
_omit_columns = None
|
437
437
|
if _omit_columns is None:
|
438
438
|
raise fastapi.HTTPException(
|
439
|
-
status_code
|
440
|
-
detail
|
439
|
+
status_code=409,
|
440
|
+
detail="Omitted columns must be a JSON-encoded list.",
|
441
441
|
)
|
442
442
|
|
443
443
|
pipe = get_pipe(connector_keys, metric_key, location_key)
|
444
444
|
if not is_pipe_registered(pipe, pipes(refresh=True)):
|
445
445
|
raise fastapi.HTTPException(
|
446
|
-
status_code
|
447
|
-
detail
|
446
|
+
status_code=409,
|
447
|
+
detail="Pipe must be registered with the datetime column specified."
|
448
448
|
)
|
449
449
|
|
450
450
|
if pipe.target in ('users', 'plugins', 'pipes'):
|
451
451
|
raise fastapi.HTTPException(
|
452
|
-
status_code
|
453
|
-
detail
|
452
|
+
status_code=409,
|
453
|
+
detail=f"Cannot retrieve data from protected table '{pipe.target}'.",
|
454
454
|
)
|
455
455
|
|
456
456
|
df = pipe.get_data(
|
457
|
-
select_columns
|
458
|
-
omit_columns
|
459
|
-
begin
|
460
|
-
end
|
461
|
-
params
|
462
|
-
debug
|
457
|
+
select_columns=_select_columns,
|
458
|
+
omit_columns=_omit_columns,
|
459
|
+
begin=begin,
|
460
|
+
end=end,
|
461
|
+
params=_params,
|
462
|
+
debug=debug,
|
463
463
|
)
|
464
464
|
if df is None:
|
465
465
|
raise fastapi.HTTPException(
|
466
|
-
status_code
|
467
|
-
detail
|
466
|
+
status_code=400,
|
467
|
+
detail="Could not fetch data with the given parameters.",
|
468
468
|
)
|
469
469
|
|
470
470
|
### NaN cannot be JSON-serialized.
|
@@ -482,16 +482,16 @@ def get_pipe_data(
|
|
482
482
|
|
483
483
|
@app.get(pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/csv', tags=['Pipes'])
|
484
484
|
def get_pipe_csv(
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
485
|
+
connector_keys: str,
|
486
|
+
metric_key: str,
|
487
|
+
location_key: str,
|
488
|
+
begin: Union[str, int, None] = None,
|
489
|
+
end: Union[str, int, None] = None,
|
490
|
+
params: Optional[str] = None,
|
491
|
+
curr_user = (
|
492
|
+
fastapi.Depends(manager) if not no_auth else None
|
493
|
+
),
|
494
|
+
) -> str:
|
495
495
|
"""
|
496
496
|
Get a Pipe's data as a CSV file. Optionally set query boundaries.
|
497
497
|
"""
|
@@ -518,8 +518,8 @@ def get_pipe_csv(
|
|
518
518
|
|
519
519
|
if not isinstance(_params, dict):
|
520
520
|
raise fastapi.HTTPException(
|
521
|
-
status_code
|
522
|
-
detail
|
521
|
+
status_code=409,
|
522
|
+
detail="Params must be a valid JSON-encoded dictionary.",
|
523
523
|
)
|
524
524
|
|
525
525
|
p = get_pipe(connector_keys, metric_key, location_key)
|
@@ -529,7 +529,7 @@ def get_pipe_csv(
|
|
529
529
|
detail = "Pipe must be registered with the datetime column specified."
|
530
530
|
)
|
531
531
|
|
532
|
-
dt_col =
|
532
|
+
dt_col = p.columns.get('datetime', None)
|
533
533
|
if dt_col:
|
534
534
|
if begin is None:
|
535
535
|
begin = p.get_sync_time(round_down=False, newest=False)
|
@@ -552,13 +552,13 @@ def get_pipe_csv(
|
|
552
552
|
|
553
553
|
@app.get(pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/id', tags=['Pipes'])
|
554
554
|
def get_pipe_id(
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
555
|
+
connector_keys: str,
|
556
|
+
metric_key: str,
|
557
|
+
location_key: str,
|
558
|
+
curr_user = (
|
559
|
+
fastapi.Depends(manager) if not no_auth else None
|
560
|
+
),
|
561
|
+
) -> int:
|
562
562
|
"""
|
563
563
|
Get a Pipe's ID.
|
564
564
|
"""
|
meerschaum/config/_version.py
CHANGED
@@ -346,14 +346,14 @@ def get_pipe_data(
|
|
346
346
|
try:
|
347
347
|
response = self.get(
|
348
348
|
r_url + "/data",
|
349
|
-
params
|
349
|
+
params={
|
350
350
|
'select_columns': json.dumps(select_columns),
|
351
351
|
'omit_columns': json.dumps(omit_columns),
|
352
352
|
'begin': begin,
|
353
353
|
'end': end,
|
354
|
-
'params': json.dumps(params)
|
354
|
+
'params': json.dumps(params, default=str)
|
355
355
|
},
|
356
|
-
debug
|
356
|
+
debug=debug
|
357
357
|
)
|
358
358
|
if not response.ok:
|
359
359
|
return None
|
@@ -272,7 +272,7 @@ class SQLConnector(Connector):
|
|
272
272
|
"""
|
273
273
|
Return whether this connector may be multithreaded.
|
274
274
|
"""
|
275
|
-
if self.flavor
|
275
|
+
if self.flavor in ('duckdb', 'oracle'):
|
276
276
|
return False
|
277
277
|
if self.flavor == 'sqlite':
|
278
278
|
return ':memory:' not in self.URI
|
@@ -15,12 +15,12 @@ from meerschaum.utils.warnings import warn
|
|
15
15
|
|
16
16
|
_in_memory_temp_tables: Dict[str, bool] = {}
|
17
17
|
def _log_temporary_tables_creation(
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
18
|
+
self,
|
19
|
+
tables: Union[str, List[str]],
|
20
|
+
ready_to_drop: bool = False,
|
21
|
+
create: bool = True,
|
22
|
+
debug: bool = False,
|
23
|
+
) -> SuccessTuple:
|
24
24
|
"""
|
25
25
|
Log a temporary table's creation for later deletion.
|
26
26
|
"""
|
@@ -58,15 +58,15 @@ def _log_temporary_tables_creation(
|
|
58
58
|
|
59
59
|
|
60
60
|
def _drop_temporary_table(
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
61
|
+
self,
|
62
|
+
table: str,
|
63
|
+
debug: bool = False,
|
64
|
+
) -> SuccessTuple:
|
65
65
|
"""
|
66
66
|
Drop a temporary table and clear it from the internal table.
|
67
67
|
"""
|
68
|
-
from meerschaum.utils.sql import sql_item_name, table_exists,
|
69
|
-
if_exists = "IF EXISTS" if self.flavor
|
68
|
+
from meerschaum.utils.sql import sql_item_name, table_exists, DROP_IF_EXISTS_FLAVORS
|
69
|
+
if_exists = "IF EXISTS" if self.flavor in DROP_IF_EXISTS_FLAVORS else ""
|
70
70
|
if not if_exists:
|
71
71
|
if not table_exists(table, self, self.internal_schema, debug=debug):
|
72
72
|
return True, "Success"
|