meerschaum 2.2.0.dev1__py3-none-any.whl → 2.2.0.dev2__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/api/dash/callbacks/dashboard.py +18 -12
- meerschaum/api/dash/pipes.py +44 -25
- meerschaum/config/_version.py +1 -1
- meerschaum/config/stack/__init__.py +0 -1
- meerschaum/utils/packages/__init__.py +4 -1
- meerschaum/utils/packages/_packages.py +5 -5
- meerschaum/utils/schedule.py +268 -29
- {meerschaum-2.2.0.dev1.dist-info → meerschaum-2.2.0.dev2.dist-info}/METADATA +8 -10
- {meerschaum-2.2.0.dev1.dist-info → meerschaum-2.2.0.dev2.dist-info}/RECORD +15 -15
- {meerschaum-2.2.0.dev1.dist-info → meerschaum-2.2.0.dev2.dist-info}/LICENSE +0 -0
- {meerschaum-2.2.0.dev1.dist-info → meerschaum-2.2.0.dev2.dist-info}/NOTICE +0 -0
- {meerschaum-2.2.0.dev1.dist-info → meerschaum-2.2.0.dev2.dist-info}/WHEEL +0 -0
- {meerschaum-2.2.0.dev1.dist-info → meerschaum-2.2.0.dev2.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.2.0.dev1.dist-info → meerschaum-2.2.0.dev2.dist-info}/top_level.txt +0 -0
- {meerschaum-2.2.0.dev1.dist-info → meerschaum-2.2.0.dev2.dist-info}/zip-safe +0 -0
@@ -516,9 +516,9 @@ def update_keys_options(
|
|
516
516
|
if location_keys:
|
517
517
|
num_filter += 1
|
518
518
|
|
519
|
-
|
520
|
-
|
521
|
-
|
519
|
+
_ck_filter = connector_keys
|
520
|
+
_mk_filter = metric_keys
|
521
|
+
_lk_filter = location_keys
|
522
522
|
_ck_alone = (connector_keys and num_filter == 1) or instance_click
|
523
523
|
_mk_alone = (metric_keys and num_filter == 1) or instance_click
|
524
524
|
_lk_alone = (location_keys and num_filter == 1) or instance_click
|
@@ -528,8 +528,11 @@ def update_keys_options(
|
|
528
528
|
try:
|
529
529
|
_all_keys = fetch_pipes_keys('registered', get_web_connector(ctx.states))
|
530
530
|
_keys = fetch_pipes_keys(
|
531
|
-
'registered',
|
532
|
-
|
531
|
+
'registered',
|
532
|
+
get_web_connector(ctx.states),
|
533
|
+
connector_keys = _ck_filter,
|
534
|
+
metric_keys = _mk_filter,
|
535
|
+
location_keys = _lk_filter,
|
533
536
|
)
|
534
537
|
except Exception as e:
|
535
538
|
instance_alerts += [alert_from_success_tuple((False, str(e)))]
|
@@ -551,30 +554,33 @@ def update_keys_options(
|
|
551
554
|
add_options(_connectors_options, _all_keys if _ck_alone else _keys, 'ck')
|
552
555
|
add_options(_metrics_options, _all_keys if _mk_alone else _keys, 'mk')
|
553
556
|
add_options(_locations_options, _all_keys if _lk_alone else _keys, 'lk')
|
554
|
-
|
557
|
+
_connectors_options.sort(key=lambda x: str(x).lower())
|
558
|
+
_metrics_options.sort(key=lambda x: str(x).lower())
|
559
|
+
_locations_options.sort(key=lambda x: str(x).lower())
|
560
|
+
connector_keys = [
|
555
561
|
ck
|
556
|
-
for ck in connector_keys
|
562
|
+
for ck in connector_keys
|
557
563
|
if ck in [
|
558
564
|
_ck['value']
|
559
565
|
for _ck in _connectors_options
|
560
566
|
]
|
561
|
-
]
|
562
|
-
metric_keys =
|
567
|
+
]
|
568
|
+
metric_keys = [
|
563
569
|
mk
|
564
570
|
for mk in metric_keys
|
565
571
|
if mk in [
|
566
572
|
_mk['value']
|
567
573
|
for _mk in _metrics_options
|
568
574
|
]
|
569
|
-
]
|
570
|
-
location_keys =
|
575
|
+
]
|
576
|
+
location_keys = [
|
571
577
|
lk
|
572
578
|
for lk in location_keys
|
573
579
|
if lk in [
|
574
580
|
_lk['value']
|
575
581
|
for _lk in _locations_options
|
576
582
|
]
|
577
|
-
]
|
583
|
+
]
|
578
584
|
_connectors_datalist = [html.Option(value=o['value']) for o in _connectors_options]
|
579
585
|
_metrics_datalist = [html.Option(value=o['value']) for o in _metrics_options]
|
580
586
|
_locations_datalist = [html.Option(value=o['value']) for o in _locations_options]
|
meerschaum/api/dash/pipes.py
CHANGED
@@ -124,11 +124,11 @@ def get_pipes_cards(*keys, session_data: Optional[Dict[str, Any]] = None):
|
|
124
124
|
label = "Manage",
|
125
125
|
children = [
|
126
126
|
dbc.DropdownMenuItem(
|
127
|
-
'
|
127
|
+
'Delete',
|
128
128
|
id = {
|
129
129
|
'type': 'manage-pipe-button',
|
130
130
|
'index': meta_str,
|
131
|
-
'action': '
|
131
|
+
'action': 'delete',
|
132
132
|
},
|
133
133
|
),
|
134
134
|
dbc.DropdownMenuItem(
|
@@ -140,15 +140,31 @@ def get_pipes_cards(*keys, session_data: Optional[Dict[str, Any]] = None):
|
|
140
140
|
},
|
141
141
|
),
|
142
142
|
dbc.DropdownMenuItem(
|
143
|
-
'
|
143
|
+
'Clear',
|
144
144
|
id = {
|
145
145
|
'type': 'manage-pipe-button',
|
146
146
|
'index': meta_str,
|
147
|
-
'action': '
|
147
|
+
'action': 'clear',
|
148
|
+
},
|
149
|
+
),
|
150
|
+
dbc.DropdownMenuItem(
|
151
|
+
'Verify',
|
152
|
+
id = {
|
153
|
+
'type': 'manage-pipe-button',
|
154
|
+
'index': meta_str,
|
155
|
+
'action': 'verify',
|
156
|
+
},
|
157
|
+
),
|
158
|
+
dbc.DropdownMenuItem(
|
159
|
+
'Sync',
|
160
|
+
id = {
|
161
|
+
'type': 'manage-pipe-button',
|
162
|
+
'index': meta_str,
|
163
|
+
'action': 'sync',
|
148
164
|
},
|
149
165
|
),
|
150
166
|
],
|
151
|
-
direction = "
|
167
|
+
direction = "up",
|
152
168
|
menu_variant = "dark",
|
153
169
|
size = 'sm',
|
154
170
|
color = 'secondary',
|
@@ -156,21 +172,10 @@ def get_pipes_cards(*keys, session_data: Optional[Dict[str, Any]] = None):
|
|
156
172
|
) if authenticated else [],
|
157
173
|
width = 2,
|
158
174
|
),
|
159
|
-
# dbc.Col(
|
160
|
-
# (
|
161
|
-
# dbc.Button(
|
162
|
-
# 'Sync',
|
163
|
-
# size = 'sm',
|
164
|
-
# style = {'width': '100%'},
|
165
|
-
# id = {'type': 'pipe-sync-button', 'index': meta_str},
|
166
|
-
# ) if authenticated else []
|
167
|
-
# ),
|
168
|
-
# width = 2,
|
169
|
-
# ),
|
170
175
|
dbc.Col(width=6),
|
171
176
|
dbc.Col(
|
172
177
|
dbc.Button(
|
173
|
-
'Download
|
178
|
+
'Download CSV',
|
174
179
|
size = 'sm',
|
175
180
|
color = 'link',
|
176
181
|
style = {'float': 'right'},
|
@@ -244,14 +249,28 @@ def accordion_items_from_pipe(
|
|
244
249
|
overview_header = [html.Thead(html.Tr([html.Th("Attribute"), html.Th("Value")]))]
|
245
250
|
dt_name, id_name, val_name = pipe.get_columns('datetime', 'id', 'value', error=False)
|
246
251
|
overview_rows = [
|
247
|
-
html.Tr([html.Td("Connector"), html.Td(f"{pipe.connector_keys}")]),
|
248
|
-
html.Tr([html.Td("Metric"), html.Td(f"{pipe.metric_key}")]),
|
249
|
-
html.Tr([html.Td("Location"), html.Td(f"{pipe.location_key}")]),
|
250
|
-
html.Tr([html.Td("Instance"), html.Td(f"{pipe.instance_keys}")]),
|
251
|
-
html.Tr([html.Td("Target Table"), html.Td(f"{pipe.target}")]),
|
252
|
+
html.Tr([html.Td("Connector"), html.Td(html.Pre(f"{pipe.connector_keys}"))]),
|
253
|
+
html.Tr([html.Td("Metric"), html.Td(html.Pre(f"{pipe.metric_key}"))]),
|
254
|
+
html.Tr([html.Td("Location"), html.Td(html.Pre(f"{pipe.location_key}"))]),
|
255
|
+
html.Tr([html.Td("Instance"), html.Td(html.Pre(f"{pipe.instance_keys}"))]),
|
256
|
+
html.Tr([html.Td("Target Table"), html.Td(html.Pre(f"{pipe.target}"))]),
|
252
257
|
]
|
253
|
-
|
254
|
-
|
258
|
+
columns = pipe.columns.copy()
|
259
|
+
if columns:
|
260
|
+
datetime_index = columns.pop('datetime', None)
|
261
|
+
columns_items = []
|
262
|
+
if datetime_index:
|
263
|
+
columns_items.append(html.Li(f"{datetime_index} (datetime)"))
|
264
|
+
columns_items.extend([
|
265
|
+
html.Li(f"{col}")
|
266
|
+
for col_key, col in columns.items()
|
267
|
+
])
|
268
|
+
overview_rows.append(
|
269
|
+
html.Tr([
|
270
|
+
html.Td("Indices" if len(columns_items) != 1 else "Index"),
|
271
|
+
html.Td(html.Pre(html.Ul(columns_items))),
|
272
|
+
])
|
273
|
+
)
|
255
274
|
tags = pipe.tags
|
256
275
|
if tags:
|
257
276
|
tags_items = html.Ul([
|
@@ -261,7 +280,7 @@ def accordion_items_from_pipe(
|
|
261
280
|
overview_rows.append(
|
262
281
|
html.Tr([
|
263
282
|
html.Td("Tags"),
|
264
|
-
html.Td(tags_items),
|
283
|
+
html.Td(html.Pre(tags_items)),
|
265
284
|
])
|
266
285
|
)
|
267
286
|
|
meerschaum/config/_version.py
CHANGED
@@ -8,7 +8,7 @@ Functions for managing packages and virtual environments reside here.
|
|
8
8
|
|
9
9
|
from __future__ import annotations
|
10
10
|
|
11
|
-
import importlib.util, os, pathlib
|
11
|
+
import importlib.util, os, pathlib, re
|
12
12
|
from meerschaum.utils.typing import Any, List, SuccessTuple, Optional, Union, Tuple, Dict, Iterable
|
13
13
|
from meerschaum.utils.threading import Lock, RLock
|
14
14
|
from meerschaum.utils.packages._packages import packages, all_packages, get_install_names
|
@@ -640,6 +640,9 @@ def need_update(
|
|
640
640
|
|
641
641
|
### We might be depending on a prerelease.
|
642
642
|
### Sanity check that the required version is not greater than the installed version.
|
643
|
+
if 'a' in required_version:
|
644
|
+
required_version = required_version.replace('a', '-dev')
|
645
|
+
version = version.replace('a', '-dev')
|
643
646
|
try:
|
644
647
|
return (
|
645
648
|
(not semver.Version.parse(version).match(required_version))
|
@@ -52,7 +52,7 @@ packages: Dict[str, Dict[str, str]] = {
|
|
52
52
|
'watchgod' : 'watchgod>=0.7.0',
|
53
53
|
'dill' : 'dill>=0.3.3',
|
54
54
|
'virtualenv' : 'virtualenv>=20.1.0',
|
55
|
-
'
|
55
|
+
'apscheduler' : 'apscheduler>=4.0.0a4',
|
56
56
|
},
|
57
57
|
'drivers': {
|
58
58
|
'cryptography' : 'cryptography>=38.0.1',
|
@@ -75,11 +75,11 @@ packages: Dict[str, Dict[str, str]] = {
|
|
75
75
|
'gadwall' : 'gadwall>=0.2.0',
|
76
76
|
},
|
77
77
|
'stack': {
|
78
|
-
'compose' : 'docker-compose>=1.
|
78
|
+
'compose' : 'docker-compose>=1.29.2',
|
79
79
|
},
|
80
80
|
'build': {
|
81
|
-
'cx_Freeze' : 'cx_Freeze>=
|
82
|
-
'PyInstaller' : 'pyinstaller
|
81
|
+
'cx_Freeze' : 'cx_Freeze>=7.0.0',
|
82
|
+
'PyInstaller' : 'pyinstaller>6.6.0',
|
83
83
|
},
|
84
84
|
'dev-tools': {
|
85
85
|
'twine' : 'twine>=3.2.0',
|
@@ -149,7 +149,7 @@ packages['api'] = {
|
|
149
149
|
'passlib' : 'passlib>=1.7.4',
|
150
150
|
'fastapi_login' : 'fastapi-login>=1.7.2',
|
151
151
|
'multipart' : 'python-multipart>=0.0.5',
|
152
|
-
'pydantic' : 'pydantic
|
152
|
+
# 'pydantic' : 'pydantic>2.0.0',
|
153
153
|
'httpx' : 'httpx>=0.24.1',
|
154
154
|
'websockets' : 'websockets>=11.0.3',
|
155
155
|
}
|
meerschaum/utils/schedule.py
CHANGED
@@ -7,11 +7,69 @@ Schedule processes and threads.
|
|
7
7
|
"""
|
8
8
|
|
9
9
|
from __future__ import annotations
|
10
|
-
|
10
|
+
import sys
|
11
|
+
from datetime import datetime, timezone, timedelta, timedelta
|
12
|
+
import meerschaum as mrsm
|
13
|
+
from meerschaum.utils.typing import Callable, Any, Optional, List, Dict
|
14
|
+
|
15
|
+
INTERVAL_UNITS: List[str] = ['months', 'weeks', 'days', 'hours', 'minutes', 'seconds']
|
16
|
+
FREQUENCY_ALIASES: Dict[str, str] = {
|
17
|
+
'daily': 'every 1 day',
|
18
|
+
'hourly': 'every 1 hour',
|
19
|
+
'minutely': 'every 1 minute',
|
20
|
+
'weekly': 'every 1 week',
|
21
|
+
'monthly': 'every 1 month',
|
22
|
+
'secondly': 'every 1 second',
|
23
|
+
}
|
24
|
+
LOGIC_ALIASES: Dict[str, str] = {
|
25
|
+
'and': '&',
|
26
|
+
'or': '|',
|
27
|
+
' through ': '-',
|
28
|
+
' thru ': '-',
|
29
|
+
' - ': '-',
|
30
|
+
'beginning': 'starting',
|
31
|
+
}
|
32
|
+
CRON_DAYS_OF_WEEK: List[str] = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']
|
33
|
+
CRON_DAYS_OF_WEEK_ALIASES: Dict[str, str] = {
|
34
|
+
'monday': 'mon',
|
35
|
+
'tuesday': 'tue',
|
36
|
+
'tues': 'tue',
|
37
|
+
'wednesday': 'wed',
|
38
|
+
'thursday': 'thu',
|
39
|
+
'thurs': 'thu',
|
40
|
+
'friday': 'fri',
|
41
|
+
'saturday': 'sat',
|
42
|
+
'sunday': 'sun',
|
43
|
+
}
|
44
|
+
CRON_MONTHS: List[str] = [
|
45
|
+
'jan', 'feb', 'mar', 'apr', 'may', 'jun',
|
46
|
+
'jul', 'aug', 'sep', 'oct', 'nov', 'dec',
|
47
|
+
]
|
48
|
+
CRON_MONTHS_ALIASES: Dict[str, str] = {
|
49
|
+
'january': 'jan',
|
50
|
+
'february': 'feb',
|
51
|
+
'march': 'mar',
|
52
|
+
'april': 'apr',
|
53
|
+
'may': 'may',
|
54
|
+
'june': 'jun',
|
55
|
+
'july': 'jul',
|
56
|
+
'august': 'aug',
|
57
|
+
'september': 'sep',
|
58
|
+
'october': 'oct',
|
59
|
+
'november': 'nov',
|
60
|
+
'december': 'dec',
|
61
|
+
}
|
62
|
+
SCHEDULE_ALIASES: Dict[str, str] = {
|
63
|
+
**FREQUENCY_ALIASES,
|
64
|
+
**LOGIC_ALIASES,
|
65
|
+
**CRON_DAYS_OF_WEEK_ALIASES,
|
66
|
+
**CRON_MONTHS_ALIASES,
|
67
|
+
}
|
68
|
+
STARTING_KEYWORD: str = 'starting'
|
11
69
|
|
12
70
|
def schedule_function(
|
13
71
|
function: Callable[[Any], Any],
|
14
|
-
|
72
|
+
schedule: str,
|
15
73
|
*args,
|
16
74
|
debug: bool = False,
|
17
75
|
**kw
|
@@ -25,41 +83,222 @@ def schedule_function(
|
|
25
83
|
function: Callable[[Any], Any]
|
26
84
|
The function to execute.
|
27
85
|
|
28
|
-
|
29
|
-
The frequency at which `function` should be executed (e.g. `'daily'`).
|
86
|
+
schedule: str
|
87
|
+
The frequency schedule at which `function` should be executed (e.g. `'daily'`).
|
30
88
|
|
31
89
|
"""
|
32
90
|
import warnings
|
33
91
|
from meerschaum.utils.warnings import warn
|
34
|
-
from meerschaum.utils.
|
35
|
-
from meerschaum.utils.misc import filter_keywords
|
36
|
-
from concurrent.futures._base import CancelledError
|
92
|
+
from meerschaum.utils.misc import filter_keywords, round_time
|
37
93
|
kw['debug'] = debug
|
38
94
|
kw = filter_keywords(function, **kw)
|
39
95
|
|
40
|
-
|
41
|
-
|
96
|
+
apscheduler = mrsm.attempt_import('apscheduler', lazy=False)
|
97
|
+
now = round_time(datetime.now(timezone.utc), timedelta(minutes=1))
|
98
|
+
trigger = parse_schedule(schedule, now=now)
|
42
99
|
|
43
|
-
|
44
|
-
|
45
|
-
try:
|
46
|
-
app = rocketry.Rocketry()
|
47
|
-
FuncTask = rocketry.tasks.FuncTask
|
48
|
-
with warnings.catch_warnings():
|
49
|
-
warnings.filterwarnings('ignore', 'Task\'s session not defined.')
|
50
|
-
task = FuncTask(_wrapper, start_cond=frequency)
|
51
|
-
app.session.add_task(task)
|
52
|
-
return app.run(debug=debug)
|
53
|
-
except (KeyboardInterrupt, CancelledError):
|
100
|
+
with apscheduler.Scheduler() as scheduler:
|
101
|
+
job = scheduler.add_schedule(function, trigger, args=args, kwargs=kw)
|
54
102
|
try:
|
55
|
-
|
56
|
-
except
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
103
|
+
scheduler.run_until_stopped()
|
104
|
+
except KeyboardInterrupt as e:
|
105
|
+
scheduler.stop()
|
106
|
+
scheduler.wait_until_stopped()
|
107
|
+
|
108
|
+
|
109
|
+
def parse_schedule(schedule: str, now: Optional[datetime] = None):
|
110
|
+
"""
|
111
|
+
Parse a schedule string (e.g. 'daily') into a Trigger object.
|
112
|
+
"""
|
113
|
+
from meerschaum.utils.warnings import error
|
114
|
+
from meerschaum.utils.misc import items_str, is_int
|
115
|
+
(
|
116
|
+
apscheduler_triggers_cron,
|
117
|
+
apscheduler_triggers_interval,
|
118
|
+
apscheduler_triggers_calendarinterval,
|
119
|
+
apscheduler_triggers_combining,
|
120
|
+
) = (
|
121
|
+
mrsm.attempt_import(
|
122
|
+
'apscheduler.triggers.cron',
|
123
|
+
'apscheduler.triggers.interval',
|
124
|
+
'apscheduler.triggers.calendarinterval',
|
125
|
+
'apscheduler.triggers.combining',
|
126
|
+
lazy = False,
|
64
127
|
)
|
128
|
+
)
|
129
|
+
|
130
|
+
starting_ts = parse_start_time(schedule, now=now)
|
131
|
+
schedule = schedule.split(STARTING_KEYWORD)[0].strip()
|
132
|
+
for alias_keyword, true_keyword in SCHEDULE_ALIASES.items():
|
133
|
+
schedule = schedule.replace(alias_keyword, true_keyword)
|
134
|
+
|
135
|
+
### TODO Allow for combining `and` + `or` logic.
|
136
|
+
if '&' in schedule and '|' in schedule:
|
137
|
+
error(f"Cannot accept both 'and' + 'or' logic in the schedule frequency.", ValueError)
|
138
|
+
|
139
|
+
join_str = '|' if '|' in schedule else '&'
|
140
|
+
join_trigger = (
|
141
|
+
apscheduler_triggers_combining.OrTrigger
|
142
|
+
if join_str == '|'
|
143
|
+
else apscheduler_triggers_combining.AndTrigger
|
144
|
+
)
|
145
|
+
join_kwargs = {
|
146
|
+
'max_iterations': 1_000_000,
|
147
|
+
'threshold': 0,
|
148
|
+
} if join_str == '&' else {}
|
149
|
+
|
150
|
+
schedule_parts = [part.strip() for part in schedule.split(join_str)]
|
151
|
+
triggers = []
|
152
|
+
|
153
|
+
has_seconds = 'second' in schedule
|
154
|
+
has_minutes = 'minute' in schedule
|
155
|
+
has_days = 'day' in schedule
|
156
|
+
has_weeks = 'week' in schedule
|
157
|
+
has_hours = 'hour' in schedule
|
158
|
+
num_hourly_intervals = schedule.count('hour')
|
159
|
+
divided_days = False
|
160
|
+
divided_hours = False
|
161
|
+
|
162
|
+
for schedule_part in schedule_parts:
|
163
|
+
|
164
|
+
### Intervals must begin with 'every' (after alias substitution).
|
165
|
+
if schedule_part.lower().startswith('every '):
|
166
|
+
schedule_num_str, schedule_unit = (
|
167
|
+
schedule_part[len('every '):].split(' ', maxsplit=1)
|
168
|
+
)
|
169
|
+
schedule_unit = schedule_unit.rstrip('s') + 's'
|
170
|
+
if schedule_unit not in INTERVAL_UNITS:
|
171
|
+
error(
|
172
|
+
f"Invalid interval '{schedule_unit}'.\n"
|
173
|
+
+ f" Accepted values are {items_str(INTERVAL_UNITS)}.",
|
174
|
+
ValueError,
|
175
|
+
)
|
176
|
+
|
177
|
+
schedule_num = (
|
178
|
+
int(schedule_num_str)
|
179
|
+
if is_int(schedule_num_str)
|
180
|
+
else float(schedule_num_str)
|
181
|
+
)
|
182
|
+
|
183
|
+
### NOTE: When combining days or weeks with other schedules,
|
184
|
+
### we must divide one of the day-schedules by 2.
|
185
|
+
### TODO Remove this when APScheduler is patched.
|
186
|
+
if (
|
187
|
+
join_str == '&'
|
188
|
+
and (has_days or has_weeks)
|
189
|
+
and len(schedule_parts) > 1
|
190
|
+
and not divided_days
|
191
|
+
):
|
192
|
+
schedule_num /= 2
|
193
|
+
divided_days = True
|
65
194
|
|
195
|
+
### NOTE: When combining multiple hourly intervals,
|
196
|
+
### one must be divided by 2.
|
197
|
+
if (
|
198
|
+
join_str == '&'
|
199
|
+
# and num_hourly_intervals > 1
|
200
|
+
and len(schedule_parts) > 1
|
201
|
+
and not divided_hours
|
202
|
+
):
|
203
|
+
print("divided hours")
|
204
|
+
schedule_num /= 2
|
205
|
+
# divided_hours = True
|
206
|
+
|
207
|
+
trigger = (
|
208
|
+
apscheduler_triggers_interval.IntervalTrigger(
|
209
|
+
**{
|
210
|
+
schedule_unit: schedule_num,
|
211
|
+
'start_time': starting_ts,
|
212
|
+
}
|
213
|
+
)
|
214
|
+
if schedule_unit != 'months' else (
|
215
|
+
apscheduler_triggers_calendarinterval.CalendarIntervalTrigger(
|
216
|
+
**{
|
217
|
+
schedule_unit: schedule_num,
|
218
|
+
'start_date': starting_ts,
|
219
|
+
# 'timezone': starting_ts.tzinfo, TODO Re-enable once APScheduler updates.
|
220
|
+
}
|
221
|
+
)
|
222
|
+
)
|
223
|
+
)
|
224
|
+
|
225
|
+
### Determine whether this is a pure cron string or a cron subset (e.g. 'may-aug')_.
|
226
|
+
else:
|
227
|
+
first_three_prefix = schedule_part[:3]
|
228
|
+
cron_kw = {}
|
229
|
+
if first_three_prefix in CRON_DAYS_OF_WEEK:
|
230
|
+
cron_kw['day_of_week'] = schedule_part
|
231
|
+
elif first_three_prefix in CRON_MONTHS:
|
232
|
+
cron_kw['month'] = schedule_part
|
233
|
+
trigger = (
|
234
|
+
apscheduler_triggers_cron.CronTrigger(
|
235
|
+
**{
|
236
|
+
**cron_kw,
|
237
|
+
'hour': '*',
|
238
|
+
'minute': '*' if has_minutes else starting_ts.minute,
|
239
|
+
'second': '*' if has_seconds else starting_ts.second,
|
240
|
+
'start_time': starting_ts,
|
241
|
+
'timezone': starting_ts.tzinfo,
|
242
|
+
}
|
243
|
+
)
|
244
|
+
if cron_kw
|
245
|
+
else apscheduler_triggers_cron.CronTrigger.from_crontab(
|
246
|
+
schedule_part,
|
247
|
+
timezone = starting_ts.tzinfo,
|
248
|
+
)
|
249
|
+
)
|
250
|
+
### Explicitly set the `start_time` after building with `from_crontab`.
|
251
|
+
if trigger.start_time != starting_ts:
|
252
|
+
trigger.start_time = starting_ts
|
253
|
+
|
254
|
+
triggers.append(trigger)
|
255
|
+
|
256
|
+
return (
|
257
|
+
join_trigger(triggers, **join_kwargs)
|
258
|
+
if len(triggers) != 1
|
259
|
+
else triggers[0]
|
260
|
+
)
|
261
|
+
|
262
|
+
|
263
|
+
def parse_start_time(schedule: str, now: Optional[datetime] = None) -> datetime:
|
264
|
+
"""
|
265
|
+
Return the datetime to use for the given schedule string.
|
266
|
+
|
267
|
+
Parameters
|
268
|
+
----------
|
269
|
+
schedule: str
|
270
|
+
The schedule frequency to be parsed into a starting datetime.
|
271
|
+
|
272
|
+
now: Optional[datetime], default None
|
273
|
+
If provided, use this value as a default if no start time is explicitly stated.
|
274
|
+
|
275
|
+
Returns
|
276
|
+
-------
|
277
|
+
A `datetime` object, either `now` or the datetime embedded in the schedule string.
|
278
|
+
|
279
|
+
Examples
|
280
|
+
--------
|
281
|
+
>>> parse_start_time('daily starting 2024-01-01')
|
282
|
+
datetime.datetime(2024, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)
|
283
|
+
>>> parse_start_time('monthly starting 1st')
|
284
|
+
datetime.datetime(2024, 5, 1, 0, 0, tzinfo=datetime.timezone.utc)
|
285
|
+
>>> parse_start_time('hourly starting 00:30')
|
286
|
+
datetime.datetime(2024, 5, 13, 0, 30, tzinfo=datetime.timezone.utc)
|
287
|
+
"""
|
288
|
+
from meerschaum.utils.misc import round_time
|
289
|
+
from meerschaum.utils.warnings import error, warn
|
290
|
+
dateutil_parser = mrsm.attempt_import('dateutil.parser')
|
291
|
+
starting_parts = schedule.split(STARTING_KEYWORD)
|
292
|
+
starting_str = ('now' if len(starting_parts) == 1 else starting_parts[-1]).strip()
|
293
|
+
now = now or round_time(datetime.now(timezone.utc), timedelta(minutes=1))
|
294
|
+
try:
|
295
|
+
starting_ts = now if starting_str == 'now' else dateutil_parser.parse(starting_str)
|
296
|
+
schedule_parse_error = None
|
297
|
+
except Exception as e:
|
298
|
+
warn(f"Unable to parse starting time from '{starting_str}'.", stack=False)
|
299
|
+
schedule_parse_error = str(e)
|
300
|
+
if schedule_parse_error:
|
301
|
+
error(schedule_parse_error, ValueError, stack=False)
|
302
|
+
if not starting_ts.tzinfo:
|
303
|
+
starting_ts = starting_ts.replace(tzinfo=timezone.utc)
|
304
|
+
return starting_ts
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: meerschaum
|
3
|
-
Version: 2.2.0.
|
3
|
+
Version: 2.2.0.dev2
|
4
4
|
Summary: Sync Time-Series Pipes with Meerschaum
|
5
5
|
Home-page: https://meerschaum.io
|
6
6
|
Author: Bennett Meares
|
@@ -56,7 +56,7 @@ Requires-Dist: psutil >=5.8.0 ; extra == '_required'
|
|
56
56
|
Requires-Dist: watchgod >=0.7.0 ; extra == '_required'
|
57
57
|
Requires-Dist: dill >=0.3.3 ; extra == '_required'
|
58
58
|
Requires-Dist: virtualenv >=20.1.0 ; extra == '_required'
|
59
|
-
Requires-Dist:
|
59
|
+
Requires-Dist: apscheduler >=4.0.0a4 ; extra == '_required'
|
60
60
|
Provides-Extra: api
|
61
61
|
Requires-Dist: uvicorn[standard] >=0.22.0 ; extra == 'api'
|
62
62
|
Requires-Dist: gunicorn >=20.1.0 ; extra == 'api'
|
@@ -66,7 +66,6 @@ Requires-Dist: fastapi >=0.100.0 ; extra == 'api'
|
|
66
66
|
Requires-Dist: passlib >=1.7.4 ; extra == 'api'
|
67
67
|
Requires-Dist: fastapi-login >=1.7.2 ; extra == 'api'
|
68
68
|
Requires-Dist: python-multipart >=0.0.5 ; extra == 'api'
|
69
|
-
Requires-Dist: pydantic <2.0.0 ; extra == 'api'
|
70
69
|
Requires-Dist: httpx >=0.24.1 ; extra == 'api'
|
71
70
|
Requires-Dist: numpy >=1.18.5 ; extra == 'api'
|
72
71
|
Requires-Dist: pandas[parquet] >=2.0.1 ; extra == 'api'
|
@@ -106,7 +105,7 @@ Requires-Dist: psutil >=5.8.0 ; extra == 'api'
|
|
106
105
|
Requires-Dist: watchgod >=0.7.0 ; extra == 'api'
|
107
106
|
Requires-Dist: dill >=0.3.3 ; extra == 'api'
|
108
107
|
Requires-Dist: virtualenv >=20.1.0 ; extra == 'api'
|
109
|
-
Requires-Dist:
|
108
|
+
Requires-Dist: apscheduler >=4.0.0a4 ; extra == 'api'
|
110
109
|
Requires-Dist: pprintpp >=0.4.0 ; extra == 'api'
|
111
110
|
Requires-Dist: asciitree >=0.3.3 ; extra == 'api'
|
112
111
|
Requires-Dist: typing-extensions >=4.7.1 ; extra == 'api'
|
@@ -124,8 +123,8 @@ Requires-Dist: dash-daq >=0.5.0 ; extra == 'api'
|
|
124
123
|
Requires-Dist: terminado >=0.12.1 ; extra == 'api'
|
125
124
|
Requires-Dist: tornado >=6.1.0 ; extra == 'api'
|
126
125
|
Provides-Extra: build
|
127
|
-
Requires-Dist: cx-Freeze >=
|
128
|
-
Requires-Dist: pyinstaller
|
126
|
+
Requires-Dist: cx-Freeze >=7.0.0 ; extra == 'build'
|
127
|
+
Requires-Dist: pyinstaller >6.6.0 ; extra == 'build'
|
129
128
|
Provides-Extra: cli
|
130
129
|
Requires-Dist: pgcli >=3.1.0 ; extra == 'cli'
|
131
130
|
Requires-Dist: mycli >=1.23.2 ; extra == 'cli'
|
@@ -212,7 +211,7 @@ Requires-Dist: psutil >=5.8.0 ; extra == 'full'
|
|
212
211
|
Requires-Dist: watchgod >=0.7.0 ; extra == 'full'
|
213
212
|
Requires-Dist: dill >=0.3.3 ; extra == 'full'
|
214
213
|
Requires-Dist: virtualenv >=20.1.0 ; extra == 'full'
|
215
|
-
Requires-Dist:
|
214
|
+
Requires-Dist: apscheduler >=4.0.0a4 ; extra == 'full'
|
216
215
|
Requires-Dist: cryptography >=38.0.1 ; extra == 'full'
|
217
216
|
Requires-Dist: psycopg[binary] >=3.1.18 ; extra == 'full'
|
218
217
|
Requires-Dist: PyMySQL >=0.9.0 ; extra == 'full'
|
@@ -249,7 +248,6 @@ Requires-Dist: fastapi >=0.100.0 ; extra == 'full'
|
|
249
248
|
Requires-Dist: passlib >=1.7.4 ; extra == 'full'
|
250
249
|
Requires-Dist: fastapi-login >=1.7.2 ; extra == 'full'
|
251
250
|
Requires-Dist: python-multipart >=0.0.5 ; extra == 'full'
|
252
|
-
Requires-Dist: pydantic <2.0.0 ; extra == 'full'
|
253
251
|
Requires-Dist: httpx >=0.24.1 ; extra == 'full'
|
254
252
|
Provides-Extra: gui
|
255
253
|
Requires-Dist: toga >=0.3.0-dev29 ; extra == 'gui'
|
@@ -297,9 +295,9 @@ Requires-Dist: psutil >=5.8.0 ; extra == 'sql'
|
|
297
295
|
Requires-Dist: watchgod >=0.7.0 ; extra == 'sql'
|
298
296
|
Requires-Dist: dill >=0.3.3 ; extra == 'sql'
|
299
297
|
Requires-Dist: virtualenv >=20.1.0 ; extra == 'sql'
|
300
|
-
Requires-Dist:
|
298
|
+
Requires-Dist: apscheduler >=4.0.0a4 ; extra == 'sql'
|
301
299
|
Provides-Extra: stack
|
302
|
-
Requires-Dist: docker-compose >=1.
|
300
|
+
Requires-Dist: docker-compose >=1.29.2 ; extra == 'stack'
|
303
301
|
|
304
302
|
<img src="https://meerschaum.io/assets/banner_1920x320.png" alt="Meerschaum banner" style="width: 100%"/>
|
305
303
|
|
@@ -60,7 +60,7 @@ meerschaum/api/dash/connectors.py,sha256=nJxBOFldtCMJLYjUSVYZwX5BO-LNjTNHgoEaXe-
|
|
60
60
|
meerschaum/api/dash/graphs.py,sha256=wJUDWzcLN8-C3xko6rj0F2v7Rt8YDkSXoVkkXJjYGIk,2046
|
61
61
|
meerschaum/api/dash/jobs.py,sha256=htqnrtAGCOgn2-THezMGYM8n1E-M-sM5A5qNmOGfHzg,7529
|
62
62
|
meerschaum/api/dash/keys.py,sha256=anGVwK5pVR5alVQS0oStl7iq9c6klsocENfSjCRdGyQ,12591
|
63
|
-
meerschaum/api/dash/pipes.py,sha256=
|
63
|
+
meerschaum/api/dash/pipes.py,sha256=weaxEodeTVcZJPxgnEkN0mFP9tKlzObaoK9ubQ1BAlY,18496
|
64
64
|
meerschaum/api/dash/plugins.py,sha256=Ib9z9jFC3pSGSuqjeuSlswhHnLcBrVf30B8VFdYyV-s,3115
|
65
65
|
meerschaum/api/dash/sync.py,sha256=9lt7IRdG-fe9gf_ZO_viPiGlerX7ic6r_VFocv3I51A,504
|
66
66
|
meerschaum/api/dash/users.py,sha256=xfKwezl_yM4-gmCFSk4VRXR8teIEouw03c6oGVhOzns,2362
|
@@ -73,7 +73,7 @@ meerschaum/api/dash/assets/favicon.ico,sha256=nDEekVxwS60wEDno1nbw5GF3TJOcDV26SE
|
|
73
73
|
meerschaum/api/dash/assets/logo_48x48.png,sha256=hTR5BHUHEN4yP2xiqAcDciuigoII9T3-80R-dzsxVmw,10218
|
74
74
|
meerschaum/api/dash/assets/logo_500x500.png,sha256=9EUtf6wQcEZTXHKfQ2kjNXod6Rn_4DTB_BkTgxggq00,67702
|
75
75
|
meerschaum/api/dash/callbacks/__init__.py,sha256=tAcK0ZX5qAN9mD5f7AYv0IBsGI6vI5r6wswPXfGgJfM,325
|
76
|
-
meerschaum/api/dash/callbacks/dashboard.py,sha256=
|
76
|
+
meerschaum/api/dash/callbacks/dashboard.py,sha256=BZWMAuaSuvlEZNUChCJ_I-QK9ClQliQ8XWNbK_gstP0,32346
|
77
77
|
meerschaum/api/dash/callbacks/jobs.py,sha256=OEYxJRlmnxqbsvHb5jBJJCsRvVCsMNusm9AClCT9Pm0,7689
|
78
78
|
meerschaum/api/dash/callbacks/login.py,sha256=dfl0EOkEUcF7a-u8fq0WGNtwV63714ds30yWfOM2rqE,2613
|
79
79
|
meerschaum/api/dash/callbacks/plugins.py,sha256=7CrwwbBI2N3DR4Yb1bKmrtJ3cAQ3dVv1l6E_AtZ6Wng,2777
|
@@ -134,9 +134,9 @@ meerschaum/config/_preprocess.py,sha256=-AEA8m_--KivZwTQ1sWN6LTn5sio_fUr2XZ51BO6
|
|
134
134
|
meerschaum/config/_read_config.py,sha256=WFZKIXZMDe_ca0ES7ivgM_mnwShvFxLdoeisT_X5-h0,14720
|
135
135
|
meerschaum/config/_shell.py,sha256=s74cmJl8NrhM_Y1cB_P41_JDUYXV0g4WXnKFZWMtnrY,3551
|
136
136
|
meerschaum/config/_sync.py,sha256=Q-sz5YcjL3CJS2Dyw4rVRQsz9th9GWa9o5F9D0Jrmn8,4120
|
137
|
-
meerschaum/config/_version.py,sha256=
|
137
|
+
meerschaum/config/_version.py,sha256=C1chg72mvosNVsfDxJ0TiYiWC1iqAPXO56RhWOWpGHk,76
|
138
138
|
meerschaum/config/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
139
|
-
meerschaum/config/stack/__init__.py,sha256=
|
139
|
+
meerschaum/config/stack/__init__.py,sha256=pKR7aDqqrGZjjNhbWbA9AMdfBjF_-zl7xtgVXk9B9Mg,9012
|
140
140
|
meerschaum/config/stack/grafana/__init__.py,sha256=wzuoch_AK49lcn7lH2qTSJ_PPbSagF4lcweeipz_XiE,2010
|
141
141
|
meerschaum/config/stack/mosquitto/__init__.py,sha256=-OwOjq8KiBoSH_pmgCAAF3Dp3CRD4KgAEdimZSadROs,186
|
142
142
|
meerschaum/config/stack/mosquitto/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -204,7 +204,7 @@ meerschaum/utils/networking.py,sha256=Sr_eYUGW8_UV9-k9LqRFf7xLtbUcsDucODyLCRsFRU
|
|
204
204
|
meerschaum/utils/pool.py,sha256=vkE42af4fjrTEJTxf6Ek3xGucm1MtEkpsSEiaVzNKHs,2655
|
205
205
|
meerschaum/utils/process.py,sha256=tbEutHAg_Kn5UetOI-fduRjsafGOYX5tkLvpzqosgvc,7098
|
206
206
|
meerschaum/utils/prompt.py,sha256=0mBFbgi_l9rCou9UnC_6qKTHkqyl1Z_jSRzfmc0xRXM,16490
|
207
|
-
meerschaum/utils/schedule.py,sha256=
|
207
|
+
meerschaum/utils/schedule.py,sha256=VAlNZQHjGoAzsmGTiHbUMzf3kIxriPF9GjKKZZefyHQ,10350
|
208
208
|
meerschaum/utils/sql.py,sha256=4sCNEpgUd6uFz6ySs4nnUMVaOT0YAvPM1ZlQYJTSF-0,46656
|
209
209
|
meerschaum/utils/threading.py,sha256=fAXk7-FnbFvdU1FQ4vHKk5NeGbbTpTw7y9dRnlVayNI,2472
|
210
210
|
meerschaum/utils/typing.py,sha256=L05wOXfWdn_nJ0KnZVr-2zdMYcqjdyOW_7InT3xe6-s,2807
|
@@ -221,16 +221,16 @@ meerschaum/utils/formatting/_jobs.py,sha256=s1lVcdMkzNj5Bqw-GsUhcguUFtahi5nQ-kg1
|
|
221
221
|
meerschaum/utils/formatting/_pipes.py,sha256=wy0iWJFsFl3X2VloaiA_gp9Yx9w6tD3FQZvAQAqef4A,19492
|
222
222
|
meerschaum/utils/formatting/_pprint.py,sha256=tgrT3FyGyu5CWJYysqK3kX1xdZYorlbOk9fcU_vt9Qg,3096
|
223
223
|
meerschaum/utils/formatting/_shell.py,sha256=ox75O7VHDAiwzSvdMSJZhXLadvAqYJVeihU6WeZ2Ogc,3677
|
224
|
-
meerschaum/utils/packages/__init__.py,sha256=
|
225
|
-
meerschaum/utils/packages/_packages.py,sha256=
|
224
|
+
meerschaum/utils/packages/__init__.py,sha256=P7nASOvBEcpaz1CBl2KGVQRZ1F1zkNflL34UGPH6lKw,56682
|
225
|
+
meerschaum/utils/packages/_packages.py,sha256=qHmYl74vfMULlia3EU6qrurpKyAsBqQcmantUxBFb4E,7970
|
226
226
|
meerschaum/utils/packages/lazy_loader.py,sha256=VHnph3VozH29R4JnSSBfwtA5WKZYZQFT_GeQSShCnuc,2540
|
227
227
|
meerschaum/utils/venv/_Venv.py,sha256=sBnlmxHdAh2bx8btfVoD79-H9-cYsv5lP02IIXkyECs,3553
|
228
228
|
meerschaum/utils/venv/__init__.py,sha256=sj-n8scWH2NPDJGAxfpqzsYqVUt2jMEr-7Uq9G7YUNQ,23183
|
229
|
-
meerschaum-2.2.0.
|
230
|
-
meerschaum-2.2.0.
|
231
|
-
meerschaum-2.2.0.
|
232
|
-
meerschaum-2.2.0.
|
233
|
-
meerschaum-2.2.0.
|
234
|
-
meerschaum-2.2.0.
|
235
|
-
meerschaum-2.2.0.
|
236
|
-
meerschaum-2.2.0.
|
229
|
+
meerschaum-2.2.0.dev2.dist-info/LICENSE,sha256=jG2zQEdRNt88EgHUWPpXVWmOrOduUQRx7MnYV9YIPaw,11359
|
230
|
+
meerschaum-2.2.0.dev2.dist-info/METADATA,sha256=nrTJOaYvdtJzVMeX0tBuCt_D5N8ZVOAigQdUj7b3q8c,23902
|
231
|
+
meerschaum-2.2.0.dev2.dist-info/NOTICE,sha256=OTA9Fcthjf5BRvWDDIcBC_xfLpeDV-RPZh3M-HQBRtQ,114
|
232
|
+
meerschaum-2.2.0.dev2.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
233
|
+
meerschaum-2.2.0.dev2.dist-info/entry_points.txt,sha256=5YBVzibw-0rNA_1VjB16z5GABsOGf-CDhW4yqH8C7Gc,88
|
234
|
+
meerschaum-2.2.0.dev2.dist-info/top_level.txt,sha256=bNoSiDj0El6buocix-FRoAtJOeq1qOF5rRm2u9i7Q6A,11
|
235
|
+
meerschaum-2.2.0.dev2.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
236
|
+
meerschaum-2.2.0.dev2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|