meerschaum 2.1.7__py3-none-any.whl → 2.2.0__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.
Files changed (59) hide show
  1. meerschaum/__main__.py +1 -1
  2. meerschaum/_internal/arguments/_parser.py +3 -0
  3. meerschaum/_internal/entry.py +3 -2
  4. meerschaum/actions/install.py +7 -3
  5. meerschaum/actions/show.py +128 -42
  6. meerschaum/actions/sync.py +7 -3
  7. meerschaum/api/__init__.py +24 -14
  8. meerschaum/api/_oauth2.py +4 -4
  9. meerschaum/api/dash/callbacks/dashboard.py +93 -23
  10. meerschaum/api/dash/callbacks/jobs.py +55 -3
  11. meerschaum/api/dash/jobs.py +34 -8
  12. meerschaum/api/dash/keys.py +1 -1
  13. meerschaum/api/dash/pages/dashboard.py +14 -4
  14. meerschaum/api/dash/pipes.py +137 -26
  15. meerschaum/api/dash/plugins.py +25 -9
  16. meerschaum/api/resources/static/js/xterm.js +1 -1
  17. meerschaum/api/resources/templates/termpage.html +3 -0
  18. meerschaum/api/routes/_login.py +5 -4
  19. meerschaum/api/routes/_plugins.py +6 -3
  20. meerschaum/config/_dash.py +11 -0
  21. meerschaum/config/_default.py +3 -1
  22. meerschaum/config/_jobs.py +13 -4
  23. meerschaum/config/_paths.py +2 -0
  24. meerschaum/config/_sync.py +2 -3
  25. meerschaum/config/_version.py +1 -1
  26. meerschaum/config/stack/__init__.py +6 -7
  27. meerschaum/config/stack/grafana/__init__.py +1 -1
  28. meerschaum/config/static/__init__.py +4 -1
  29. meerschaum/connectors/__init__.py +2 -0
  30. meerschaum/connectors/api/_plugins.py +2 -1
  31. meerschaum/connectors/sql/SQLConnector.py +4 -2
  32. meerschaum/connectors/sql/_create_engine.py +9 -9
  33. meerschaum/connectors/sql/_instance.py +3 -1
  34. meerschaum/connectors/sql/_pipes.py +54 -38
  35. meerschaum/connectors/sql/_plugins.py +0 -2
  36. meerschaum/connectors/sql/_sql.py +7 -9
  37. meerschaum/core/User/_User.py +158 -16
  38. meerschaum/core/User/__init__.py +1 -1
  39. meerschaum/plugins/_Plugin.py +12 -3
  40. meerschaum/plugins/__init__.py +23 -1
  41. meerschaum/utils/daemon/Daemon.py +89 -36
  42. meerschaum/utils/daemon/FileDescriptorInterceptor.py +140 -0
  43. meerschaum/utils/daemon/RotatingFile.py +130 -14
  44. meerschaum/utils/daemon/__init__.py +3 -0
  45. meerschaum/utils/dtypes/__init__.py +9 -5
  46. meerschaum/utils/packages/__init__.py +21 -5
  47. meerschaum/utils/packages/_packages.py +18 -20
  48. meerschaum/utils/process.py +13 -10
  49. meerschaum/utils/schedule.py +276 -30
  50. meerschaum/utils/threading.py +1 -0
  51. meerschaum/utils/typing.py +1 -1
  52. {meerschaum-2.1.7.dist-info → meerschaum-2.2.0.dist-info}/METADATA +59 -62
  53. {meerschaum-2.1.7.dist-info → meerschaum-2.2.0.dist-info}/RECORD +59 -57
  54. {meerschaum-2.1.7.dist-info → meerschaum-2.2.0.dist-info}/WHEEL +1 -1
  55. {meerschaum-2.1.7.dist-info → meerschaum-2.2.0.dist-info}/LICENSE +0 -0
  56. {meerschaum-2.1.7.dist-info → meerschaum-2.2.0.dist-info}/NOTICE +0 -0
  57. {meerschaum-2.1.7.dist-info → meerschaum-2.2.0.dist-info}/entry_points.txt +0 -0
  58. {meerschaum-2.1.7.dist-info → meerschaum-2.2.0.dist-info}/top_level.txt +0 -0
  59. {meerschaum-2.1.7.dist-info → meerschaum-2.2.0.dist-info}/zip-safe +0 -0
@@ -7,11 +7,71 @@ 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
11
14
 
