meerschaum 2.1.4__py3-none-any.whl → 2.1.6__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/__main__.py +1 -1
- meerschaum/_internal/arguments/_parser.py +5 -4
- meerschaum/_internal/docs/index.py +2 -2
- meerschaum/_internal/term/TermPageHandler.py +2 -2
- meerschaum/actions/register.py +5 -1
- meerschaum/actions/show.py +12 -1
- meerschaum/actions/sync.py +49 -21
- meerschaum/actions/tag.py +101 -0
- meerschaum/api/__init__.py +1 -1
- meerschaum/api/dash/callbacks/dashboard.py +1 -1
- meerschaum/api/routes/_login.py +4 -3
- meerschaum/config/_formatting.py +1 -1
- meerschaum/config/_version.py +1 -1
- meerschaum/connectors/api/APIConnector.py +6 -2
- meerschaum/connectors/sql/_pipes.py +43 -30
- meerschaum/connectors/sql/tables/__init__.py +0 -16
- meerschaum/core/Pipe/__init__.py +1 -2
- meerschaum/core/Pipe/_data.py +5 -4
- meerschaum/utils/__init__.py +3 -1
- meerschaum/utils/{get_pipes.py → _get_pipes.py} +5 -16
- meerschaum/utils/daemon/Daemon.py +4 -2
- meerschaum/utils/dataframe.py +3 -3
- meerschaum/utils/interactive.py +2 -16
- meerschaum/utils/misc.py +27 -28
- meerschaum/utils/packages/__init__.py +7 -1
- meerschaum/utils/packages/_packages.py +0 -1
- meerschaum/utils/pool.py +14 -17
- meerschaum/utils/sql.py +41 -19
- meerschaum/utils/typing.py +11 -0
- meerschaum/utils/venv/__init__.py +2 -1
- {meerschaum-2.1.4.dist-info → meerschaum-2.1.6.dist-info}/METADATA +2 -3
- {meerschaum-2.1.4.dist-info → meerschaum-2.1.6.dist-info}/RECORD +38 -37
- {meerschaum-2.1.4.dist-info → meerschaum-2.1.6.dist-info}/WHEEL +1 -1
- {meerschaum-2.1.4.dist-info → meerschaum-2.1.6.dist-info}/LICENSE +0 -0
- {meerschaum-2.1.4.dist-info → meerschaum-2.1.6.dist-info}/NOTICE +0 -0
- {meerschaum-2.1.4.dist-info → meerschaum-2.1.6.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.1.4.dist-info → meerschaum-2.1.6.dist-info}/top_level.txt +0 -0
- {meerschaum-2.1.4.dist-info → meerschaum-2.1.6.dist-info}/zip-safe +0 -0
meerschaum/__main__.py
CHANGED
@@ -76,7 +76,7 @@ def _close_pools():
|
|
76
76
|
"""Close multiprocessing pools before exiting."""
|
77
77
|
### Final step: close global pools.
|
78
78
|
from meerschaum.utils.pool import get_pools
|
79
|
-
for
|
79
|
+
for pool in get_pools().values():
|
80
80
|
try:
|
81
81
|
pool.close()
|
82
82
|
pool.terminate()
|
@@ -8,7 +8,9 @@ This module creates the argparse Parser.
|
|
8
8
|
|
9
9
|
from __future__ import annotations
|
10
10
|
import sys
|
11
|
-
import argparse
|
11
|
+
import argparse
|
12
|
+
import json
|
13
|
+
from datetime import datetime, timedelta, timezone
|
12
14
|
from meerschaum.utils.typing import Union, Dict, List, Any, Tuple, Callable
|
13
15
|
from meerschaum.utils.misc import string_to_dict
|
14
16
|
from meerschaum.utils.warnings import error
|
@@ -35,19 +37,18 @@ class ArgumentParser(argparse.ArgumentParser):
|
|
35
37
|
return result
|
36
38
|
|
37
39
|
|
38
|
-
def parse_datetime(dt_str: str) -> datetime
|
40
|
+
def parse_datetime(dt_str: str) -> datetime:
|
39
41
|
"""Parse a string into a datetime."""
|
40
42
|
from meerschaum.utils.misc import is_int
|
41
43
|
if is_int(dt_str):
|
42
44
|
return int(dt_str)
|
43
45
|
|
44
46
|
from meerschaum.utils.packages import attempt_import
|
45
|
-
import datetime
|
46
47
|
dateutil_parser = attempt_import('dateutil.parser')
|
47
48
|
|
48
49
|
try:
|
49
50
|
if dt_str.lower() == 'now':
|
50
|
-
dt = datetime.
|
51
|
+
dt = datetime.now(timezone.utc).replace(tzinfo=None)
|
51
52
|
else:
|
52
53
|
dt = dateutil_parser.parse(dt_str)
|
53
54
|
except Exception as e:
|
@@ -11,14 +11,14 @@
|
|
11
11
|
|  |  | [](https://openbase.com/python/meerschaum?utm_source=embedded&utm_medium=badge&utm_campaign=rate-badge) |  |
|
12
12
|
|
13
13
|
<p align="center">
|
14
|
-
<img src="https://meerschaum.io/files/images/demo.gif" alt="Meerschaum demo"
|
14
|
+
<img src="https://meerschaum.io/files/images/demo.gif" alt="Meerschaum demo" style="width: 100%;">
|
15
15
|
</p>
|
16
16
|
|
17
17
|
## What is Meerschaum?
|
18
18
|
Meerschaum is a tool for quickly synchronizing time-series data streams called **pipes**. With Meerschaum, you can have a data visualization stack running in minutes.
|
19
19
|
|
20
20
|
<p align="center">
|
21
|
-
<img src="https://meerschaum.io/assets/screenshots/weather_pipes.png"/>
|
21
|
+
<img src="https://meerschaum.io/assets/screenshots/weather_pipes.png" style="width: 100%;"/>
|
22
22
|
</p>
|
23
23
|
|
24
24
|
## Why Meerschaum?
|
@@ -8,9 +8,9 @@ Define the terminal page handler class.
|
|
8
8
|
|
9
9
|
from __future__ import annotations
|
10
10
|
from meerschaum.utils.packages import attempt_import
|
11
|
-
|
11
|
+
tornado_web = attempt_import('tornado.web', lazy=False)
|
12
12
|
|
13
|
-
class TermPageHandler(
|
13
|
+
class TermPageHandler(tornado_web.RequestHandler):
|
14
14
|
def get(self):
|
15
15
|
from meerschaum.api import endpoints
|
16
16
|
return self.render(
|
meerschaum/actions/register.py
CHANGED
@@ -7,7 +7,7 @@ Register new Pipes. Requires the API to be running.
|
|
7
7
|
"""
|
8
8
|
|
9
9
|
from __future__ import annotations
|
10
|
-
from meerschaum.utils.typing import SuccessTuple, Any, List, Optional
|
10
|
+
from meerschaum.utils.typing import SuccessTuple, Any, List, Optional, Dict
|
11
11
|
|
12
12
|
def register(
|
13
13
|
action: Optional[List[str]] = None,
|
@@ -55,6 +55,7 @@ def _register_pipes(
|
|
55
55
|
metric_keys: Optional[List[str]] = None,
|
56
56
|
location_keys: Optional[List[str]] = None,
|
57
57
|
params: Optional[Dict[str, Any]] = None,
|
58
|
+
tags: Optional[List[str]] = None,
|
58
59
|
debug: bool = False,
|
59
60
|
**kw: Any
|
60
61
|
) -> SuccessTuple:
|
@@ -79,6 +80,8 @@ def _register_pipes(
|
|
79
80
|
location_keys = []
|
80
81
|
if params is None:
|
81
82
|
params = {}
|
83
|
+
if tags:
|
84
|
+
params['tags'] = tags
|
82
85
|
|
83
86
|
if (
|
84
87
|
len(connector_keys) == 0 or
|
@@ -96,6 +99,7 @@ def _register_pipes(
|
|
96
99
|
metric_keys = metric_keys,
|
97
100
|
location_keys = location_keys,
|
98
101
|
params = params,
|
102
|
+
tags = tags,
|
99
103
|
as_list = True,
|
100
104
|
method = 'explicit',
|
101
105
|
debug = debug,
|
meerschaum/actions/show.py
CHANGED
@@ -738,6 +738,7 @@ def _show_environment(
|
|
738
738
|
|
739
739
|
|
740
740
|
def _show_tags(
|
741
|
+
action: Optional[List[str]] = None,
|
741
742
|
tags: Optional[List[str]] = None,
|
742
743
|
workers: Optional[int] = None,
|
743
744
|
nopretty: bool = False,
|
@@ -757,7 +758,12 @@ def _show_tags(
|
|
757
758
|
)
|
758
759
|
panel = rich_panel.Panel.fit('Tags')
|
759
760
|
tree = rich_tree.Tree(panel)
|
760
|
-
|
761
|
+
action = action or []
|
762
|
+
tags = action + (tags or [])
|
763
|
+
pipes = mrsm.get_pipes(as_list=True, tags=tags, **kwargs)
|
764
|
+
if not pipes:
|
765
|
+
return False, f"No pipes were found with the given tags."
|
766
|
+
|
761
767
|
pool = get_pool(workers=workers)
|
762
768
|
tag_prefix = get_config('formatting', 'pipes', 'unicode', 'icons', 'tag') if UNICODE else ''
|
763
769
|
tag_style = get_config('formatting', 'pipes', 'ansi', 'styles', 'tags') if ANSI else None
|
@@ -769,6 +775,8 @@ def _show_tags(
|
|
769
775
|
|
770
776
|
for pipe, tags in pipes_tags.items():
|
771
777
|
for tag in tags:
|
778
|
+
if action and tag not in action:
|
779
|
+
continue
|
772
780
|
tags_pipes[tag].append(pipe)
|
773
781
|
|
774
782
|
columns = []
|
@@ -790,6 +798,9 @@ def _show_tags(
|
|
790
798
|
tag_panel = rich_panel.Panel(tag_group, title=tag_text, title_align='left')
|
791
799
|
columns.append(tag_panel)
|
792
800
|
|
801
|
+
if len(columns) == 0:
|
802
|
+
return False, "No pipes have been tagged."
|
803
|
+
|
793
804
|
if not nopretty:
|
794
805
|
mrsm.pprint(
|
795
806
|
rich_columns.Columns(
|
meerschaum/actions/sync.py
CHANGED
@@ -9,7 +9,7 @@ NOTE: `sync` required a SQL connection and is not intended for client use
|
|
9
9
|
"""
|
10
10
|
|
11
11
|
from __future__ import annotations
|
12
|
-
from datetime import timedelta
|
12
|
+
from datetime import timedelta, datetime, timezone
|
13
13
|
import meerschaum as mrsm
|
14
14
|
from meerschaum.utils.typing import SuccessTuple, Any, List, Optional, Tuple, Union
|
15
15
|
|
@@ -39,7 +39,7 @@ def sync(
|
|
39
39
|
|
40
40
|
def _pipes_lap(
|
41
41
|
workers: Optional[int] = None,
|
42
|
-
debug: bool = None,
|
42
|
+
debug: Optional[bool] = None,
|
43
43
|
unblock: bool = False,
|
44
44
|
force: bool = False,
|
45
45
|
min_seconds: int = 1,
|
@@ -52,7 +52,7 @@ def _pipes_lap(
|
|
52
52
|
nopretty: bool = False,
|
53
53
|
_progress: Optional['rich.progress.Progress'] = None,
|
54
54
|
**kw: Any
|
55
|
-
) -> Tuple[List[
|
55
|
+
) -> Tuple[List[mrsm.Pipe], List[mrsm.Pipe]]:
|
56
56
|
"""
|
57
57
|
Do a lap of syncing pipes.
|
58
58
|
"""
|
@@ -402,11 +402,20 @@ def _wrap_pipe(
|
|
402
402
|
Wrapper function for handling exceptions.
|
403
403
|
"""
|
404
404
|
import time
|
405
|
+
import traceback
|
406
|
+
from datetime import datetime, timedelta, timezone
|
407
|
+
import meerschaum as mrsm
|
408
|
+
from meerschaum.utils.typing import is_success_tuple, SuccessTuple
|
405
409
|
from meerschaum.connectors import get_connector_plugin
|
406
410
|
from meerschaum.utils.venv import Venv
|
407
411
|
from meerschaum.plugins import _pre_sync_hooks, _post_sync_hooks
|
408
412
|
from meerschaum.utils.misc import filter_keywords
|
413
|
+
from meerschaum.utils.pool import get_pool
|
414
|
+
from meerschaum.utils.warnings import warn
|
415
|
+
|
416
|
+
pool = get_pool(workers=workers)
|
409
417
|
|
418
|
+
sync_timestamp = datetime.now(timezone.utc)
|
410
419
|
sync_start = time.perf_counter()
|
411
420
|
sync_kwargs = {k: v for k, v in kw.items() if k != 'blocking'}
|
412
421
|
sync_kwargs.update({
|
@@ -415,8 +424,9 @@ def _wrap_pipe(
|
|
415
424
|
'debug': debug,
|
416
425
|
'min_seconds': min_seconds,
|
417
426
|
'workers': workers,
|
418
|
-
'bounded':
|
427
|
+
'bounded': bounded,
|
419
428
|
'chunk_interval': chunk_interval,
|
429
|
+
'sync_timestamp': sync_timestamp,
|
420
430
|
})
|
421
431
|
if not verify and not deduplicate:
|
422
432
|
sync_method = pipe.sync
|
@@ -427,12 +437,32 @@ def _wrap_pipe(
|
|
427
437
|
sync_kwargs['deduplicate'] = deduplicate
|
428
438
|
sync_kwargs['sync_method'] = sync_method
|
429
439
|
|
430
|
-
|
431
|
-
plugin_name = module_name.split('.')[-1] if module_name.startswith('plugins.') else None
|
440
|
+
def call_sync_hook(plugin_name: str, sync_hook) -> SuccessTuple:
|
432
441
|
plugin = mrsm.Plugin(plugin_name) if plugin_name else None
|
433
|
-
with Venv(plugin):
|
434
|
-
|
435
|
-
|
442
|
+
with mrsm.Venv(plugin):
|
443
|
+
try:
|
444
|
+
sync_hook_result = sync_hook(pipe, **filter_keywords(sync_hook, **sync_kwargs))
|
445
|
+
if is_success_tuple(sync_hook_result):
|
446
|
+
return sync_hook_result
|
447
|
+
except Exception as e:
|
448
|
+
msg = (
|
449
|
+
f"Failed to execute sync hook '{sync_hook.__name__}' "
|
450
|
+
+ f"from plugin '{plugin}':\n{traceback.format_exc()}"
|
451
|
+
)
|
452
|
+
warn(msg, stack=False)
|
453
|
+
return False, msg
|
454
|
+
return True, "Success"
|
455
|
+
|
456
|
+
hook_results = []
|
457
|
+
def apply_hooks(is_pre_sync: bool):
|
458
|
+
_sync_hooks = (_pre_sync_hooks if is_pre_sync else _post_sync_hooks)
|
459
|
+
for module_name, sync_hooks in _sync_hooks.items():
|
460
|
+
plugin_name = module_name.split('.')[-1] if module_name.startswith('plugins.') else None
|
461
|
+
for sync_hook in sync_hooks:
|
462
|
+
hook_result = pool.apply_async(call_sync_hook, (plugin_name, sync_hook))
|
463
|
+
hook_results.append(hook_result)
|
464
|
+
|
465
|
+
apply_hooks(True)
|
436
466
|
|
437
467
|
try:
|
438
468
|
with Venv(get_connector_plugin(pipe.connector), debug=debug):
|
@@ -444,18 +474,16 @@ def _wrap_pipe(
|
|
444
474
|
return_tuple = (False, f"Failed to sync {pipe} with exception:" + "\n" + str(e))
|
445
475
|
|
446
476
|
duration = time.perf_counter() - sync_start
|
447
|
-
sync_kwargs
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
)
|
458
|
-
|
477
|
+
sync_kwargs.update({
|
478
|
+
'success_tuple': return_tuple,
|
479
|
+
'sync_duration': duration,
|
480
|
+
'sync_complete_timestamp': datetime.now(timezone.utc),
|
481
|
+
})
|
482
|
+
apply_hooks(False)
|
483
|
+
for hook_result in hook_results:
|
484
|
+
hook_success, hook_msg = hook_result.get()
|
485
|
+
mrsm.pprint((hook_success, hook_msg))
|
486
|
+
|
459
487
|
return return_tuple
|
460
488
|
|
461
489
|
|
@@ -0,0 +1,101 @@
|
|
1
|
+
#! /usr/bin/env python
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
# vim:fenc=utf-8
|
4
|
+
|
5
|
+
"""
|
6
|
+
Functions for editing elements belong here.
|
7
|
+
"""
|
8
|
+
|
9
|
+
from __future__ import annotations
|
10
|
+
import meerschaum as mrsm
|
11
|
+
from meerschaum.utils.typing import List, Any, SuccessTuple, Optional, Dict
|
12
|
+
|
13
|
+
def tag(
|
14
|
+
action: Optional[List[str]] = None,
|
15
|
+
**kwargs: Any
|
16
|
+
) -> SuccessTuple:
|
17
|
+
"""
|
18
|
+
Edit an existing element.
|
19
|
+
"""
|
20
|
+
from meerschaum.actions import choose_subaction
|
21
|
+
options = {
|
22
|
+
'pipes': _tag_pipes,
|
23
|
+
}
|
24
|
+
return choose_subaction(action, options, **kwargs)
|
25
|
+
|
26
|
+
|
27
|
+
def _tag_pipes(
|
28
|
+
action: Optional[List[str]] = None,
|
29
|
+
debug: bool = False,
|
30
|
+
**kwargs: Any
|
31
|
+
) -> SuccessTuple:
|
32
|
+
"""
|
33
|
+
Add or remove tags to registered pipes.
|
34
|
+
Prefix a tag with a leading underscore to remove it.
|
35
|
+
|
36
|
+
Note that the `--tags` flag applies to existing tags.
|
37
|
+
Specify the tags you wish to add or remove as positional arguments, e.g.:
|
38
|
+
|
39
|
+
```
|
40
|
+
mrsm tag pipes production sync-daily -c sql:main
|
41
|
+
mrsm tag pipes _production --tags production
|
42
|
+
```
|
43
|
+
"""
|
44
|
+
from meerschaum.utils.warnings import warn
|
45
|
+
from meerschaum.utils.misc import separate_negation_values
|
46
|
+
if not action:
|
47
|
+
return False, "No tags were provided."
|
48
|
+
|
49
|
+
pipes = mrsm.get_pipes(as_list=True, debug=debug, **kwargs)
|
50
|
+
if not pipes:
|
51
|
+
return False, "No pipes were found with the given keys."
|
52
|
+
success_msg = f"Updated tags for {len(pipes)} pipe" + ('s' if len(pipes) != 1 else '') + '.'
|
53
|
+
|
54
|
+
add_tags, remove_tags = separate_negation_values(action)
|
55
|
+
edited_pipes = []
|
56
|
+
for pipe in pipes:
|
57
|
+
pipe_was_edited = False
|
58
|
+
pipe_tags = pipe.tags
|
59
|
+
existing_tags = set(pipe_tags)
|
60
|
+
for tag_to_add in add_tags:
|
61
|
+
if tag_to_add not in existing_tags:
|
62
|
+
pipe_tags.append(tag_to_add)
|
63
|
+
pipe_was_edited = True
|
64
|
+
|
65
|
+
for tag_to_remove in remove_tags:
|
66
|
+
if tag_to_remove in existing_tags:
|
67
|
+
pipe_tags.remove(tag_to_remove)
|
68
|
+
pipe_was_edited = True
|
69
|
+
|
70
|
+
if pipe_was_edited:
|
71
|
+
edited_pipes.append(pipe)
|
72
|
+
|
73
|
+
if not edited_pipes:
|
74
|
+
return True, success_msg
|
75
|
+
|
76
|
+
failed_edit_msgs = []
|
77
|
+
for pipe in edited_pipes:
|
78
|
+
edit_success, edit_msg = pipe.edit(debug=debug)
|
79
|
+
if not edit_success:
|
80
|
+
warn(f"Failed to update tags for {pipe}:\n{edit_msg}", stack=False)
|
81
|
+
failed_edit_msgs.append(edit_msg)
|
82
|
+
|
83
|
+
num_failures = len(failed_edit_msgs)
|
84
|
+
success = num_failures == 0
|
85
|
+
msg = (
|
86
|
+
success_msg
|
87
|
+
if success else (
|
88
|
+
f"Failed to update tags for {num_failures} pipe"
|
89
|
+
+ ('s' if num_failures != 1 else '')
|
90
|
+
+ ":\n"
|
91
|
+
+ '\n'.join(failed_edit_msgs)
|
92
|
+
)
|
93
|
+
)
|
94
|
+
return success, msg
|
95
|
+
|
96
|
+
|
97
|
+
### NOTE: This must be the final statement of the module.
|
98
|
+
### Any subactions added below these lines will not
|
99
|
+
### be added to the `help` docstring.
|
100
|
+
from meerschaum.actions import choices_docstring as _choices_docstring
|
101
|
+
tag.__doc__ += _choices_docstring('tag')
|
meerschaum/api/__init__.py
CHANGED
@@ -18,7 +18,7 @@ The Meerschaum Web API lets you access and control your data over the Internet.
|
|
18
18
|
from meerschaum.config import get_config
|
19
19
|
from meerschaum.config.static import STATIC_CONFIG, SERVER_ID
|
20
20
|
from meerschaum.utils.packages import attempt_import
|
21
|
-
from meerschaum.utils
|
21
|
+
from meerschaum.utils import get_pipes as _get_pipes
|
22
22
|
from meerschaum.config._paths import API_UVICORN_CONFIG_PATH, API_UVICORN_RESOURCES_PATH
|
23
23
|
from meerschaum.plugins import _api_plugins
|
24
24
|
from meerschaum.utils.warnings import warn
|
@@ -523,7 +523,7 @@ def update_keys_options(
|
|
523
523
|
_mk_alone = metric_keys and num_filter == 1
|
524
524
|
_lk_alone = location_keys and num_filter == 1
|
525
525
|
|
526
|
-
from meerschaum.utils
|
526
|
+
from meerschaum.utils import fetch_pipes_keys
|
527
527
|
|
528
528
|
try:
|
529
529
|
_all_keys = fetch_pipes_keys('registered', get_web_connector(ctx.states))
|
meerschaum/api/routes/_login.py
CHANGED
@@ -6,7 +6,8 @@
|
|
6
6
|
Manage access and refresh tokens.
|
7
7
|
"""
|
8
8
|
|
9
|
-
import
|
9
|
+
from datetime import datetime, timedelta, timezone
|
10
|
+
import fastapi
|
10
11
|
from fastapi_login.exceptions import InvalidCredentialsException
|
11
12
|
from fastapi.security import OAuth2PasswordRequestForm
|
12
13
|
from starlette.responses import Response, JSONResponse
|
@@ -47,8 +48,8 @@ def login(
|
|
47
48
|
raise InvalidCredentialsException
|
48
49
|
|
49
50
|
expires_minutes = STATIC_CONFIG['api']['oauth']['token_expires_minutes']
|
50
|
-
expires_delta =
|
51
|
-
expires_dt = datetime.
|
51
|
+
expires_delta = timedelta(minutes=expires_minutes)
|
52
|
+
expires_dt = datetime.now(timezone.utc).replace(tzinfo=None) + expires_delta
|
52
53
|
access_token = manager.create_access_token(
|
53
54
|
data = dict(sub=username),
|
54
55
|
expires = expires_delta
|
meerschaum/config/_formatting.py
CHANGED
meerschaum/config/_version.py
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
Interact with Meerschaum APIs. May be chained together (see 'meerschaum:api_instance' in your config.yaml).
|
7
7
|
"""
|
8
8
|
|
9
|
-
from datetime import datetime, timedelta
|
9
|
+
from datetime import datetime, timedelta, timezone
|
10
10
|
from meerschaum.utils.typing import Optional
|
11
11
|
from meerschaum.connectors import Connector
|
12
12
|
from meerschaum.utils.warnings import warn, error
|
@@ -146,7 +146,11 @@ class APIConnector(Connector):
|
|
146
146
|
def token(self):
|
147
147
|
expired = (
|
148
148
|
True if self._expires is None else (
|
149
|
-
(
|
149
|
+
(
|
150
|
+
self._expires
|
151
|
+
<
|
152
|
+
datetime.now(timezone.utc).replace(tzinfo=None) + timedelta(minutes=1)
|
153
|
+
)
|
150
154
|
)
|
151
155
|
)
|
152
156
|
|
@@ -242,7 +242,6 @@ def fetch_pipes_keys(
|
|
242
242
|
pipes_tbl.c.metric_key,
|
243
243
|
pipes_tbl.c.location_key,
|
244
244
|
]
|
245
|
-
+ ([pipes_tbl.c.parameters] if tags else [])
|
246
245
|
)
|
247
246
|
|
248
247
|
q = sqlalchemy.select(*select_cols).where(sqlalchemy.and_(True, *_where))
|
@@ -267,7 +266,8 @@ def fetch_pipes_keys(
|
|
267
266
|
sqlalchemy.String,
|
268
267
|
).like(f'%"tags":%"{nt}"%')
|
269
268
|
)
|
270
|
-
|
269
|
+
if sub_ands:
|
270
|
+
ors.append(sqlalchemy.and_(*sub_ands))
|
271
271
|
|
272
272
|
for xt in _ex_tags:
|
273
273
|
nands.append(
|
@@ -303,26 +303,7 @@ def fetch_pipes_keys(
|
|
303
303
|
except Exception as e:
|
304
304
|
error(str(e))
|
305
305
|
|
306
|
-
|
307
|
-
if not tags:
|
308
|
-
return _keys
|
309
|
-
### Make 100% sure that the tags are correct.
|
310
|
-
keys = []
|
311
|
-
for row in rows:
|
312
|
-
ktup = (row[0], row[1], row[2])
|
313
|
-
_actual_tags = (
|
314
|
-
json.loads(row[3]) if isinstance(row[3], str)
|
315
|
-
else row[3]
|
316
|
-
).get('tags', [])
|
317
|
-
for nt in _in_tags:
|
318
|
-
if nt in _actual_tags:
|
319
|
-
keys.append(ktup)
|
320
|
-
for xt in _ex_tags:
|
321
|
-
if xt in _actual_tags:
|
322
|
-
keys.remove(ktup)
|
323
|
-
else:
|
324
|
-
keys.append(ktup)
|
325
|
-
return keys
|
306
|
+
return [(row[0], row[1], row[2]) for row in rows]
|
326
307
|
|
327
308
|
|
328
309
|
def create_indices(
|
@@ -404,7 +385,13 @@ def get_create_index_queries(
|
|
404
385
|
-------
|
405
386
|
A dictionary of column names mapping to lists of queries.
|
406
387
|
"""
|
407
|
-
from meerschaum.utils.sql import
|
388
|
+
from meerschaum.utils.sql import (
|
389
|
+
sql_item_name,
|
390
|
+
get_distinct_col_count,
|
391
|
+
update_queries,
|
392
|
+
get_null_replacement,
|
393
|
+
COALESCE_UNIQUE_INDEX_FLAVORS,
|
394
|
+
)
|
408
395
|
from meerschaum.config import get_config
|
409
396
|
index_queries = {}
|
410
397
|
|
@@ -516,15 +503,37 @@ def get_create_index_queries(
|
|
516
503
|
if ix and ix in existing_cols_types
|
517
504
|
]
|
518
505
|
)
|
506
|
+
coalesce_indices_cols_str = ', '.join(
|
507
|
+
[
|
508
|
+
(
|
509
|
+
"COALESCE("
|
510
|
+
+ sql_item_name(ix, self.flavor)
|
511
|
+
+ ", "
|
512
|
+
+ get_null_replacement(existing_cols_types[ix], self.flavor)
|
513
|
+
+ ") "
|
514
|
+
) if ix_key != 'datetime' else (sql_item_name(ix, self.flavor))
|
515
|
+
for ix_key, ix in pipe.columns.items()
|
516
|
+
if ix and ix in existing_cols_types
|
517
|
+
]
|
518
|
+
)
|
519
|
+
unique_index_name = sql_item_name(pipe.target + '_unique_index', self.flavor)
|
519
520
|
constraint_name = sql_item_name(pipe.target + '_constraint', self.flavor)
|
520
|
-
|
521
|
+
add_constraint_query = (
|
521
522
|
f"ALTER TABLE {_pipe_name} ADD CONSTRAINT {constraint_name} UNIQUE ({indices_cols_str})"
|
522
|
-
if self.flavor != 'sqlite'
|
523
|
-
else f"CREATE UNIQUE INDEX {constraint_name} ON {_pipe_name} ({indices_cols_str})"
|
524
523
|
)
|
524
|
+
unique_index_cols_str = (
|
525
|
+
indices_cols_str
|
526
|
+
if self.flavor not in COALESCE_UNIQUE_INDEX_FLAVORS
|
527
|
+
else coalesce_indices_cols_str
|
528
|
+
)
|
529
|
+
create_unique_index_query = (
|
530
|
+
f"CREATE UNIQUE INDEX {unique_index_name} ON {_pipe_name} ({unique_index_cols_str})"
|
531
|
+
)
|
532
|
+
constraint_queries = [create_unique_index_query]
|
533
|
+
if self.flavor != 'sqlite':
|
534
|
+
constraint_queries.append(add_constraint_query)
|
525
535
|
if upsert and indices_cols_str:
|
526
|
-
index_queries[
|
527
|
-
|
536
|
+
index_queries[unique_index_name] = constraint_queries
|
528
537
|
return index_queries
|
529
538
|
|
530
539
|
|
@@ -1093,7 +1102,7 @@ def get_pipe_attributes(
|
|
1093
1102
|
def sync_pipe(
|
1094
1103
|
self,
|
1095
1104
|
pipe: mrsm.Pipe,
|
1096
|
-
df: Union[
|
1105
|
+
df: Union[pd.DataFrame, str, Dict[Any, Any], None] = None,
|
1097
1106
|
begin: Optional[datetime] = None,
|
1098
1107
|
end: Optional[datetime] = None,
|
1099
1108
|
chunksize: Optional[int] = -1,
|
@@ -1305,7 +1314,11 @@ def sync_pipe(
|
|
1305
1314
|
temp_pipe = Pipe(
|
1306
1315
|
pipe.connector_keys.replace(':', '_') + '_', pipe.metric_key, pipe.location_key,
|
1307
1316
|
instance = pipe.instance_keys,
|
1308
|
-
columns =
|
1317
|
+
columns = {
|
1318
|
+
ix_key: ix
|
1319
|
+
for ix_key, ix in pipe.columns.items()
|
1320
|
+
if ix and ix in update_df.columns
|
1321
|
+
},
|
1309
1322
|
dtypes = pipe.dtypes,
|
1310
1323
|
target = temp_target,
|
1311
1324
|
temporary = True,
|
@@ -214,22 +214,6 @@ def create_tables(
|
|
214
214
|
from meerschaum.utils.sql import get_rename_table_queries, table_exists
|
215
215
|
_tables = tables if tables is not None else get_tables(conn)
|
216
216
|
|
217
|
-
rename_queries = []
|
218
|
-
for table_key, table in _tables.items():
|
219
|
-
if table_exists(
|
220
|
-
table_key,
|
221
|
-
conn,
|
222
|
-
schema = conn.instance_schema,
|
223
|
-
):
|
224
|
-
rename_queries.extend(get_rename_table_queries(
|
225
|
-
table_key,
|
226
|
-
table.name,
|
227
|
-
schema = conn.instance_schema,
|
228
|
-
flavor = conn.flavor,
|
229
|
-
))
|
230
|
-
if rename_queries:
|
231
|
-
conn.exec_queries(rename_queries)
|
232
|
-
|
233
217
|
try:
|
234
218
|
conn.metadata.create_all(bind=conn.engine)
|
235
219
|
except Exception as e:
|
meerschaum/core/Pipe/__init__.py
CHANGED
@@ -7,8 +7,7 @@ Pipes are the primary metaphor of the Meerschaum system.
|
|
7
7
|
You can interact with pipe data via `meerschaum.Pipe` objects.
|
8
8
|
|
9
9
|
If you are working with multiple pipes, it is highly recommended that you instead use
|
10
|
-
`meerschaum.
|
11
|
-
to create a dictionary of Pipe objects.
|
10
|
+
`meerschaum.get_pipes` to create a dictionary of Pipe objects.
|
12
11
|
|
13
12
|
```
|
14
13
|
>>> from meerschaum import get_pipes
|
meerschaum/core/Pipe/_data.py
CHANGED
@@ -8,7 +8,7 @@ Retrieve Pipes' data from instances.
|
|
8
8
|
|
9
9
|
from __future__ import annotations
|
10
10
|
from datetime import datetime, timedelta
|
11
|
-
from meerschaum.utils.typing import Optional, Dict, Any, Union, Generator, List, Tuple
|
11
|
+
from meerschaum.utils.typing import Optional, Dict, Any, Union, Generator, List, Tuple, Iterator
|
12
12
|
from meerschaum.config import get_config
|
13
13
|
|
14
14
|
def get_data(
|
@@ -247,7 +247,7 @@ def _get_data_as_iterator(
|
|
247
247
|
fresh: bool = False,
|
248
248
|
debug: bool = False,
|
249
249
|
**kw: Any
|
250
|
-
) ->
|
250
|
+
) -> Iterator['pd.DataFrame']:
|
251
251
|
"""
|
252
252
|
Return a pipe's data as a generator.
|
253
253
|
"""
|
@@ -267,16 +267,17 @@ def _get_data_as_iterator(
|
|
267
267
|
|
268
268
|
_ = kw.pop('as_chunks', None)
|
269
269
|
_ = kw.pop('as_iterator', None)
|
270
|
+
dt_col = self.columns.get('datetime', None)
|
270
271
|
min_dt = (
|
271
272
|
begin
|
272
273
|
if begin is not None
|
273
274
|
else self.get_sync_time(round_down=False, newest=False, params=params, debug=debug)
|
274
|
-
)
|
275
|
+
) if dt_col else None
|
275
276
|
max_dt = (
|
276
277
|
end
|
277
278
|
if end is not None
|
278
279
|
else self.get_sync_time(round_down=False, newest=True, params=params, debug=debug)
|
279
|
-
)
|
280
|
+
) if dt_col else None
|
280
281
|
|
281
282
|
### We want to search just past the maximum value.
|
282
283
|
if end is None:
|