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.
@@ -516,9 +516,9 @@ def update_keys_options(
516
516
  if location_keys:
517
517
  num_filter += 1
518
518
 
519
- _ck_alone, _mk_alone, _lk_alone = False, False, False
520
- _ck_filter, _mk_filter, _lk_filter = connector_keys, metric_keys, location_keys
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', get_web_connector(ctx.states),
532
- connector_keys=_ck_filter, metric_keys=_mk_filter, location_keys=_lk_filter
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
- connector_keys = sorted([
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 = sorted([
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 = sorted([
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]
@@ -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
- 'Sync',
127
+ 'Delete',
128
128
  id = {
129
129
  'type': 'manage-pipe-button',
130
130
  'index': meta_str,
131
- 'action': 'sync',
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
- 'Delete',
143
+ 'Clear',
144
144
  id = {
145
145
  'type': 'manage-pipe-button',
146
146
  'index': meta_str,
147
- 'action': 'delete',
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 = "end",
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 recent data',
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
- for col_key, col in pipe.columns.items():
254
- overview_rows.append(html.Tr([html.Td(f"'{col_key}' Index"), html.Td(col)]))
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
 
@@ -2,4 +2,4 @@
2
2
  Specify the Meerschaum release version.
3
3
  """
4
4
 
5
- __version__ = "2.2.0.dev1"
5
+ __version__ = "2.2.0.dev2"
@@ -97,7 +97,6 @@ compose_header = """
97
97
 
98
98
 
99
99
  default_docker_compose_config = {
100
- 'version': '3.9',
101
100
  'services': {
102
101
  'db': {
103
102
  'environment': {
@@ -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
- 'rocketry' : 'rocketry>=2.5.1',
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.27.4',
78
+ 'compose' : 'docker-compose>=1.29.2',
79
79
  },
80
80
  'build': {
81
- 'cx_Freeze' : 'cx_Freeze>=6.5.1',
82
- 'PyInstaller' : 'pyinstaller>=5.0.0-dev0',
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<2.0.0',
152
+ # 'pydantic' : 'pydantic>2.0.0',
153
153
  'httpx' : 'httpx>=0.24.1',
154
154
  'websockets' : 'websockets>=11.0.3',
155
155
  }
@@ -7,11 +7,69 @@ Schedule processes and threads.
7
7
  """
8
8
 
9
9
  from __future__ import annotations
10
- from meerschaum.utils.typing import Callable, Any, Optional
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
- frequency: str,
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
- frequency: str
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.packages import attempt_import
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
- def _wrapper():
41
- return function(*args, **kw)
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
- pydantic = attempt_import('pydantic', debug=debug, lazy=False)
44
- rocketry = attempt_import('rocketry', debug=debug, lazy=False)
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
- app.session.shut_down(force=True)
56
- except CancelledError:
57
- pass
58
- return None
59
- except AttributeError:
60
- warn(
61
- "Failed to import scheduler.\n\n "
62
- + "Run `mrsm install package 'pydantic<2.0.0'` and try again.",
63
- stack = False,
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.dev1
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: rocketry >=2.5.1 ; extra == '_required'
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: rocketry >=2.5.1 ; extra == 'api'
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 >=6.5.1 ; extra == 'build'
128
- Requires-Dist: pyinstaller >=5.0.0-dev0 ; extra == 'build'
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: rocketry >=2.5.1 ; extra == 'full'
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: rocketry >=2.5.1 ; extra == 'sql'
298
+ Requires-Dist: apscheduler >=4.0.0a4 ; extra == 'sql'
301
299
  Provides-Extra: stack
302
- Requires-Dist: docker-compose >=1.27.4 ; extra == 'stack'
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=QFznPYi5drleN03sWNnf6i2jJ_j_eLviRFplqR7fZ2M,17599
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=DCXs_98JBXPgNCEkZLPaUCNnZrT4P9HK2d-YsBTw8BM,32206
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=PkQljZNVFa8BNyCTXpUW8xtjTZNJ6ru--pVX3diqFcU,76
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=4a_up1oxkitwgIylWWk0vA4XkGhEpWazUaENOPEdYQI,9034
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=lYra4f7QpGlmV4vAhO1UvA5TWQSPdLqw7eG7ekKZtwg,1951
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=yyf-3rAyRy5XaQkNFHjOC85VIkZt0o00o7rG3ykG7XU,56534
225
- meerschaum/utils/packages/_packages.py,sha256=ygRG1sBps2C0_7lfwMRnD84S4cPt4KiNAGXYduiyfoI,7968
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.dev1.dist-info/LICENSE,sha256=jG2zQEdRNt88EgHUWPpXVWmOrOduUQRx7MnYV9YIPaw,11359
230
- meerschaum-2.2.0.dev1.dist-info/METADATA,sha256=PsW8SeL9h5X0HPnnBk3tcNly1Wvl6epTWr0VRYuMRvo,23985
231
- meerschaum-2.2.0.dev1.dist-info/NOTICE,sha256=OTA9Fcthjf5BRvWDDIcBC_xfLpeDV-RPZh3M-HQBRtQ,114
232
- meerschaum-2.2.0.dev1.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
233
- meerschaum-2.2.0.dev1.dist-info/entry_points.txt,sha256=5YBVzibw-0rNA_1VjB16z5GABsOGf-CDhW4yqH8C7Gc,88
234
- meerschaum-2.2.0.dev1.dist-info/top_level.txt,sha256=bNoSiDj0El6buocix-FRoAtJOeq1qOF5rRm2u9i7Q6A,11
235
- meerschaum-2.2.0.dev1.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
236
- meerschaum-2.2.0.dev1.dist-info/RECORD,,
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,,