15
+ STARTING_KEYWORD: str = 'starting'
16
+ INTERVAL_UNITS: List[str] = ['months', 'weeks', 'days', 'hours', 'minutes', 'seconds', 'years']
17
+ FREQUENCY_ALIASES: Dict[str, str] = {
18
+ 'daily': 'every 1 day',
19
+ 'hourly': 'every 1 hour',
20
+ 'minutely': 'every 1 minute',
21
+ 'weekly': 'every 1 week',
22
+ 'monthly': 'every 1 month',
23
+ 'secondly': 'every 1 second',
24
+ 'yearly': 'every 1 year',
25
+ }
26
+ LOGIC_ALIASES: Dict[str, str] = {
27
+ 'and': '&',
28
+ 'or': '|',
29
+ ' through ': '-',
30
+ ' thru ': '-',
31
+ ' - ': '-',
32
+ 'beginning': STARTING_KEYWORD,
33
+ }
34
+ CRON_DAYS_OF_WEEK: List[str] = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']
35
+ CRON_DAYS_OF_WEEK_ALIASES: Dict[str, str] = {
36
+ 'monday': 'mon',
37
+ 'tuesday': 'tue',
38
+ 'tues': 'tue',
39
+ 'wednesday': 'wed',
40
+ 'thursday': 'thu',
41
+ 'thurs': 'thu',
42
+ 'friday': 'fri',
43
+ 'saturday': 'sat',
44
+ 'sunday': 'sun',
45
+ }
46
+ CRON_MONTHS: List[str] = [
47
+ 'jan', 'feb', 'mar', 'apr', 'may', 'jun',
48
+ 'jul', 'aug', 'sep', 'oct', 'nov', 'dec',
49
+ ]
50
+ CRON_MONTHS_ALIASES: Dict[str, str] = {
51
+ 'january': 'jan',
52
+ 'february': 'feb',
53
+ 'march': 'mar',
54
+ 'april': 'apr',
55
+ 'may': 'may',
56
+ 'june': 'jun',
57
+ 'july': 'jul',
58
+ 'august': 'aug',
59
+ 'september': 'sep',
60
+ 'october': 'oct',
61
+ 'november': 'nov',
62
+ 'december': 'dec',
63
+ }
64
+ SCHEDULE_ALIASES: Dict[str, str] = {
65
+ **FREQUENCY_ALIASES,
66
+ **LOGIC_ALIASES,
67
+ **CRON_DAYS_OF_WEEK_ALIASES,
68
+ **CRON_MONTHS_ALIASES,
69
+ }
70
+
71
+ _scheduler = None
12
72
  def schedule_function(
13
73
  function: Callable[[Any], Any],
14
- frequency: str,
74
+ schedule: str,
15
75
  *args,
16
76
  debug: bool = False,
17
77
  **kw
@@ -25,41 +85,227 @@ def schedule_function(
25
85
  function: Callable[[Any], Any]
26
86
  The function to execute.
27
87
 
28
- frequency: str
29
- The frequency at which `function` should be executed (e.g. `'daily'`).
88
+ schedule: str
89
+ The frequency schedule at which `function` should be executed (e.g. `'daily'`).
30
90
 
31
91
  """
32
- import warnings
92
+ import asyncio
33
93
  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
94
+ from meerschaum.utils.misc import filter_keywords, round_time
95
+ global _scheduler
37
96
  kw['debug'] = debug
38
97
  kw = filter_keywords(function, **kw)
39
98
 
40
- def _wrapper():
41
- return function(*args, **kw)
99
+ apscheduler = mrsm.attempt_import('apscheduler', lazy=False)
100
+ now = round_time(datetime.now(timezone.utc), timedelta(minutes=1))
101
+ trigger = parse_schedule(schedule, now=now)
102
+ _scheduler = apscheduler.AsyncScheduler()
103
+ try:
104
+ loop = asyncio.get_running_loop()
105
+ except RuntimeError:
106
+ loop = asyncio.new_event_loop()
107
+
108
+ async def run_scheduler():
109
+ async with _scheduler:
110
+ job = await _scheduler.add_schedule(function, trigger, args=args, kwargs=kw)
111
+ try:
112
+ await _scheduler.run_until_stopped()
113
+ except (KeyboardInterrupt, SystemExit) as e:
114
+ await _stop_scheduler()
115
+ raise e
42
116
 
43
- pydantic = attempt_import('pydantic', debug=debug, lazy=False)
44
- rocketry = attempt_import('rocketry', debug=debug, lazy=False)
45
117
  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):
54
- 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,
118
+ loop.run_until_complete(run_scheduler())
119
+ except (KeyboardInterrupt, SystemExit) as e:
120
+ loop.run_until_complete(_stop_scheduler())
121
+
122
+
123
+ def parse_schedule(schedule: str, now: Optional[datetime] = None):
124
+ """
125
+ Parse a schedule string (e.g. 'daily') into a Trigger object.
126
+ """
127
+ from meerschaum.utils.warnings import error
128
+ from meerschaum.utils.misc import items_str, is_int
129
+ (
130
+ apscheduler_triggers_cron,
131
+ apscheduler_triggers_interval,
132
+ apscheduler_triggers_calendarinterval,
133
+ apscheduler_triggers_combining,
134
+ ) = (
135
+ mrsm.attempt_import(
136
+ 'apscheduler.triggers.cron',
137
+ 'apscheduler.triggers.interval',
138
+ 'apscheduler.triggers.calendarinterval',
139
+ 'apscheduler.triggers.combining',
140
+ lazy = False,
64
141
  )
142
+ )
143
+
144
+ starting_ts = parse_start_time(schedule, now=now)
145
+ schedule = schedule.split(STARTING_KEYWORD)[0].strip()
146
+ for alias_keyword, true_keyword in SCHEDULE_ALIASES.items():
147
+ schedule = schedule.replace(alias_keyword, true_keyword)
148
+
149
+ ### TODO Allow for combining `and` + `or` logic.
150
+ if '&' in schedule and '|' in schedule:
151
+ raise ValueError(f"Cannot accept both 'and' + 'or' logic in the schedule frequency.")
152
+
153
+ join_str = '|' if '|' in schedule else '&'
154
+ join_trigger = (
155
+ apscheduler_triggers_combining.OrTrigger
156
+ if join_str == '|'
157
+ else apscheduler_triggers_combining.AndTrigger
158
+ )
159
+ join_kwargs = {
160
+ 'max_iterations': 1_000_000,
161
+ 'threshold': 0,
162
+ } if join_str == '&' else {}
163
+
164
+ schedule_parts = [part.strip() for part in schedule.split(join_str)]
165
+ triggers = []
166
+
167
+ has_seconds = 'second' in schedule
168
+ has_minutes = 'minute' in schedule
169
+
170
+ for schedule_part in schedule_parts:
171
+
172
+ ### Intervals must begin with 'every' (after alias substitution).
173
+ if schedule_part.lower().startswith('every '):
174
+ schedule_num_str, schedule_unit = (
175
+ schedule_part[len('every '):].split(' ', maxsplit=1)
176
+ )
177
+ schedule_unit = schedule_unit.rstrip('s') + 's'
178
+ if schedule_unit not in INTERVAL_UNITS:
179
+ raise ValueError(
180
+ f"Invalid interval '{schedule_unit}'.\n"
181
+ + f" Accepted values are {items_str(INTERVAL_UNITS)}."
182
+ )
183
+
184
+ schedule_num = (
185
+ int(schedule_num_str)
186
+ if is_int(schedule_num_str)
187
+ else float(schedule_num_str)
188
+ )
189
+
190
+ trigger = (
191
+ apscheduler_triggers_interval.IntervalTrigger(
192
+ **{
193
+ schedule_unit: schedule_num,
194
+ 'start_time': starting_ts,
195
+ }
196
+ )
197
+ if schedule_unit not in ('months', 'years') else (
198
+ apscheduler_triggers_calendarinterval.CalendarIntervalTrigger(
199
+ **{
200
+ schedule_unit: schedule_num,
201
+ 'start_date': starting_ts,
202
+ 'timezone': starting_ts.tzinfo,
203
+ }
204
+ )
205
+ )
206
+ )
207
+
208
+ ### Determine whether this is a pure cron string or a cron subset (e.g. 'may-aug')_.
209
+ else:
210
+ first_three_prefix = schedule_part[:3].lower()
211
+ first_four_prefix = schedule_part[:4].lower()
212
+ cron_kw = {}
213
+ if first_three_prefix in CRON_DAYS_OF_WEEK:
214
+ cron_kw['day_of_week'] = schedule_part
215
+ elif first_three_prefix in CRON_MONTHS:
216
+ cron_kw['month'] = schedule_part
217
+ elif is_int(first_four_prefix) and len(first_four_prefix) == 4:
218
+ cron_kw['year'] = int(first_four_prefix)
219
+ trigger = (
220
+ apscheduler_triggers_cron.CronTrigger(
221
+ **{
222
+ **cron_kw,
223
+ 'hour': '*',
224
+ 'minute': '*' if has_minutes else starting_ts.minute,
225
+ 'second': '*' if has_seconds else starting_ts.second,
226
+ 'start_time': starting_ts,
227
+ 'timezone': starting_ts.tzinfo,
228
+ }
229
+ )
230
+ if cron_kw
231
+ else apscheduler_triggers_cron.CronTrigger.from_crontab(
232
+ schedule_part,
233
+ timezone = starting_ts.tzinfo,
234
+ )
235
+ )
236
+ ### Explicitly set the `start_time` after building with `from_crontab`.
237
+ if trigger.start_time != starting_ts:
238
+ trigger.start_time = starting_ts
239
+
240
+ triggers.append(trigger)
241
+
242
+ return (
243
+ join_trigger(triggers, **join_kwargs)
244
+ if len(triggers) != 1
245
+ else triggers[0]
246
+ )
247
+
248
+
249
+ def parse_start_time(schedule: str, now: Optional[datetime] = None) -> datetime:
250
+ """
251
+ Return the datetime to use for the given schedule string.
252
+
253
+ Parameters
254
+ ----------
255
+ schedule: str
256
+ The schedule frequency to be parsed into a starting datetime.
257
+
258
+ now: Optional[datetime], default None
259
+ If provided, use this value as a default if no start time is explicitly stated.
260
+
261
+ Returns
262
+ -------
263
+ A `datetime` object, either `now` or the datetime embedded in the schedule string.
264
+
265
+ Examples
266
+ --------
267
+ >>> parse_start_time('daily starting 2024-01-01')
268
+ datetime.datetime(2024, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)
269
+ >>> parse_start_time('monthly starting 1st')
270
+ datetime.datetime(2024, 5, 1, 0, 0, tzinfo=datetime.timezone.utc)
271
+ >>> parse_start_time('hourly starting 00:30')
272
+ datetime.datetime(2024, 5, 13, 0, 30, tzinfo=datetime.timezone.utc)
273
+ """
274
+ from meerschaum.utils.misc import round_time
275
+ from meerschaum.utils.warnings import error, warn
276
+ dateutil_parser = mrsm.attempt_import('dateutil.parser')
277
+ starting_parts = schedule.split(STARTING_KEYWORD)
278
+ starting_str = ('now' if len(starting_parts) == 1 else starting_parts[-1]).strip()
279
+ now = now or round_time(datetime.now(timezone.utc), timedelta(minutes=1))
280
+ try:
281
+ if starting_str == 'now':
282
+ starting_ts = now
283
+ elif 'tomorrow' in starting_str or 'today' in starting_str:
284
+ today = round_time(now, timedelta(days=1))
285
+ tomorrow = today + timedelta(days=1)
286
+ is_tomorrow = 'tomorrow' in starting_str
287
+ time_str = starting_str.replace('tomorrow', '').replace('today', '').strip()
288
+ time_ts = dateutil_parser.parse(time_str) if time_str else today
289
+ starting_ts = (
290
+ (tomorrow if is_tomorrow else today)
291
+ + timedelta(hours=time_ts.hour)
292
+ + timedelta(minutes=time_ts.minute)
293
+ )
294
+ else:
295
+ starting_ts = 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
305
+
65
306
 
307
+ async def _stop_scheduler():
308
+ if _scheduler is None:
309
+ return
310
+ await _scheduler.stop()
311
+ await _scheduler.wait_until_stopped()
@@ -10,6 +10,7 @@ from __future__ import annotations
10
10
  from meerschaum.utils.typing import Optional
11
11
 
12
12
  import threading
13
+ import traceback
13
14
  Lock = threading.Lock
14
15
  RLock = threading.RLock
15
16
  Event = threading.Event
@@ -86,7 +86,7 @@ PipesDict = Dict[
86
86
  ]
87
87
  ]
88
88
  ]
89
- WebState = Dict[str, str]
89
+ WebState = Dict[str, Union[str, Dict[str, str]]]
90
90
 
91
91
  def is_success_tuple(x: Any) -> bool:
92
92
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: meerschaum
3
- Version: 2.1.7
3
+ Version: 2.2.0
4
4
  Summary: Sync Time-Series Pipes with Meerschaum
5
5
  Home-page: https://meerschaum.io
6
6
  Author: Bennett Meares
@@ -31,47 +31,19 @@ Requires-Python: >=3.8
31
31
  Description-Content-Type: text/markdown
32
32
  License-File: LICENSE
33
33
  License-File: NOTICE
34
- Provides-Extra: _drivers
35
- Requires-Dist: pyodbc >=4.0.30 ; extra == '_drivers'
36
- Requires-Dist: cx-Oracle >=8.3.0 ; extra == '_drivers'
37
- Provides-Extra: _required
38
- Requires-Dist: wheel >=0.34.2 ; extra == '_required'
39
- Requires-Dist: setuptools >=63.3.0 ; extra == '_required'
40
- Requires-Dist: PyYAML >=5.3.1 ; extra == '_required'
41
- Requires-Dist: pip >=22.0.4 ; extra == '_required'
42
- Requires-Dist: update-checker >=0.18.0 ; extra == '_required'
43
- Requires-Dist: semver >=3.0.0 ; extra == '_required'
44
- Requires-Dist: pathspec >=0.9.0 ; extra == '_required'
45
- Requires-Dist: python-dateutil >=2.7.5 ; extra == '_required'
46
- Requires-Dist: requests >=2.23.0 ; extra == '_required'
47
- Requires-Dist: binaryornot >=0.4.4 ; extra == '_required'
48
- Requires-Dist: pyvim >=3.0.2 ; extra == '_required'
49
- Requires-Dist: aiofiles >=0.6.0 ; extra == '_required'
50
- Requires-Dist: packaging >=21.3.0 ; extra == '_required'
51
- Requires-Dist: prompt-toolkit >=3.0.39 ; extra == '_required'
52
- Requires-Dist: more-itertools >=8.7.0 ; extra == '_required'
53
- Requires-Dist: python-daemon >=0.2.3 ; extra == '_required'
54
- Requires-Dist: fasteners >=0.18.0 ; extra == '_required'
55
- Requires-Dist: psutil >=5.8.0 ; extra == '_required'
56
- Requires-Dist: watchgod >=0.7.0 ; extra == '_required'
57
- Requires-Dist: dill >=0.3.3 ; extra == '_required'
58
- Requires-Dist: virtualenv >=20.1.0 ; extra == '_required'
59
- Requires-Dist: rocketry >=2.5.1 ; extra == '_required'
60
34
  Provides-Extra: api
61
- Requires-Dist: uvicorn[standard] >=0.22.0 ; extra == 'api'
62
- Requires-Dist: gunicorn >=20.1.0 ; extra == 'api'
35
+ Requires-Dist: uvicorn[standard] >=0.29.0 ; extra == 'api'
36
+ Requires-Dist: gunicorn >=22.0.0 ; extra == 'api'
63
37
  Requires-Dist: python-dotenv >=0.20.0 ; extra == 'api'
64
38
  Requires-Dist: websockets >=11.0.3 ; extra == 'api'
65
- Requires-Dist: fastapi >=0.100.0 ; extra == 'api'
66
- Requires-Dist: passlib >=1.7.4 ; extra == 'api'
39
+ Requires-Dist: fastapi >=0.111.0 ; extra == 'api'
67
40
  Requires-Dist: fastapi-login >=1.7.2 ; extra == 'api'
68
- Requires-Dist: python-multipart >=0.0.5 ; extra == 'api'
69
- Requires-Dist: pydantic <2.0.0 ; extra == 'api'
41
+ Requires-Dist: python-multipart >=0.0.9 ; extra == 'api'
70
42
  Requires-Dist: httpx >=0.24.1 ; extra == 'api'
71
43
  Requires-Dist: numpy >=1.18.5 ; extra == 'api'
72
44
  Requires-Dist: pandas[parquet] >=2.0.1 ; extra == 'api'
73
- Requires-Dist: pyarrow >=7.0.0 ; extra == 'api'
74
- Requires-Dist: dask >=2023.5.0 ; extra == 'api'
45
+ Requires-Dist: pyarrow >=16.1.0 ; extra == 'api'
46
+ Requires-Dist: dask[dataframe] >=2024.5.1 ; extra == 'api'
75
47
  Requires-Dist: pytz ; extra == 'api'
76
48
  Requires-Dist: joblib >=0.17.0 ; extra == 'api'
77
49
  Requires-Dist: SQLAlchemy >=2.0.5 ; extra == 'api'
@@ -79,11 +51,11 @@ Requires-Dist: databases >=0.4.0 ; extra == 'api'
79
51
  Requires-Dist: aiosqlite >=0.16.0 ; extra == 'api'
80
52
  Requires-Dist: asyncpg >=0.21.0 ; extra == 'api'
81
53
  Requires-Dist: cryptography >=38.0.1 ; extra == 'api'
82
- Requires-Dist: psycopg2-binary >=2.8.6 ; extra == 'api'
54
+ Requires-Dist: psycopg[binary] >=3.1.18 ; extra == 'api'
83
55
  Requires-Dist: PyMySQL >=0.9.0 ; extra == 'api'
84
56
  Requires-Dist: aiomysql >=0.0.21 ; extra == 'api'
85
57
  Requires-Dist: sqlalchemy-cockroachdb >=2.0.0 ; extra == 'api'
86
- Requires-Dist: duckdb >=0.9.0 ; extra == 'api'
58
+ Requires-Dist: duckdb <0.10.3 ; extra == 'api'
87
59
  Requires-Dist: duckdb-engine >=0.9.2 ; extra == 'api'
88
60
  Requires-Dist: wheel >=0.34.2 ; extra == 'api'
89
61
  Requires-Dist: setuptools >=63.3.0 ; extra == 'api'
@@ -103,10 +75,10 @@ Requires-Dist: more-itertools >=8.7.0 ; extra == 'api'
103
75
  Requires-Dist: python-daemon >=0.2.3 ; extra == 'api'
104
76
  Requires-Dist: fasteners >=0.18.0 ; extra == 'api'
105
77
  Requires-Dist: psutil >=5.8.0 ; extra == 'api'
106
- Requires-Dist: watchgod >=0.7.0 ; extra == 'api'
78
+ Requires-Dist: watchfiles >=0.21.0 ; extra == 'api'
107
79
  Requires-Dist: dill >=0.3.3 ; extra == 'api'
108
80
  Requires-Dist: virtualenv >=20.1.0 ; extra == 'api'
109
- Requires-Dist: rocketry >=2.5.1 ; extra == 'api'
81
+ Requires-Dist: APScheduler >=4.0.0a5 ; extra == 'api'
110
82
  Requires-Dist: pprintpp >=0.4.0 ; extra == 'api'
111
83
  Requires-Dist: asciitree >=0.3.3 ; extra == 'api'
112
84
  Requires-Dist: typing-extensions >=4.7.1 ; extra == 'api'
@@ -124,14 +96,37 @@ Requires-Dist: dash-daq >=0.5.0 ; extra == 'api'
124
96
  Requires-Dist: terminado >=0.12.1 ; extra == 'api'
125
97
  Requires-Dist: tornado >=6.1.0 ; extra == 'api'
126
98
  Provides-Extra: build
127
- Requires-Dist: cx-Freeze >=6.5.1 ; extra == 'build'
128
- Requires-Dist: pyinstaller >=5.0.0-dev0 ; extra == 'build'
99
+ Requires-Dist: cx-Freeze >=7.0.0 ; extra == 'build'
100
+ Requires-Dist: pyinstaller >6.6.0 ; extra == 'build'
129
101
  Provides-Extra: cli
130
102
  Requires-Dist: pgcli >=3.1.0 ; extra == 'cli'
131
103
  Requires-Dist: mycli >=1.23.2 ; extra == 'cli'
132
104
  Requires-Dist: litecli >=1.5.0 ; extra == 'cli'
133
105
  Requires-Dist: mssql-cli >=1.0.0 ; extra == 'cli'
134
106
  Requires-Dist: gadwall >=0.2.0 ; extra == 'cli'
107
+ Provides-Extra: core
108
+ Requires-Dist: wheel >=0.34.2 ; extra == 'core'
109
+ Requires-Dist: setuptools >=63.3.0 ; extra == 'core'
110
+ Requires-Dist: PyYAML >=5.3.1 ; extra == 'core'
111
+ Requires-Dist: pip >=22.0.4 ; extra == 'core'
112
+ Requires-Dist: update-checker >=0.18.0 ; extra == 'core'
113
+ Requires-Dist: semver >=3.0.0 ; extra == 'core'
114
+ Requires-Dist: pathspec >=0.9.0 ; extra == 'core'
115
+ Requires-Dist: python-dateutil >=2.7.5 ; extra == 'core'
116
+ Requires-Dist: requests >=2.23.0 ; extra == 'core'
117
+ Requires-Dist: binaryornot >=0.4.4 ; extra == 'core'
118
+ Requires-Dist: pyvim >=3.0.2 ; extra == 'core'
119
+ Requires-Dist: aiofiles >=0.6.0 ; extra == 'core'
120
+ Requires-Dist: packaging >=21.3.0 ; extra == 'core'
121
+ Requires-Dist: prompt-toolkit >=3.0.39 ; extra == 'core'
122
+ Requires-Dist: more-itertools >=8.7.0 ; extra == 'core'
123
+ Requires-Dist: python-daemon >=0.2.3 ; extra == 'core'
124
+ Requires-Dist: fasteners >=0.18.0 ; extra == 'core'
125
+ Requires-Dist: psutil >=5.8.0 ; extra == 'core'
126
+ Requires-Dist: watchfiles >=0.21.0 ; extra == 'core'
127
+ Requires-Dist: dill >=0.3.3 ; extra == 'core'
128
+ Requires-Dist: virtualenv >=20.1.0 ; extra == 'core'
129
+ Requires-Dist: APScheduler >=4.0.0a5 ; extra == 'core'
135
130
  Provides-Extra: dash
136
131
  Requires-Dist: Flask-Compress >=1.10.1 ; extra == 'dash'
137
132
  Requires-Dist: dash >=2.6.2 ; extra == 'dash'
@@ -149,6 +144,7 @@ Requires-Dist: mypy >=0.812.0 ; extra == 'dev-tools'
149
144
  Requires-Dist: pytest >=6.2.2 ; extra == 'dev-tools'
150
145
  Requires-Dist: pytest-xdist >=3.2.1 ; extra == 'dev-tools'
151
146
  Requires-Dist: heartrate >=0.2.1 ; extra == 'dev-tools'
147
+ Requires-Dist: build >=1.2.1 ; extra == 'dev-tools'
152
148
  Provides-Extra: docs
153
149
  Requires-Dist: mkdocs >=1.1.2 ; extra == 'docs'
154
150
  Requires-Dist: mkdocs-material >=6.2.5 ; extra == 'docs'
@@ -161,12 +157,15 @@ Requires-Dist: mkdocs-redirects >=1.0.4 ; extra == 'docs'
161
157
  Requires-Dist: jinja2 ==3.0.3 ; extra == 'docs'
162
158
  Provides-Extra: drivers
163
159
  Requires-Dist: cryptography >=38.0.1 ; extra == 'drivers'
164
- Requires-Dist: psycopg2-binary >=2.8.6 ; extra == 'drivers'
160
+ Requires-Dist: psycopg[binary] >=3.1.18 ; extra == 'drivers'
165
161
  Requires-Dist: PyMySQL >=0.9.0 ; extra == 'drivers'
166
162
  Requires-Dist: aiomysql >=0.0.21 ; extra == 'drivers'
167
163
  Requires-Dist: sqlalchemy-cockroachdb >=2.0.0 ; extra == 'drivers'
168
- Requires-Dist: duckdb >=0.9.0 ; extra == 'drivers'
164
+ Requires-Dist: duckdb <0.10.3 ; extra == 'drivers'
169
165
  Requires-Dist: duckdb-engine >=0.9.2 ; extra == 'drivers'
166
+ Provides-Extra: drivers-extras
167
+ Requires-Dist: pyodbc >=4.0.30 ; extra == 'drivers-extras'
168
+ Requires-Dist: cx-Oracle >=8.3.0 ; extra == 'drivers-extras'
170
169
  Provides-Extra: extras
171
170
  Requires-Dist: cmd2 >=1.4.0 ; extra == 'extras'
172
171
  Requires-Dist: ruamel.yaml >=0.16.12 ; extra == 'extras'
@@ -209,24 +208,24 @@ Requires-Dist: more-itertools >=8.7.0 ; extra == 'full'
209
208
  Requires-Dist: python-daemon >=0.2.3 ; extra == 'full'
210
209
  Requires-Dist: fasteners >=0.18.0 ; extra == 'full'
211
210
  Requires-Dist: psutil >=5.8.0 ; extra == 'full'
212
- Requires-Dist: watchgod >=0.7.0 ; extra == 'full'
211
+ Requires-Dist: watchfiles >=0.21.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.0a5 ; extra == 'full'
216
215
  Requires-Dist: cryptography >=38.0.1 ; extra == 'full'
217
- Requires-Dist: psycopg2-binary >=2.8.6 ; extra == 'full'
216
+ Requires-Dist: psycopg[binary] >=3.1.18 ; extra == 'full'
218
217
  Requires-Dist: PyMySQL >=0.9.0 ; extra == 'full'
219
218
  Requires-Dist: aiomysql >=0.0.21 ; extra == 'full'
220
219
  Requires-Dist: sqlalchemy-cockroachdb >=2.0.0 ; extra == 'full'
221
- Requires-Dist: duckdb >=0.9.0 ; extra == 'full'
220
+ Requires-Dist: duckdb <0.10.3 ; extra == 'full'
222
221
  Requires-Dist: duckdb-engine >=0.9.2 ; extra == 'full'
223
222
  Requires-Dist: toga >=0.3.0-dev29 ; extra == 'full'
224
223
  Requires-Dist: pywebview >=3.6.3 ; extra == 'full'
225
224
  Requires-Dist: pycparser >=2.21.0 ; extra == 'full'
226
225
  Requires-Dist: numpy >=1.18.5 ; extra == 'full'
227
226
  Requires-Dist: pandas[parquet] >=2.0.1 ; extra == 'full'
228
- Requires-Dist: pyarrow >=7.0.0 ; extra == 'full'
229
- Requires-Dist: dask >=2023.5.0 ; extra == 'full'
227
+ Requires-Dist: pyarrow >=16.1.0 ; extra == 'full'
228
+ Requires-Dist: dask[dataframe] >=2024.5.1 ; extra == 'full'
230
229
  Requires-Dist: pytz ; extra == 'full'
231
230
  Requires-Dist: joblib >=0.17.0 ; extra == 'full'
232
231
  Requires-Dist: SQLAlchemy >=2.0.5 ; extra == 'full'
@@ -241,15 +240,13 @@ Requires-Dist: dash-extensions >=1.0.4 ; extra == 'full'
241
240
  Requires-Dist: dash-daq >=0.5.0 ; extra == 'full'
242
241
  Requires-Dist: terminado >=0.12.1 ; extra == 'full'
243
242
  Requires-Dist: tornado >=6.1.0 ; extra == 'full'
244
- Requires-Dist: uvicorn[standard] >=0.22.0 ; extra == 'full'
245
- Requires-Dist: gunicorn >=20.1.0 ; extra == 'full'
243
+ Requires-Dist: uvicorn[standard] >=0.29.0 ; extra == 'full'
244
+ Requires-Dist: gunicorn >=22.0.0 ; extra == 'full'
246
245
  Requires-Dist: python-dotenv >=0.20.0 ; extra == 'full'
247
246
  Requires-Dist: websockets >=11.0.3 ; extra == 'full'
248
- Requires-Dist: fastapi >=0.100.0 ; extra == 'full'
249
- Requires-Dist: passlib >=1.7.4 ; extra == 'full'
247
+ Requires-Dist: fastapi >=0.111.0 ; extra == 'full'
250
248
  Requires-Dist: fastapi-login >=1.7.2 ; extra == 'full'
251
- Requires-Dist: python-multipart >=0.0.5 ; extra == 'full'
252
- Requires-Dist: pydantic <2.0.0 ; extra == 'full'
249
+ Requires-Dist: python-multipart >=0.0.9 ; extra == 'full'
253
250
  Requires-Dist: httpx >=0.24.1 ; extra == 'full'
254
251
  Provides-Extra: gui
255
252
  Requires-Dist: toga >=0.3.0-dev29 ; extra == 'gui'
@@ -261,8 +258,8 @@ Provides-Extra: setup
261
258
  Provides-Extra: sql
262
259
  Requires-Dist: numpy >=1.18.5 ; extra == 'sql'
263
260
  Requires-Dist: pandas[parquet] >=2.0.1 ; extra == 'sql'
264
- Requires-Dist: pyarrow >=7.0.0 ; extra == 'sql'
265
- Requires-Dist: dask >=2023.5.0 ; extra == 'sql'
261
+ Requires-Dist: pyarrow >=16.1.0 ; extra == 'sql'
262
+ Requires-Dist: dask[dataframe] >=2024.5.1 ; extra == 'sql'
266
263
  Requires-Dist: pytz ; extra == 'sql'
267
264
  Requires-Dist: joblib >=0.17.0 ; extra == 'sql'
268
265
  Requires-Dist: SQLAlchemy >=2.0.5 ; extra == 'sql'
@@ -270,11 +267,11 @@ Requires-Dist: databases >=0.4.0 ; extra == 'sql'
270
267
  Requires-Dist: aiosqlite >=0.16.0 ; extra == 'sql'
271
268
  Requires-Dist: asyncpg >=0.21.0 ; extra == 'sql'
272
269
  Requires-Dist: cryptography >=38.0.1 ; extra == 'sql'
273
- Requires-Dist: psycopg2-binary >=2.8.6 ; extra == 'sql'
270
+ Requires-Dist: psycopg[binary] >=3.1.18 ; extra == 'sql'
274
271
  Requires-Dist: PyMySQL >=0.9.0 ; extra == 'sql'
275
272
  Requires-Dist: aiomysql >=0.0.21 ; extra == 'sql'
276
273
  Requires-Dist: sqlalchemy-cockroachdb >=2.0.0 ; extra == 'sql'
277
- Requires-Dist: duckdb >=0.9.0 ; extra == 'sql'
274
+ Requires-Dist: duckdb <0.10.3 ; extra == 'sql'
278
275
  Requires-Dist: duckdb-engine >=0.9.2 ; extra == 'sql'
279
276
  Requires-Dist: wheel >=0.34.2 ; extra == 'sql'
280
277
  Requires-Dist: setuptools >=63.3.0 ; extra == 'sql'
@@ -294,12 +291,12 @@ Requires-Dist: more-itertools >=8.7.0 ; extra == 'sql'
294
291
  Requires-Dist: python-daemon >=0.2.3 ; extra == 'sql'
295
292
  Requires-Dist: fasteners >=0.18.0 ; extra == 'sql'
296
293
  Requires-Dist: psutil >=5.8.0 ; extra == 'sql'
297
- Requires-Dist: watchgod >=0.7.0 ; extra == 'sql'
294
+ Requires-Dist: watchfiles >=0.21.0 ; extra == 'sql'
298
295
  Requires-Dist: dill >=0.3.3 ; extra == 'sql'
299
296
  Requires-Dist: virtualenv >=20.1.0 ; extra == 'sql'
300
- Requires-Dist: rocketry >=2.5.1 ; extra == 'sql'
297
+ Requires-Dist: APScheduler >=4.0.0a5 ; extra == 'sql'
301
298
  Provides-Extra: stack
302
- Requires-Dist: docker-compose >=1.27.4 ; extra == 'stack'
299
+ Requires-Dist: docker-compose >=1.29.2 ; extra == 'stack'
303
300
 
304
301
  <img src="https://meerschaum.io/assets/banner_1920x320.png" alt="Meerschaum banner" style="width: 100%"/>
305
302