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.
- meerschaum/__main__.py +1 -1
- meerschaum/_internal/arguments/_parser.py +3 -0
- meerschaum/_internal/entry.py +3 -2
- meerschaum/actions/install.py +7 -3
- meerschaum/actions/show.py +128 -42
- meerschaum/actions/sync.py +7 -3
- meerschaum/api/__init__.py +24 -14
- meerschaum/api/_oauth2.py +4 -4
- meerschaum/api/dash/callbacks/dashboard.py +93 -23
- meerschaum/api/dash/callbacks/jobs.py +55 -3
- meerschaum/api/dash/jobs.py +34 -8
- meerschaum/api/dash/keys.py +1 -1
- meerschaum/api/dash/pages/dashboard.py +14 -4
- meerschaum/api/dash/pipes.py +137 -26
- meerschaum/api/dash/plugins.py +25 -9
- meerschaum/api/resources/static/js/xterm.js +1 -1
- meerschaum/api/resources/templates/termpage.html +3 -0
- meerschaum/api/routes/_login.py +5 -4
- meerschaum/api/routes/_plugins.py +6 -3
- meerschaum/config/_dash.py +11 -0
- meerschaum/config/_default.py +3 -1
- meerschaum/config/_jobs.py +13 -4
- meerschaum/config/_paths.py +2 -0
- meerschaum/config/_sync.py +2 -3
- meerschaum/config/_version.py +1 -1
- meerschaum/config/stack/__init__.py +6 -7
- meerschaum/config/stack/grafana/__init__.py +1 -1
- meerschaum/config/static/__init__.py +4 -1
- meerschaum/connectors/__init__.py +2 -0
- meerschaum/connectors/api/_plugins.py +2 -1
- meerschaum/connectors/sql/SQLConnector.py +4 -2
- meerschaum/connectors/sql/_create_engine.py +9 -9
- meerschaum/connectors/sql/_instance.py +3 -1
- meerschaum/connectors/sql/_pipes.py +54 -38
- meerschaum/connectors/sql/_plugins.py +0 -2
- meerschaum/connectors/sql/_sql.py +7 -9
- meerschaum/core/User/_User.py +158 -16
- meerschaum/core/User/__init__.py +1 -1
- meerschaum/plugins/_Plugin.py +12 -3
- meerschaum/plugins/__init__.py +23 -1
- meerschaum/utils/daemon/Daemon.py +89 -36
- meerschaum/utils/daemon/FileDescriptorInterceptor.py +140 -0
- meerschaum/utils/daemon/RotatingFile.py +130 -14
- meerschaum/utils/daemon/__init__.py +3 -0
- meerschaum/utils/dtypes/__init__.py +9 -5
- meerschaum/utils/packages/__init__.py +21 -5
- meerschaum/utils/packages/_packages.py +18 -20
- meerschaum/utils/process.py +13 -10
- meerschaum/utils/schedule.py +276 -30
- meerschaum/utils/threading.py +1 -0
- meerschaum/utils/typing.py +1 -1
- {meerschaum-2.1.7.dist-info → meerschaum-2.2.0.dist-info}/METADATA +59 -62
- {meerschaum-2.1.7.dist-info → meerschaum-2.2.0.dist-info}/RECORD +59 -57
- {meerschaum-2.1.7.dist-info → meerschaum-2.2.0.dist-info}/WHEEL +1 -1
- {meerschaum-2.1.7.dist-info → meerschaum-2.2.0.dist-info}/LICENSE +0 -0
- {meerschaum-2.1.7.dist-info → meerschaum-2.2.0.dist-info}/NOTICE +0 -0
- {meerschaum-2.1.7.dist-info → meerschaum-2.2.0.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.1.7.dist-info → meerschaum-2.2.0.dist-info}/top_level.txt +0 -0
- {meerschaum-2.1.7.dist-info → meerschaum-2.2.0.dist-info}/zip-safe +0 -0
@@ -13,9 +13,13 @@ import pathlib
|
|
13
13
|
import traceback
|
14
14
|
import sys
|
15
15
|
import atexit
|
16
|
+
from datetime import datetime, timezone, timedelta
|
16
17
|
from typing import List, Union, Optional, Tuple
|
17
18
|
from meerschaum.config import get_config
|
18
19
|
from meerschaum.utils.warnings import warn
|
20
|
+
from meerschaum.utils.misc import round_time
|
21
|
+
from meerschaum.utils.daemon.FileDescriptorInterceptor import FileDescriptorInterceptor
|
22
|
+
from meerschaum.utils.threading import Thread
|
19
23
|
import meerschaum as mrsm
|
20
24
|
daemon = mrsm.attempt_import('daemon')
|
21
25
|
|
@@ -33,6 +37,8 @@ class RotatingFile(io.IOBase):
|
|
33
37
|
num_files_to_keep: Optional[int] = None,
|
34
38
|
max_file_size: Optional[int] = None,
|
35
39
|
redirect_streams: bool = False,
|
40
|
+
write_timestamps: bool = False,
|
41
|
+
timestamp_format: str = '%Y-%m-%d %H:%M',
|
36
42
|
):
|
37
43
|
"""
|
38
44
|
Create a file-like object which manages other files.
|
@@ -54,6 +60,9 @@ class RotatingFile(io.IOBase):
|
|
54
60
|
|
55
61
|
NOTE: Only set this to `True` if you are entering into a daemon context.
|
56
62
|
Doing so will redirect `sys.stdout` and `sys.stderr` into the log files.
|
63
|
+
|
64
|
+
write_timestamps: bool, default False
|
65
|
+
If `True`, prepend the current UTC timestamp to each line of the file.
|
57
66
|
"""
|
58
67
|
self.file_path = pathlib.Path(file_path)
|
59
68
|
if num_files_to_keep is None:
|
@@ -68,6 +77,8 @@ class RotatingFile(io.IOBase):
|
|
68
77
|
self.num_files_to_keep = num_files_to_keep
|
69
78
|
self.max_file_size = max_file_size
|
70
79
|
self.redirect_streams = redirect_streams
|
80
|
+
self.write_timestamps = write_timestamps
|
81
|
+
self.timestamp_format = timestamp_format
|
71
82
|
self.subfile_regex_pattern = re.compile(
|
72
83
|
r'^'
|
73
84
|
+ self.file_path.name
|
@@ -91,7 +102,7 @@ class RotatingFile(io.IOBase):
|
|
91
102
|
"""
|
92
103
|
Return the file descriptor for the latest subfile.
|
93
104
|
"""
|
94
|
-
self.refresh_files()
|
105
|
+
self.refresh_files(start_interception=False)
|
95
106
|
return self._current_file_obj.fileno()
|
96
107
|
|
97
108
|
|
@@ -221,7 +232,11 @@ class RotatingFile(io.IOBase):
|
|
221
232
|
]
|
222
233
|
|
223
234
|
|
224
|
-
def refresh_files(
|
235
|
+
def refresh_files(
|
236
|
+
self,
|
237
|
+
potential_new_len: int = 0,
|
238
|
+
start_interception: bool = False,
|
239
|
+
) -> '_io.TextUIWrapper':
|
225
240
|
"""
|
226
241
|
Check the state of the subfiles.
|
227
242
|
If the latest subfile is too large, create a new file and delete old ones.
|
@@ -229,6 +244,9 @@ class RotatingFile(io.IOBase):
|
|
229
244
|
Parameters
|
230
245
|
----------
|
231
246
|
potential_new_len: int, default 0
|
247
|
+
|
248
|
+
start_interception: bool, default False
|
249
|
+
If `True`, kick off the file interception threads.
|
232
250
|
"""
|
233
251
|
self.flush()
|
234
252
|
|
@@ -247,8 +265,15 @@ class RotatingFile(io.IOBase):
|
|
247
265
|
if is_first_run_with_logs or lost_latest_handle:
|
248
266
|
self._current_file_obj = open(latest_subfile_path, 'a+', encoding='utf-8')
|
249
267
|
if self.redirect_streams:
|
250
|
-
|
251
|
-
|
268
|
+
try:
|
269
|
+
daemon.daemon.redirect_stream(sys.stdout, self._current_file_obj)
|
270
|
+
daemon.daemon.redirect_stream(sys.stderr, self._current_file_obj)
|
271
|
+
except OSError as e:
|
272
|
+
warn(
|
273
|
+
f"Encountered an issue when redirecting streams:\n{traceback.format_exc()}"
|
274
|
+
)
|
275
|
+
if start_interception and self.write_timestamps:
|
276
|
+
self.start_log_fd_interception()
|
252
277
|
|
253
278
|
create_new_file = (
|
254
279
|
(latest_subfile_index == -1)
|
@@ -276,9 +301,10 @@ class RotatingFile(io.IOBase):
|
|
276
301
|
|
277
302
|
### Sanity check in case writing somehow fails.
|
278
303
|
if self._previous_file_obj is self._current_file_obj:
|
279
|
-
self._previous_file_obj
|
304
|
+
self._previous_file_obj = None
|
280
305
|
|
281
306
|
self.delete(unused_only=True)
|
307
|
+
|
282
308
|
return self._current_file_obj
|
283
309
|
|
284
310
|
|
@@ -291,6 +317,7 @@ class RotatingFile(io.IOBase):
|
|
291
317
|
unused_only: bool, default False
|
292
318
|
If `True`, only close file descriptors not currently in use.
|
293
319
|
"""
|
320
|
+
self.stop_log_fd_interception(unused_only=unused_only)
|
294
321
|
subfile_indices = sorted(self.subfile_objects.keys())
|
295
322
|
for subfile_index in subfile_indices:
|
296
323
|
subfile_object = self.subfile_objects[subfile_index]
|
@@ -298,19 +325,26 @@ class RotatingFile(io.IOBase):
|
|
298
325
|
continue
|
299
326
|
try:
|
300
327
|
if not subfile_object.closed:
|
301
|
-
# subfile_object.flush()
|
302
328
|
subfile_object.close()
|
303
|
-
_ = self.subfile_objects.pop(subfile_index, None)
|
304
|
-
if self.redirect_streams:
|
305
|
-
_ = self._redirected_subfile_objects.pop(subfile_index, None)
|
306
329
|
except Exception as e:
|
307
330
|
warn(f"Failed to close an open subfile:\n{traceback.format_exc()}")
|
308
331
|
|
332
|
+
_ = self.subfile_objects.pop(subfile_index, None)
|
333
|
+
if self.redirect_streams:
|
334
|
+
_ = self._redirected_subfile_objects.pop(subfile_index, None)
|
335
|
+
|
309
336
|
if not unused_only:
|
310
337
|
self._previous_file_obj = None
|
311
338
|
self._current_file_obj = None
|
312
339
|
|
313
340
|
|
341
|
+
def get_timestamp_prefix_str(self) -> str:
|
342
|
+
"""
|
343
|
+
Return the current minute prefixm string.
|
344
|
+
"""
|
345
|
+
return datetime.now(timezone.utc).strftime(self.timestamp_format) + ' | '
|
346
|
+
|
347
|
+
|
314
348
|
def write(self, data: str) -> None:
|
315
349
|
"""
|
316
350
|
Write the given text into the latest subfile.
|
@@ -325,9 +359,18 @@ class RotatingFile(io.IOBase):
|
|
325
359
|
if isinstance(data, bytes):
|
326
360
|
data = data.decode('utf-8')
|
327
361
|
|
328
|
-
self.
|
362
|
+
prefix_str = self.get_timestamp_prefix_str() if self.write_timestamps else ""
|
363
|
+
suffix_str = "\n" if self.write_timestamps else ""
|
364
|
+
self.refresh_files(
|
365
|
+
potential_new_len = len(prefix_str + data + suffix_str),
|
366
|
+
start_interception = self.write_timestamps,
|
367
|
+
)
|
329
368
|
try:
|
369
|
+
if prefix_str:
|
370
|
+
self._current_file_obj.write(prefix_str)
|
330
371
|
self._current_file_obj.write(data)
|
372
|
+
if suffix_str:
|
373
|
+
self._current_file_obj.write(suffix_str)
|
331
374
|
except Exception as e:
|
332
375
|
warn(f"Failed to write to subfile:\n{traceback.format_exc()}")
|
333
376
|
self.flush()
|
@@ -471,7 +514,7 @@ class RotatingFile(io.IOBase):
|
|
471
514
|
subfile_object = self.subfile_objects[subfile_index]
|
472
515
|
for i in range(self.SEEK_BACK_ATTEMPTS):
|
473
516
|
try:
|
474
|
-
subfile_object.seek(max(seek_ix - i), 0)
|
517
|
+
subfile_object.seek(max((seek_ix - i), 0))
|
475
518
|
subfile_lines = subfile_object.readlines()
|
476
519
|
except UnicodeDecodeError:
|
477
520
|
continue
|
@@ -532,10 +575,83 @@ class RotatingFile(io.IOBase):
|
|
532
575
|
try:
|
533
576
|
subfile_object.flush()
|
534
577
|
except Exception as e:
|
535
|
-
warn(f"Failed to flush subfile:\n{traceback.format_exc()}")
|
578
|
+
warn(f"Failed to flush subfile {subfile_index}:\n{traceback.format_exc()}")
|
536
579
|
if self.redirect_streams:
|
537
|
-
|
538
|
-
|
580
|
+
try:
|
581
|
+
sys.stdout.flush()
|
582
|
+
except Exception as e:
|
583
|
+
warn(f"Failed to flush STDOUT:\n{traceback.format_exc()}")
|
584
|
+
try:
|
585
|
+
sys.stderr.flush()
|
586
|
+
except Exception as e:
|
587
|
+
warn(f"Failed to flush STDERR:\n{traceback.format_exc()}")
|
588
|
+
|
589
|
+
|
590
|
+
def start_log_fd_interception(self):
|
591
|
+
"""
|
592
|
+
Start the file descriptor monitoring threads.
|
593
|
+
"""
|
594
|
+
if not self.write_timestamps:
|
595
|
+
return
|
596
|
+
|
597
|
+
threads = self.__dict__.get('_interceptor_threads', [])
|
598
|
+
self._stdout_interceptor = FileDescriptorInterceptor(
|
599
|
+
sys.stdout.fileno(),
|
600
|
+
self.get_timestamp_prefix_str,
|
601
|
+
)
|
602
|
+
self._stderr_interceptor = FileDescriptorInterceptor(
|
603
|
+
sys.stderr.fileno(),
|
604
|
+
self.get_timestamp_prefix_str,
|
605
|
+
)
|
606
|
+
|
607
|
+
self._stdout_interceptor_thread = Thread(
|
608
|
+
target = self._stdout_interceptor.start_interception,
|
609
|
+
daemon = True,
|
610
|
+
)
|
611
|
+
self._stderr_interceptor_thread = Thread(
|
612
|
+
target = self._stderr_interceptor.start_interception,
|
613
|
+
daemon = True,
|
614
|
+
)
|
615
|
+
self._stdout_interceptor_thread.start()
|
616
|
+
self._stderr_interceptor_thread.start()
|
617
|
+
self._intercepting = True
|
618
|
+
|
619
|
+
if '_interceptor_threads' not in self.__dict__:
|
620
|
+
self._interceptor_threads = []
|
621
|
+
if '_interceptors' not in self.__dict__:
|
622
|
+
self._interceptors = []
|
623
|
+
self._interceptor_threads.extend([
|
624
|
+
self._stdout_interceptor_thread,
|
625
|
+
self._stderr_interceptor_thread,
|
626
|
+
])
|
627
|
+
self._interceptors.extend([
|
628
|
+
self._stdout_interceptor,
|
629
|
+
self._stderr_interceptor,
|
630
|
+
])
|
631
|
+
self.stop_log_fd_interception(unused_only=True)
|
632
|
+
|
633
|
+
|
634
|
+
def stop_log_fd_interception(self, unused_only: bool = False):
|
635
|
+
"""
|
636
|
+
Stop the file descriptor monitoring threads.
|
637
|
+
"""
|
638
|
+
if not self.write_timestamps:
|
639
|
+
return
|
640
|
+
interceptors = self.__dict__.get('_interceptors', [])
|
641
|
+
interceptor_threads = self.__dict__.get('_interceptor_threads', [])
|
642
|
+
|
643
|
+
end_ix = len(interceptors) if not unused_only else -2
|
644
|
+
|
645
|
+
for interceptor in interceptors[:end_ix]:
|
646
|
+
interceptor.stop_interception()
|
647
|
+
del interceptors[:end_ix]
|
648
|
+
|
649
|
+
for thread in interceptor_threads[:end_ix]:
|
650
|
+
try:
|
651
|
+
thread.join()
|
652
|
+
except Exception as e:
|
653
|
+
warn(f"Failed to join interceptor threads:\n{traceback.format_exc()}")
|
654
|
+
del interceptor_threads[:end_ix]
|
539
655
|
|
540
656
|
|
541
657
|
def __repr__(self) -> str:
|
@@ -12,6 +12,7 @@ from meerschaum.utils.typing import SuccessTuple, List, Optional, Callable, Any,
|
|
12
12
|
from meerschaum.config._paths import DAEMON_RESOURCES_PATH
|
13
13
|
from meerschaum.utils.daemon.Daemon import Daemon
|
14
14
|
from meerschaum.utils.daemon.RotatingFile import RotatingFile
|
15
|
+
from meerschaum.utils.daemon.FileDescriptorInterceptor import FileDescriptorInterceptor
|
15
16
|
|
16
17
|
|
17
18
|
def daemon_entry(sysargs: Optional[List[str]] = None) -> SuccessTuple:
|
@@ -63,6 +64,8 @@ def daemon_entry(sysargs: Optional[List[str]] = None) -> SuccessTuple:
|
|
63
64
|
|
64
65
|
### Only run if the kwargs equal or no actions are provided.
|
65
66
|
if existing_kwargs == _args or not _args.get('action', []):
|
67
|
+
if daemon.status == 'running':
|
68
|
+
return True, f"Daemon '{daemon}' is already running."
|
66
69
|
return daemon.run(
|
67
70
|
debug = debug,
|
68
71
|
allow_dirty_run = True,
|
@@ -6,8 +6,10 @@
|
|
6
6
|
Utility functions for working with data types.
|
7
7
|
"""
|
8
8
|
|
9
|
+
import traceback
|
9
10
|
from decimal import Decimal, Context, InvalidOperation
|
10
11
|
from meerschaum.utils.typing import Dict, Union, Any
|
12
|
+
from meerschaum.utils.warnings import warn
|
11
13
|
|
12
14
|
MRSM_PD_DTYPES: Dict[str, str] = {
|
13
15
|
'json': 'object',
|
@@ -37,9 +39,7 @@ def to_pandas_dtype(dtype: str) -> str:
|
|
37
39
|
from meerschaum.utils.dtypes.sql import get_pd_type_from_db_type
|
38
40
|
return get_pd_type_from_db_type(dtype)
|
39
41
|
|
40
|
-
import traceback
|
41
42
|
from meerschaum.utils.packages import attempt_import
|
42
|
-
from meerschaum.utils.warnings import warn
|
43
43
|
pandas = attempt_import('pandas', lazy=False)
|
44
44
|
|
45
45
|
try:
|
@@ -88,8 +88,12 @@ def are_dtypes_equal(
|
|
88
88
|
return False
|
89
89
|
return True
|
90
90
|
|
91
|
-
|
92
|
-
|
91
|
+
try:
|
92
|
+
if ldtype == rdtype:
|
93
|
+
return True
|
94
|
+
except Exception as e:
|
95
|
+
warn(f"Exception when comparing dtypes, returning False:\n{traceback.format_exc()}")
|
96
|
+
return False
|
93
97
|
|
94
98
|
### Sometimes pandas dtype objects are passed.
|
95
99
|
ldtype = str(ldtype)
|
@@ -177,7 +181,7 @@ def attempt_cast_to_numeric(value: Any) -> Any:
|
|
177
181
|
return value
|
178
182
|
|
179
183
|
|
180
|
-
def value_is_null(value: Any) ->
|
184
|
+
def value_is_null(value: Any) -> bool:
|
181
185
|
"""
|
182
186
|
Determine if a value is a null-like string.
|
183
187
|
"""
|
@@ -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
|
@@ -35,6 +35,7 @@ _locks = {
|
|
35
35
|
}
|
36
36
|
_checked_for_updates = set()
|
37
37
|
_is_installed_first_check: Dict[str, bool] = {}
|
38
|
+
_MRSM_PACKAGE_ARCHIVES_PREFIX: str = "https://meerschaum.io/files/archives/"
|
38
39
|
|
39
40
|
def get_module_path(
|
40
41
|
import_name: str,
|
@@ -640,6 +641,15 @@ def need_update(
|
|
640
641
|
|
641
642
|
### We might be depending on a prerelease.
|
642
643
|
### Sanity check that the required version is not greater than the installed version.
|
644
|
+
required_version = (
|
645
|
+
required_version.replace(_MRSM_PACKAGE_ARCHIVES_PREFIX, '')
|
646
|
+
.replace(' @ ', '').replace('wheels', '').replace('+mrsm', '').replace('/-', '')
|
647
|
+
.replace('-py3-none-any.whl', '')
|
648
|
+
)
|
649
|
+
|
650
|
+
if 'a' in required_version:
|
651
|
+
required_version = required_version.replace('a', '-dev').replace('+mrsm', '')
|
652
|
+
version = version.replace('a', '-dev').replace('+mrsm', '')
|
643
653
|
try:
|
644
654
|
return (
|
645
655
|
(not semver.Version.parse(version).match(required_version))
|
@@ -819,8 +829,11 @@ def pip_install(
|
|
819
829
|
check_wheel = False, debug = debug,
|
820
830
|
):
|
821
831
|
warn(
|
822
|
-
|
823
|
-
|
832
|
+
(
|
833
|
+
"Failed to install `setuptools` and `wheel` for virtual "
|
834
|
+
+ f"environment '{venv}'."
|
835
|
+
),
|
836
|
+
color = False,
|
824
837
|
)
|
825
838
|
|
826
839
|
if requirements_file_path is not None:
|
@@ -883,13 +896,16 @@ def pip_install(
|
|
883
896
|
f"Failed to clean up package '{_install_no_version}'.",
|
884
897
|
)
|
885
898
|
|
886
|
-
|
899
|
+
rc = run_python_package(
|
887
900
|
'pip',
|
888
901
|
_args + _packages,
|
889
902
|
venv = venv,
|
890
903
|
env = _get_pip_os_env(),
|
891
904
|
debug = debug,
|
892
|
-
)
|
905
|
+
)
|
906
|
+
if debug:
|
907
|
+
print(f"{rc=}")
|
908
|
+
success = rc == 0
|
893
909
|
|
894
910
|
msg = (
|
895
911
|
"Successfully " + ('un' if _uninstall else '') + "installed packages." if success
|
@@ -30,7 +30,7 @@ packages: Dict[str, Dict[str, str]] = {
|
|
30
30
|
'more_termcolor' : 'more-termcolor>=1.1.3',
|
31
31
|
'humanfriendly' : 'humanfriendly>=10.0.0',
|
32
32
|
},
|
33
|
-
'
|
33
|
+
'core': {
|
34
34
|
'wheel' : 'wheel>=0.34.2',
|
35
35
|
'setuptools' : 'setuptools>=63.3.0',
|
36
36
|
'yaml' : 'PyYAML>=5.3.1',
|
@@ -49,21 +49,21 @@ packages: Dict[str, Dict[str, str]] = {
|
|
49
49
|
'daemon' : 'python-daemon>=0.2.3',
|
50
50
|
'fasteners' : 'fasteners>=0.18.0',
|
51
51
|
'psutil' : 'psutil>=5.8.0',
|
52
|
-
'
|
52
|
+
'watchfiles' : 'watchfiles>=0.21.0',
|
53
53
|
'dill' : 'dill>=0.3.3',
|
54
54
|
'virtualenv' : 'virtualenv>=20.1.0',
|
55
|
-
'
|
55
|
+
'apscheduler' : 'APScheduler>=4.0.0a5',
|
56
56
|
},
|
57
57
|
'drivers': {
|
58
58
|
'cryptography' : 'cryptography>=38.0.1',
|
59
|
-
'
|
59
|
+
'psycopg' : 'psycopg[binary]>=3.1.18',
|
60
60
|
'pymysql' : 'PyMySQL>=0.9.0',
|
61
61
|
'aiomysql' : 'aiomysql>=0.0.21',
|
62
62
|
'sqlalchemy_cockroachdb' : 'sqlalchemy-cockroachdb>=2.0.0',
|
63
|
-
'duckdb' : 'duckdb
|
63
|
+
'duckdb' : 'duckdb<0.10.3',
|
64
64
|
'duckdb_engine' : 'duckdb-engine>=0.9.2',
|
65
65
|
},
|
66
|
-
'
|
66
|
+
'drivers-extras': {
|
67
67
|
'pyodbc' : 'pyodbc>=4.0.30',
|
68
68
|
'cx_Oracle' : 'cx_Oracle>=8.3.0',
|
69
69
|
},
|
@@ -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',
|
@@ -89,6 +89,7 @@ packages: Dict[str, Dict[str, str]] = {
|
|
89
89
|
'pytest' : 'pytest>=6.2.2',
|
90
90
|
'pytest_xdist' : 'pytest-xdist>=3.2.1',
|
91
91
|
'heartrate' : 'heartrate>=0.2.1',
|
92
|
+
'build' : 'build>=1.2.1',
|
92
93
|
},
|
93
94
|
'setup': {
|
94
95
|
},
|
@@ -119,8 +120,8 @@ packages: Dict[str, Dict[str, str]] = {
|
|
119
120
|
packages['sql'] = {
|
120
121
|
'numpy' : 'numpy>=1.18.5',
|
121
122
|
'pandas' : 'pandas[parquet]>=2.0.1',
|
122
|
-
'pyarrow' : 'pyarrow>=
|
123
|
-
'dask' : 'dask>=
|
123
|
+
'pyarrow' : 'pyarrow>=16.1.0',
|
124
|
+
'dask' : 'dask[dataframe]>=2024.5.1',
|
124
125
|
'pytz' : 'pytz',
|
125
126
|
'joblib' : 'joblib>=0.17.0',
|
126
127
|
'sqlalchemy' : 'SQLAlchemy>=2.0.5',
|
@@ -129,7 +130,7 @@ packages['sql'] = {
|
|
129
130
|
'asyncpg' : 'asyncpg>=0.21.0',
|
130
131
|
}
|
131
132
|
packages['sql'].update(packages['drivers'])
|
132
|
-
packages['sql'].update(packages['
|
133
|
+
packages['sql'].update(packages['core'])
|
133
134
|
packages['dash'] = {
|
134
135
|
'flask_compress' : 'Flask-Compress>=1.10.1',
|
135
136
|
'dash' : 'dash>=2.6.2',
|
@@ -141,17 +142,14 @@ packages['dash'] = {
|
|
141
142
|
'tornado' : 'tornado>=6.1.0',
|
142
143
|
}
|
143
144
|
packages['api'] = {
|
144
|
-
'uvicorn' : 'uvicorn[standard]>=0.
|
145
|
-
'gunicorn' : 'gunicorn>=
|
145
|
+
'uvicorn' : 'uvicorn[standard]>=0.29.0',
|
146
|
+
'gunicorn' : 'gunicorn>=22.0.0',
|
146
147
|
'dotenv' : 'python-dotenv>=0.20.0',
|
147
148
|
'websockets' : 'websockets>=11.0.3',
|
148
|
-
'fastapi' : 'fastapi>=0.
|
149
|
-
'passlib' : 'passlib>=1.7.4',
|
149
|
+
'fastapi' : 'fastapi>=0.111.0',
|
150
150
|
'fastapi_login' : 'fastapi-login>=1.7.2',
|
151
|
-
'multipart' : 'python-multipart>=0.0.
|
152
|
-
'pydantic' : 'pydantic<2.0.0',
|
151
|
+
'multipart' : 'python-multipart>=0.0.9',
|
153
152
|
'httpx' : 'httpx>=0.24.1',
|
154
|
-
'websockets' : 'websockets>=11.0.3',
|
155
153
|
}
|
156
154
|
packages['api'].update(packages['sql'])
|
157
155
|
packages['api'].update(packages['formatting'])
|
@@ -171,7 +169,7 @@ def get_install_names():
|
|
171
169
|
install_names[get_install_no_version(_install_name)] = _import_name
|
172
170
|
return install_names
|
173
171
|
|
174
|
-
skip_groups = {'docs', 'build', 'cli', 'dev-tools', 'portable', 'extras', 'stack', '
|
172
|
+
skip_groups = {'docs', 'build', 'cli', 'dev-tools', 'portable', 'extras', 'stack', 'drivers-extras'}
|
175
173
|
full = []
|
176
174
|
_full = {}
|
177
175
|
for group, import_names in packages.items():
|
meerschaum/utils/process.py
CHANGED
@@ -11,6 +11,7 @@ See `meerschaum.utils.pool` for multiprocessing and
|
|
11
11
|
from __future__ import annotations
|
12
12
|
import os, signal, subprocess, sys, platform
|
13
13
|
from meerschaum.utils.typing import Union, Optional, Any, Callable, Dict, Tuple
|
14
|
+
from meerschaum.config.static import STATIC_CONFIG
|
14
15
|
|
15
16
|
def run_process(
|
16
17
|
*args,
|
@@ -68,9 +69,18 @@ def run_process(
|
|
68
69
|
if platform.system() == 'Windows':
|
69
70
|
foreground = False
|
70
71
|
|
71
|
-
|
72
|
+
def print_line(line):
|
73
|
+
sys.stdout.write(line.decode('utf-8'))
|
74
|
+
sys.stdout.flush()
|
75
|
+
|
76
|
+
if capture_output or line_callback is not None:
|
77
|
+
kw['stdout'] = subprocess.PIPE
|
78
|
+
kw['stderr'] = subprocess.STDOUT
|
79
|
+
elif os.environ.get(STATIC_CONFIG['environment']['daemon_id']):
|
72
80
|
kw['stdout'] = subprocess.PIPE
|
73
81
|
kw['stderr'] = subprocess.STDOUT
|
82
|
+
if line_callback is None:
|
83
|
+
line_callback = print_line
|
74
84
|
|
75
85
|
if 'env' not in kw:
|
76
86
|
kw['env'] = os.environ
|
@@ -112,15 +122,6 @@ def run_process(
|
|
112
122
|
kw['preexec_fn'] = new_pgid
|
113
123
|
|
114
124
|
try:
|
115
|
-
# fork the child
|
116
|
-
# stdout, stderr = (
|
117
|
-
# (sys.stdout, sys.stderr) if not capture_output
|
118
|
-
# else (subprocess.PIPE, subprocess.PIPE)
|
119
|
-
# )
|
120
|
-
if capture_output:
|
121
|
-
kw['stdout'] = subprocess.PIPE
|
122
|
-
kw['stderr'] = subprocess.PIPE
|
123
|
-
|
124
125
|
child = subprocess.Popen(*args, **kw)
|
125
126
|
|
126
127
|
# we can't set the process group id from the parent since the child
|
@@ -197,6 +198,8 @@ def poll_process(
|
|
197
198
|
while proc.poll() is None:
|
198
199
|
line = proc.stdout.readline()
|
199
200
|
line_callback(line)
|
201
|
+
|
200
202
|
if timeout_seconds is not None:
|
201
203
|
watchdog_thread.cancel()
|
204
|
+
|
202
205
|
return proc.poll()
|