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
@@ -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(self, potential_new_len: int = 0) -> '_io.TextUIWrapper':
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
- daemon.daemon.redirect_stream(sys.stdout, self._current_file_obj)
251
- daemon.daemon.redirect_stream(sys.stderr, self._current_file_obj)
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 is None
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.refresh_files(potential_new_len=len(data))
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
- sys.stdout.flush()
538
- sys.stderr.flush()
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
- if ldtype == rdtype:
92
- return True
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) -> 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
- f"Failed to install `setuptools` and `wheel` for virtual environment '{venv}'.",
823
- color=False,
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
- success = run_python_package(
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
- ) == 0
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
- '_required': {
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
- 'watchgod' : 'watchgod>=0.7.0',
52
+ 'watchfiles' : 'watchfiles>=0.21.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.0a5',
56
56
  },
57
57
  'drivers': {
58
58
  'cryptography' : 'cryptography>=38.0.1',
59
- 'psycopg2' : 'psycopg2-binary>=2.8.6',
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>=0.9.0',
63
+ 'duckdb' : 'duckdb<0.10.3',
64
64
  'duckdb_engine' : 'duckdb-engine>=0.9.2',
65
65
  },
66
- '_drivers': {
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.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',
@@ -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>=7.0.0',
123
- 'dask' : 'dask>=2023.5.0',
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['_required'])
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.22.0',
145
- 'gunicorn' : 'gunicorn>=20.1.0',
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.100.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.5',
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', '_drivers'}
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():
@@ -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
- if line_callback is not None:
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()