meerschaum 2.2.0rc2__py3-none-any.whl → 2.2.0rc3__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 (29) hide show
  1. meerschaum/api/__init__.py +16 -11
  2. meerschaum/config/_jobs.py +1 -1
  3. meerschaum/config/_paths.py +1 -0
  4. meerschaum/config/_version.py +1 -1
  5. meerschaum/config/static/__init__.py +1 -0
  6. meerschaum/connectors/__init__.py +2 -0
  7. meerschaum/connectors/sql/SQLConnector.py +4 -2
  8. meerschaum/connectors/sql/_create_engine.py +4 -4
  9. meerschaum/connectors/sql/_instance.py +3 -1
  10. meerschaum/connectors/sql/_pipes.py +53 -38
  11. meerschaum/connectors/sql/_sql.py +7 -9
  12. meerschaum/core/User/_User.py +2 -0
  13. meerschaum/plugins/__init__.py +23 -1
  14. meerschaum/utils/daemon/Daemon.py +17 -2
  15. meerschaum/utils/daemon/FileDescriptorInterceptor.py +46 -8
  16. meerschaum/utils/daemon/RotatingFile.py +4 -0
  17. meerschaum/utils/daemon/__init__.py +2 -0
  18. meerschaum/utils/packages/__init__.py +10 -4
  19. meerschaum/utils/packages/_packages.py +7 -8
  20. meerschaum/utils/process.py +13 -10
  21. meerschaum/utils/schedule.py +15 -1
  22. {meerschaum-2.2.0rc2.dist-info → meerschaum-2.2.0rc3.dist-info}/METADATA +19 -21
  23. {meerschaum-2.2.0rc2.dist-info → meerschaum-2.2.0rc3.dist-info}/RECORD +29 -29
  24. {meerschaum-2.2.0rc2.dist-info → meerschaum-2.2.0rc3.dist-info}/LICENSE +0 -0
  25. {meerschaum-2.2.0rc2.dist-info → meerschaum-2.2.0rc3.dist-info}/NOTICE +0 -0
  26. {meerschaum-2.2.0rc2.dist-info → meerschaum-2.2.0rc3.dist-info}/WHEEL +0 -0
  27. {meerschaum-2.2.0rc2.dist-info → meerschaum-2.2.0rc3.dist-info}/entry_points.txt +0 -0
  28. {meerschaum-2.2.0rc2.dist-info → meerschaum-2.2.0rc3.dist-info}/top_level.txt +0 -0
  29. {meerschaum-2.2.0rc2.dist-info → meerschaum-2.2.0rc3.dist-info}/zip-safe +0 -0
@@ -30,21 +30,26 @@ _locks = {'pipes': RLock(), 'connector': RLock(), 'uvicorn_config': RLock()}
30
30
  CHECK_UPDATE = os.environ.get(STATIC_CONFIG['environment']['runtime'], None) != 'docker'
31
31
 
32
32
  endpoints = STATIC_CONFIG['api']['endpoints']
33
- aiofiles = attempt_import('aiofiles', lazy=False, check_update=CHECK_UPDATE)
33
+
34
+ (
35
+ fastapi,
36
+ aiofiles,
37
+ starlette_responses,
38
+ multipart,
39
+ packaging_version,
40
+ ) = attempt_import(
41
+ 'fastapi',
42
+ 'aiofiles',
43
+ 'starlette.responses',
44
+ 'multipart',
45
+ 'packaging.version',
46
+ lazy = False,
47
+ check_update = CHECK_UPDATE,
48
+ )
34
49
  typing_extensions = attempt_import(
35
50
  'typing_extensions', lazy=False, check_update=CHECK_UPDATE,
36
51
  venv = None,
37
52
  )
38
- pydantic_dataclasses = attempt_import(
39
- 'pydantic.dataclasses', lazy=False, check_update=CHECK_UPDATE,
40
- )
41
- fastapi = attempt_import('fastapi', lazy=False, check_update=CHECK_UPDATE)
42
- starlette_reponses = attempt_import(
43
- 'starlette.responses', warn=False, lazy=False,
44
- check_update=CHECK_UPDATE,
45
- )
46
- python_multipart = attempt_import('multipart', lazy=False, check_update=CHECK_UPDATE)
47
- packaging_version = attempt_import('packaging.version', check_update=CHECK_UPDATE)
48
53
  from meerschaum.api._chain import check_allow_chaining, DISALLOW_CHAINING_MESSAGE
49
54
  uvicorn_config_path = API_UVICORN_RESOURCES_PATH / SERVER_ID / 'config.json'
50
55
 
@@ -18,7 +18,7 @@ default_jobs_config = {
18
18
  'max_file_size': 100_000,
19
19
  'lines_to_show': 30,
20
20
  'refresh_files_seconds': 5,
21
- 'min_buffer_len': 10,
21
+ 'min_buffer_len': 5,
22
22
  'timestamp_format': '%Y-%m-%d %H:%M',
23
23
  'follow_timestamp_format': '%H:%M',
24
24
  'colors': [
@@ -129,6 +129,7 @@ paths = {
129
129
 
130
130
  'PLUGINS_RESOURCES_PATH' : ('{INTERNAL_RESOURCES_PATH}', 'plugins'),
131
131
  'PLUGINS_INTERNAL_LOCK_PATH' : ('{INTERNAL_RESOURCES_PATH}', 'plugins.lock'),
132
+ 'PLUGINS_PACKAGES_INTERNAL_PATH' : ('{INTERNAL_RESOURCES_PATH}', 'packaged_plugins'),
132
133
  'PLUGINS_ARCHIVES_RESOURCES_PATH': ('{PLUGINS_RESOURCES_PATH}', '.archives'),
133
134
  'PLUGINS_TEMP_RESOURCES_PATH' : ('{PLUGINS_RESOURCES_PATH}', '.tmp'),
134
135
  'PLUGINS_INIT_PATH' : ('{PLUGINS_RESOURCES_PATH}', '__init__.py'),
@@ -2,4 +2,4 @@
2
2
  Specify the Meerschaum release version.
3
3
  """
4
4
 
5
- __version__ = "2.2.0rc2"
5
+ __version__ = "2.2.0rc3"
@@ -60,6 +60,7 @@ STATIC_CONFIG: Dict[str, Any] = {
60
60
  'gid': 'MRSM_GID',
61
61
  'noask': 'MRSM_NOASK',
62
62
  'id': 'MRSM_SERVER_ID',
63
+ 'daemon_id': 'MRSM_DAEMON_ID',
63
64
  'uri_regex': r'MRSM_([a-zA-Z0-9]*)_(\d*[a-zA-Z][a-zA-Z0-9-_+]*$)',
64
65
  'prefix': 'MRSM_',
65
66
  },
@@ -317,6 +317,8 @@ def load_plugin_connectors():
317
317
  from meerschaum.plugins import get_plugins, import_plugins
318
318
  to_import = []
319
319
  for plugin in get_plugins():
320
+ if plugin is None:
321
+ continue
320
322
  with open(plugin.__file__, encoding='utf-8') as f:
321
323
  text = f.read()
322
324
  if 'make_connector' in text:
@@ -128,8 +128,10 @@ class SQLConnector(Connector):
128
128
  """
129
129
  if 'uri' in kw:
130
130
  uri = kw['uri']
131
- if uri.startswith('postgres://'):
132
- uri = uri.replace('postgres://', 'postgresql://', 1)
131
+ if uri.startswith('postgres') and not uri.startswith('postgresql'):
132
+ uri = uri.replace('postgres', 'postgresql', 1)
133
+ if uri.startswith('postgresql') and not uri.startswith('postgresql+'):
134
+ uri = uri.replace('postgresql://', 'postgresql+psycopg', 1)
133
135
  if uri.startswith('timescaledb://'):
134
136
  uri = uri.replace('timescaledb://', 'postgresql://', 1)
135
137
  flavor = 'timescaledb'
@@ -28,7 +28,7 @@ default_create_engine_args = {
28
28
  }
29
29
  flavor_configs = {
30
30
  'timescaledb' : {
31
- 'engine' : 'postgresql',
31
+ 'engine' : 'postgresql+psycopg',
32
32
  'create_engine' : default_create_engine_args,
33
33
  'omit_create_engine': {'method',},
34
34
  'to_sql' : {},
@@ -38,7 +38,7 @@ flavor_configs = {
38
38
  },
39
39
  },
40
40
  'postgresql' : {
41
- 'engine' : 'postgresql',
41
+ 'engine' : 'postgresql+psycopg',
42
42
  'create_engine' : default_create_engine_args,
43
43
  'omit_create_engine': {'method',},
44
44
  'to_sql' : {},
@@ -48,7 +48,7 @@ flavor_configs = {
48
48
  },
49
49
  },
50
50
  'citus' : {
51
- 'engine' : 'postgresql',
51
+ 'engine' : 'postgresql+psycopg',
52
52
  'create_engine' : default_create_engine_args,
53
53
  'omit_create_engine': {'method',},
54
54
  'to_sql' : {},
@@ -242,7 +242,7 @@ def create_engine(
242
242
 
243
243
  ### Sometimes the timescaledb:// flavor can slip in.
244
244
  if _uri and self.flavor in ('timescaledb',) and self.flavor in _uri:
245
- engine_str = engine_str.replace(f'{self.flavor}://', 'postgresql://')
245
+ engine_str = engine_str.replace(f'{self.flavor}', 'postgresql', 1)
246
246
 
247
247
  if debug:
248
248
  dprint(
@@ -155,7 +155,9 @@ def _drop_old_temporary_tables(
155
155
  temp_tables_table = get_tables(mrsm_instance=self, create=False, debug=debug)['temp_tables']
156
156
  last_check = getattr(self, '_stale_temporary_tables_check_timestamp', 0)
157
157
  now_ts = time.perf_counter()
158
- if refresh or not last_check or (now_ts - last_check) > 60:
158
+ if not last_check:
159
+ self._stale_temporary_tables_check_timestamp = 0
160
+ if refresh or (now_ts - last_check) < 60:
159
161
  self._stale_temporary_tables_check_timestamp = now_ts
160
162
  return self._drop_temporary_tables(debug=debug)
161
163
 
@@ -752,7 +752,7 @@ def get_pipe_data(
752
752
  debug = debug,
753
753
  **kw
754
754
  )
755
-
755
+
756
756
  if is_dask:
757
757
  index_col = pipe.columns.get('datetime', None)
758
758
  kw['index_col'] = index_col
@@ -1478,43 +1478,11 @@ def sync_pipe_inplace(
1478
1478
  from meerschaum.utils.misc import generate_password
1479
1479
  from meerschaum.utils.debug import dprint
1480
1480
 
1481
- sqlalchemy, sqlalchemy_orm = mrsm.attempt_import('sqlalchemy', 'sqlalchemy.orm')
1482
- metadef = self.get_pipe_metadef(
1483
- pipe,
1484
- params = params,
1485
- begin = begin,
1486
- end = end,
1487
- check_existing = check_existing,
1488
- debug = debug,
1489
- )
1490
- pipe_name = sql_item_name(pipe.target, self.flavor, self.get_pipe_schema(pipe))
1491
- upsert = pipe.parameters.get('upsert', False) and f'{self.flavor}-upsert' in update_queries
1492
- internal_schema = self.internal_schema
1493
- database = getattr(self, 'database', self.parse_uri(self.URI).get('database', None))
1494
-
1495
- if not pipe.exists(debug=debug):
1496
- create_pipe_query = get_create_table_query(
1497
- metadef,
1498
- pipe.target,
1499
- self.flavor,
1500
- schema = self.get_pipe_schema(pipe),
1501
- )
1502
- result = self.exec(create_pipe_query, debug=debug)
1503
- if result is None:
1504
- return False, f"Could not insert new data into {pipe} from its SQL query definition."
1505
- if not self.create_indices(pipe, debug=debug):
1506
- warn(f"Failed to create indices for {pipe}. Continuing...")
1507
-
1508
- rowcount = pipe.get_rowcount(debug=debug)
1509
- return True, f"Inserted {rowcount}, updated 0 rows."
1510
-
1511
- session = sqlalchemy_orm.Session(self.engine)
1512
- connectable = session if self.flavor != 'duckdb' else self
1513
-
1514
1481
  transact_id = generate_password(3)
1515
1482
  def get_temp_table_name(label: str) -> str:
1516
1483
  return '-' + transact_id + '_' + label + '_' + pipe.target
1517
1484
 
1485
+ internal_schema = self.internal_schema
1518
1486
  temp_table_roots = ['backtrack', 'new', 'delta', 'joined', 'unseen', 'update']
1519
1487
  temp_tables = {
1520
1488
  table_root: get_temp_table_name(table_root)
@@ -1528,6 +1496,17 @@ def sync_pipe_inplace(
1528
1496
  )
1529
1497
  for table_root, table_name_raw in temp_tables.items()
1530
1498
  }
1499
+ metadef = self.get_pipe_metadef(
1500
+ pipe,
1501
+ params = params,
1502
+ begin = begin,
1503
+ end = end,
1504
+ check_existing = check_existing,
1505
+ debug = debug,
1506
+ )
1507
+ pipe_name = sql_item_name(pipe.target, self.flavor, self.get_pipe_schema(pipe))
1508
+ upsert = pipe.parameters.get('upsert', False) and f'{self.flavor}-upsert' in update_queries
1509
+ database = getattr(self, 'database', self.parse_uri(self.URI).get('database', None))
1531
1510
 
1532
1511
  def clean_up_temp_tables(ready_to_drop: bool = False):
1533
1512
  log_success, log_msg = self._log_temporary_tables_creation(
@@ -1541,6 +1520,36 @@ def sync_pipe_inplace(
1541
1520
  )
1542
1521
  if not log_success:
1543
1522
  warn(log_msg)
1523
+ drop_stale_success, drop_stale_msg = self._drop_old_temporary_tables(
1524
+ refresh = False,
1525
+ debug = debug,
1526
+ )
1527
+ if not drop_stale_success:
1528
+ warn(drop_stale_msg)
1529
+ return drop_stale_success, drop_stale_msg
1530
+
1531
+ sqlalchemy, sqlalchemy_orm = mrsm.attempt_import('sqlalchemy', 'sqlalchemy.orm')
1532
+ if not pipe.exists(debug=debug):
1533
+ create_pipe_query = get_create_table_query(
1534
+ metadef,
1535
+ pipe.target,
1536
+ self.flavor,
1537
+ schema = self.get_pipe_schema(pipe),
1538
+ )
1539
+ result = self.exec(create_pipe_query, debug=debug)
1540
+ if result is None:
1541
+ _ = clean_up_temp_tables()
1542
+ return False, f"Could not insert new data into {pipe} from its SQL query definition."
1543
+
1544
+ if not self.create_indices(pipe, debug=debug):
1545
+ warn(f"Failed to create indices for {pipe}. Continuing...")
1546
+
1547
+ rowcount = pipe.get_rowcount(debug=debug)
1548
+ _ = clean_up_temp_tables()
1549
+ return True, f"Inserted {rowcount}, updated 0 rows."
1550
+
1551
+ session = sqlalchemy_orm.Session(self.engine)
1552
+ connectable = session if self.flavor != 'duckdb' else self
1544
1553
 
1545
1554
  create_new_query = get_create_table_query(
1546
1555
  metadef,
@@ -1908,10 +1917,6 @@ def sync_pipe_inplace(
1908
1917
  )
1909
1918
  _ = clean_up_temp_tables(ready_to_drop=True)
1910
1919
 
1911
- drop_stale_success, drop_stale_msg = self._drop_old_temporary_tables(refresh=False, debug=debug)
1912
- if not drop_stale_success:
1913
- warn(drop_stale_msg)
1914
-
1915
1920
  return True, msg
1916
1921
 
1917
1922
 
@@ -2372,6 +2377,16 @@ def get_pipe_columns_types(
2372
2377
  """
2373
2378
  if not pipe.exists(debug=debug):
2374
2379
  return {}
2380
+
2381
+ if self.flavor == 'duckdb':
2382
+ from meerschaum.utils.sql import get_table_cols_types
2383
+ return get_table_cols_types(
2384
+ pipe.target,
2385
+ self,
2386
+ flavor = self.flavor,
2387
+ schema = self.schema,
2388
+ )
2389
+
2375
2390
  table_columns = {}
2376
2391
  try:
2377
2392
  pipe_table = self.get_pipe_table(pipe, debug=debug)
@@ -943,17 +943,15 @@ def psql_insert_copy(
943
943
  ) for row in data_iter
944
944
  )
945
945
 
946
+ table_name = sql_item_name(table.name, 'postgresql', table.schema)
947
+ columns = ', '.join(f'"{k}"' for k in keys)
948
+ sql = f"COPY {table_name} ({columns}) FROM STDIN WITH CSV NULL '\\N'"
949
+
946
950
  dbapi_conn = conn.connection
947
951
  with dbapi_conn.cursor() as cur:
948
- s_buf = StringIO()
949
- writer = csv.writer(s_buf)
950
- writer.writerows(data_iter)
951
- s_buf.seek(0)
952
-
953
- columns = ', '.join(f'"{k}"' for k in keys)
954
- table_name = sql_item_name(table.name, 'postgresql', table.schema)
955
- sql = f"COPY {table_name} ({columns}) FROM STDIN WITH CSV NULL '\\N'"
956
- cur.copy_expert(sql=sql, file=s_buf)
952
+ with cur.copy(sql) as copy:
953
+ writer = csv.writer(copy)
954
+ writer.writerows(data_iter)
957
955
 
958
956
 
959
957
  def format_sql_query_for_dask(query: str) -> 'sqlalchemy.sql.selectable.Select':
@@ -86,6 +86,8 @@ def verify_password(
86
86
  -------
87
87
  A `bool` indicating whether `password` matches `password_hash`.
88
88
  """
89
+ if password is None or password_hash is None:
90
+ return False
89
91
  hash_config = STATIC_CONFIG['users']['password_hash']
90
92
  try:
91
93
  digest, rounds_str, encoded_salt, encoded_checksum = password_hash.split('$')[1:]
@@ -247,6 +247,26 @@ def sync_plugins_symlinks(debug: bool = False, warn: bool = True) -> None:
247
247
  _warn(f"Unable to create lockfile {PLUGINS_INTERNAL_LOCK_PATH}:\n{e}")
248
248
 
249
249
  with _locks['internal_plugins']:
250
+
251
+ try:
252
+ from importlib.metadata import entry_points
253
+ except ImportError:
254
+ importlib_metadata = attempt_import('importlib_metadata', lazy=False)
255
+ entry_points = importlib_metadata.entry_points
256
+
257
+ ### NOTE: Allow plugins to be installed via `pip`.
258
+ packaged_plugin_paths = []
259
+ discovered_packaged_plugins_eps = entry_points(group='meerschaum.plugins')
260
+ for ep in discovered_packaged_plugins_eps:
261
+ module_name = ep.name
262
+ for package_file_path in ep.dist.files:
263
+ if package_file_path.suffix != '.py':
264
+ continue
265
+ if str(package_file_path) == f'{module_name}.py':
266
+ packaged_plugin_paths.append(package_file_path.locate())
267
+ elif str(package_file_path) == f'{module_name}/__init__.py':
268
+ packaged_plugin_paths.append(package_file_path.locate().parent)
269
+
250
270
  if is_symlink(PLUGINS_RESOURCES_PATH) or not PLUGINS_RESOURCES_PATH.exists():
251
271
  try:
252
272
  PLUGINS_RESOURCES_PATH.unlink()
@@ -255,7 +275,6 @@ def sync_plugins_symlinks(debug: bool = False, warn: bool = True) -> None:
255
275
 
256
276
  PLUGINS_RESOURCES_PATH.mkdir(exist_ok=True)
257
277
 
258
-
259
278
  existing_symlinked_paths = [
260
279
  (PLUGINS_RESOURCES_PATH / item)
261
280
  for item in os.listdir(PLUGINS_RESOURCES_PATH)
@@ -275,6 +294,7 @@ def sync_plugins_symlinks(debug: bool = False, warn: bool = True) -> None:
275
294
  for plugins_path in PLUGINS_DIR_PATHS
276
295
  ]
277
296
  ))
297
+ plugins_to_be_symlinked.extend(packaged_plugin_paths)
278
298
 
279
299
  ### Check for duplicates.
280
300
  seen_plugins = defaultdict(lambda: 0)
@@ -538,6 +558,8 @@ def get_plugins(*to_load, try_import: bool = True) -> Union[Tuple[Plugin], Plugi
538
558
  ]
539
559
  plugins = tuple(plugin for plugin in _plugins if plugin.is_installed(try_import=try_import))
540
560
  if len(to_load) == 1:
561
+ if len(plugins) == 0:
562
+ raise ValueError(f"Plugin '{to_load[0]}' is not installed.")
541
563
  return plugins[0]
542
564
  return plugins
543
565
 
@@ -19,6 +19,7 @@ from functools import partial
19
19
  from datetime import datetime, timezone
20
20
  from meerschaum.utils.typing import Optional, Dict, Any, SuccessTuple, Callable, List, Union
21
21
  from meerschaum.config import get_config
22
+ from meerschaum.config.static import STATIC_CONFIG
22
23
  from meerschaum.config._paths import DAEMON_RESOURCES_PATH, LOGS_RESOURCES_PATH
23
24
  from meerschaum.config._patch import apply_patch_to_config
24
25
  from meerschaum.utils.warnings import warn, error
@@ -170,9 +171,11 @@ class Daemon:
170
171
  log_refresh_seconds,
171
172
  partial(self.rotating_log.refresh_files, start_interception=True),
172
173
  )
174
+
173
175
  try:
174
176
  os.environ['LINES'], os.environ['COLUMNS'] = str(int(lines)), str(int(columns))
175
177
  with self._daemon_context:
178
+ os.environ[STATIC_CONFIG['environment']['daemon_id']] = self.daemon_id
176
179
  self.rotating_log.refresh_files(start_interception=True)
177
180
  try:
178
181
  with open(self.pid_path, 'w+', encoding='utf-8') as f:
@@ -462,6 +465,9 @@ class Daemon:
462
465
  Handle `SIGINT` within the Daemon context.
463
466
  This method is injected into the `DaemonContext`.
464
467
  """
468
+ # from meerschaum.utils.daemon.FileDescriptorInterceptor import STOP_READING_FD_EVENT
469
+ # STOP_READING_FD_EVENT.set()
470
+ self.rotating_log.stop_log_fd_interception(unused_only=False)
465
471
  timer = self.__dict__.get('_log_refresh_timer', None)
466
472
  if timer is not None:
467
473
  timer.cancel()
@@ -471,7 +477,16 @@ class Daemon:
471
477
  daemon_context.close()
472
478
 
473
479
  _close_pools()
474
- self.rotating_log.stop_log_fd_interception()
480
+ import threading
481
+ for thread in threading.enumerate():
482
+ if thread.name == 'MainThread':
483
+ continue
484
+ try:
485
+ if thread.is_alive():
486
+ stack = traceback.format_stack(sys._current_frames()[thread.ident])
487
+ thread.join()
488
+ except Exception as e:
489
+ warn(traceback.format_exc())
475
490
  raise KeyboardInterrupt()
476
491
 
477
492
 
@@ -489,7 +504,7 @@ class Daemon:
489
504
  daemon_context.close()
490
505
 
491
506
  _close_pools()
492
- raise SystemExit(1)
507
+ raise SystemExit(0)
493
508
 
494
509
 
495
510
  def _send_signal(
@@ -7,12 +7,15 @@ Intercept OS-level file descriptors.
7
7
  """
8
8
 
9
9
  import os
10
+ import select
10
11
  import traceback
12
+ from threading import Event
11
13
  from datetime import datetime
12
14
  from meerschaum.utils.typing import Callable
13
15
  from meerschaum.utils.warnings import warn
14
16
 
15
17
  FD_CLOSED: int = 9
18
+ STOP_READING_FD_EVENT: Event = Event()
16
19
 
17
20
  class FileDescriptorInterceptor:
18
21
  """
@@ -32,10 +35,12 @@ class FileDescriptorInterceptor:
32
35
  injection_hook: Callable[[], str]
33
36
  A callable which returns a string to be injected into the written data.
34
37
  """
38
+ self.stop_event = Event()
35
39
  self.injection_hook = injection_hook
36
40
  self.original_file_descriptor = file_descriptor
37
41
  self.new_file_descriptor = os.dup(file_descriptor)
38
42
  self.read_pipe, self.write_pipe = os.pipe()
43
+ self.signal_read_pipe, self.signal_write_pipe = os.pipe()
39
44
  os.dup2(self.write_pipe, file_descriptor)
40
45
 
41
46
  def start_interception(self):
@@ -44,11 +49,23 @@ class FileDescriptorInterceptor:
44
49
 
45
50
  NOTE: This is blocking and is meant to be run in a thread.
46
51
  """
52
+ os.set_blocking(self.read_pipe, False)
53
+ os.set_blocking(self.signal_read_pipe, False)
47
54
  is_first_read = True
48
- while True:
49
- data = os.read(self.read_pipe, 1024)
50
- if not data:
51
- break
55
+ while not self.stop_event.is_set():
56
+ try:
57
+ rlist, _, _ = select.select([self.read_pipe, self.signal_read_pipe], [], [], 0.1)
58
+ if self.signal_read_pipe in rlist:
59
+ break
60
+ if not rlist:
61
+ continue
62
+ data = os.read(self.read_pipe, 1024)
63
+ if not data:
64
+ break
65
+ except BlockingIOError:
66
+ continue
67
+ except OSError as e:
68
+ continue
52
69
 
53
70
  first_char_is_newline = data[0] == b'\n'
54
71
  last_char_is_newline = data[-1] == b'\n'
@@ -65,16 +82,17 @@ class FileDescriptorInterceptor:
65
82
  if last_char_is_newline
66
83
  else data.replace(b'\n', b'\n' + injected_bytes)
67
84
  )
68
-
69
85
  os.write(self.new_file_descriptor, modified_data)
70
86
 
87
+
71
88
  def stop_interception(self):
72
89
  """
73
- Restore the file descriptors and close the new pipes.
90
+ Close the new file descriptors.
74
91
  """
92
+ self.stop_event.set()
93
+ os.write(self.signal_write_pipe, b'\0')
75
94
  try:
76
- os.dup2(self.new_file_descriptor, self.original_file_descriptor)
77
- # os.close(self.new_file_descriptor)
95
+ os.close(self.new_file_descriptor)
78
96
  except OSError as e:
79
97
  if e.errno != FD_CLOSED:
80
98
  warn(
@@ -100,3 +118,23 @@ class FileDescriptorInterceptor:
100
118
  + "to the intercepted file descriptor:\n"
101
119
  + f"{traceback.format_exc()}"
102
120
  )
121
+
122
+ try:
123
+ os.close(self.signal_read_pipe)
124
+ except OSError as e:
125
+ if e.errno != FD_CLOSED:
126
+ warn(
127
+ f"Error while trying to close the signal-read-pipe "
128
+ + "to the intercepted file descriptor:\n"
129
+ + f"{traceback.format_exc()}"
130
+ )
131
+
132
+ try:
133
+ os.close(self.signal_write_pipe)
134
+ except OSError as e:
135
+ if e.errno != FD_CLOSED:
136
+ warn(
137
+ f"Error while trying to close the signal-write-pipe "
138
+ + "to the intercepted file descriptor:\n"
139
+ + f"{traceback.format_exc()}"
140
+ )
@@ -621,6 +621,10 @@ class RotatingFile(io.IOBase):
621
621
  self._stdout_interceptor_thread,
622
622
  self._stderr_interceptor_thread,
623
623
  ])
624
+ self._interceptors.extend([
625
+ self._stdout_interceptor,
626
+ self._stderr_interceptor,
627
+ ])
624
628
  self.stop_log_fd_interception(unused_only=True)
625
629
 
626
630
  def stop_log_fd_interception(self, unused_only: bool = False):
@@ -64,6 +64,8 @@ def daemon_entry(sysargs: Optional[List[str]] = None) -> SuccessTuple:
64
64
 
65
65
  ### Only run if the kwargs equal or no actions are provided.
66
66
  if existing_kwargs == _args or not _args.get('action', []):
67
+ if daemon.status == 'running':
68
+ return True, f"Daemon '{daemon}' is already running."
67
69
  return daemon.run(
68
70
  debug = debug,
69
71
  allow_dirty_run = True,
@@ -829,8 +829,11 @@ def pip_install(
829
829
  check_wheel = False, debug = debug,
830
830
  ):
831
831
  warn(
832
- f"Failed to install `setuptools` and `wheel` for virtual environment '{venv}'.",
833
- color=False,
832
+ (
833
+ "Failed to install `setuptools` and `wheel` for virtual "
834
+ + f"environment '{venv}'."
835
+ ),
836
+ color = False,
834
837
  )
835
838
 
836
839
  if requirements_file_path is not None:
@@ -893,13 +896,16 @@ def pip_install(
893
896
  f"Failed to clean up package '{_install_no_version}'.",
894
897
  )
895
898
 
896
- success = run_python_package(
899
+ rc = run_python_package(
897
900
  'pip',
898
901
  _args + _packages,
899
902
  venv = venv,
900
903
  env = _get_pip_os_env(),
901
904
  debug = debug,
902
- ) == 0
905
+ )
906
+ if debug:
907
+ print(f"{rc=}")
908
+ success = rc == 0
903
909
 
904
910
  msg = (
905
911
  "Successfully " + ('un' if _uninstall else '') + "installed packages." if success
@@ -60,7 +60,7 @@ packages: Dict[str, Dict[str, str]] = {
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.0',
64
64
  'duckdb_engine' : 'duckdb-engine>=0.9.2',
65
65
  },
66
66
  '_drivers': {
@@ -120,8 +120,8 @@ packages: Dict[str, Dict[str, str]] = {
120
120
  packages['sql'] = {
121
121
  'numpy' : 'numpy>=1.18.5',
122
122
  'pandas' : 'pandas[parquet]>=2.0.1',
123
- 'pyarrow' : 'pyarrow>=7.0.0',
124
- 'dask' : 'dask>=2023.5.0',
123
+ 'pyarrow' : 'pyarrow>=16.1.0',
124
+ 'dask' : 'dask[dataframe]>=2024.5.1',
125
125
  'pytz' : 'pytz',
126
126
  'joblib' : 'joblib>=0.17.0',
127
127
  'sqlalchemy' : 'SQLAlchemy>=2.0.5',
@@ -142,14 +142,13 @@ packages['dash'] = {
142
142
  'tornado' : 'tornado>=6.1.0',
143
143
  }
144
144
  packages['api'] = {
145
- 'uvicorn' : 'uvicorn[standard]>=0.22.0',
146
- 'gunicorn' : 'gunicorn>=20.1.0',
145
+ 'uvicorn' : 'uvicorn[standard]>=0.29.0',
146
+ 'gunicorn' : 'gunicorn>=22.0.0',
147
147
  'dotenv' : 'python-dotenv>=0.20.0',
148
148
  'websockets' : 'websockets>=11.0.3',
149
- 'fastapi' : 'fastapi>=0.100.0',
150
- 'passlib' : 'passlib>=1.7.4',
149
+ 'fastapi' : 'fastapi>=0.111.0',
151
150
  'fastapi_login' : 'fastapi-login>=1.7.2',
152
- 'multipart' : 'python-multipart>=0.0.5',
151
+ 'multipart' : 'python-multipart>=0.0.9',
153
152
  'httpx' : 'httpx>=0.24.1',
154
153
  'websockets' : 'websockets>=11.0.3',
155
154
  }
@@ -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()
@@ -278,7 +278,21 @@ def parse_start_time(schedule: str, now: Optional[datetime] = None) -> datetime:
278
278
  starting_str = ('now' if len(starting_parts) == 1 else starting_parts[-1]).strip()
279
279
  now = now or round_time(datetime.now(timezone.utc), timedelta(minutes=1))
280
280
  try:
281
- starting_ts = now if starting_str == 'now' else dateutil_parser.parse(starting_str)
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)
282
296
  schedule_parse_error = None
283
297
  except Exception as e:
284
298
  warn(f"Unable to parse starting time from '{starting_str}'.", stack=False)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: meerschaum
3
- Version: 2.2.0rc2
3
+ Version: 2.2.0rc3
4
4
  Summary: Sync Time-Series Pipes with Meerschaum
5
5
  Home-page: https://meerschaum.io
6
6
  Author: Bennett Meares
@@ -58,19 +58,18 @@ Requires-Dist: dill >=0.3.3 ; extra == '_required'
58
58
  Requires-Dist: virtualenv >=20.1.0 ; extra == '_required'
59
59
  Requires-Dist: APScheduler >=4.0.0a5 ; extra == '_required'
60
60
  Provides-Extra: api
61
- Requires-Dist: uvicorn[standard] >=0.22.0 ; extra == 'api'
62
- Requires-Dist: gunicorn >=20.1.0 ; extra == 'api'
61
+ Requires-Dist: uvicorn[standard] >=0.29.0 ; extra == 'api'
62
+ Requires-Dist: gunicorn >=22.0.0 ; extra == 'api'
63
63
  Requires-Dist: python-dotenv >=0.20.0 ; extra == 'api'
64
64
  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'
65
+ Requires-Dist: fastapi >=0.111.0 ; extra == 'api'
67
66
  Requires-Dist: fastapi-login >=1.7.2 ; extra == 'api'
68
- Requires-Dist: python-multipart >=0.0.5 ; extra == 'api'
67
+ Requires-Dist: python-multipart >=0.0.9 ; extra == 'api'
69
68
  Requires-Dist: httpx >=0.24.1 ; extra == 'api'
70
69
  Requires-Dist: numpy >=1.18.5 ; extra == 'api'
71
70
  Requires-Dist: pandas[parquet] >=2.0.1 ; extra == 'api'
72
- Requires-Dist: pyarrow >=7.0.0 ; extra == 'api'
73
- Requires-Dist: dask >=2023.5.0 ; extra == 'api'
71
+ Requires-Dist: pyarrow >=16.1.0 ; extra == 'api'
72
+ Requires-Dist: dask[dataframe] >=2024.5.1 ; extra == 'api'
74
73
  Requires-Dist: pytz ; extra == 'api'
75
74
  Requires-Dist: joblib >=0.17.0 ; extra == 'api'
76
75
  Requires-Dist: SQLAlchemy >=2.0.5 ; extra == 'api'
@@ -82,7 +81,7 @@ Requires-Dist: psycopg[binary] >=3.1.18 ; extra == 'api'
82
81
  Requires-Dist: PyMySQL >=0.9.0 ; extra == 'api'
83
82
  Requires-Dist: aiomysql >=0.0.21 ; extra == 'api'
84
83
  Requires-Dist: sqlalchemy-cockroachdb >=2.0.0 ; extra == 'api'
85
- Requires-Dist: duckdb >=0.9.0 ; extra == 'api'
84
+ Requires-Dist: duckdb <0.10.0 ; extra == 'api'
86
85
  Requires-Dist: duckdb-engine >=0.9.2 ; extra == 'api'
87
86
  Requires-Dist: wheel >=0.34.2 ; extra == 'api'
88
87
  Requires-Dist: setuptools >=63.3.0 ; extra == 'api'
@@ -165,7 +164,7 @@ Requires-Dist: psycopg[binary] >=3.1.18 ; extra == 'drivers'
165
164
  Requires-Dist: PyMySQL >=0.9.0 ; extra == 'drivers'
166
165
  Requires-Dist: aiomysql >=0.0.21 ; extra == 'drivers'
167
166
  Requires-Dist: sqlalchemy-cockroachdb >=2.0.0 ; extra == 'drivers'
168
- Requires-Dist: duckdb >=0.9.0 ; extra == 'drivers'
167
+ Requires-Dist: duckdb <0.10.0 ; extra == 'drivers'
169
168
  Requires-Dist: duckdb-engine >=0.9.2 ; extra == 'drivers'
170
169
  Provides-Extra: extras
171
170
  Requires-Dist: cmd2 >=1.4.0 ; extra == 'extras'
@@ -218,15 +217,15 @@ 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.0 ; 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,14 +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'
249
+ Requires-Dist: python-multipart >=0.0.9 ; extra == 'full'
252
250
  Requires-Dist: httpx >=0.24.1 ; extra == 'full'
253
251
  Provides-Extra: gui
254
252
  Requires-Dist: toga >=0.3.0-dev29 ; extra == 'gui'
@@ -260,8 +258,8 @@ Provides-Extra: setup
260
258
  Provides-Extra: sql
261
259
  Requires-Dist: numpy >=1.18.5 ; extra == 'sql'
262
260
  Requires-Dist: pandas[parquet] >=2.0.1 ; extra == 'sql'
263
- Requires-Dist: pyarrow >=7.0.0 ; extra == 'sql'
264
- 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'
265
263
  Requires-Dist: pytz ; extra == 'sql'
266
264
  Requires-Dist: joblib >=0.17.0 ; extra == 'sql'
267
265
  Requires-Dist: SQLAlchemy >=2.0.5 ; extra == 'sql'
@@ -273,7 +271,7 @@ Requires-Dist: psycopg[binary] >=3.1.18 ; extra == 'sql'
273
271
  Requires-Dist: PyMySQL >=0.9.0 ; extra == 'sql'
274
272
  Requires-Dist: aiomysql >=0.0.21 ; extra == 'sql'
275
273
  Requires-Dist: sqlalchemy-cockroachdb >=2.0.0 ; extra == 'sql'
276
- Requires-Dist: duckdb >=0.9.0 ; extra == 'sql'
274
+ Requires-Dist: duckdb <0.10.0 ; extra == 'sql'
277
275
  Requires-Dist: duckdb-engine >=0.9.2 ; extra == 'sql'
278
276
  Requires-Dist: wheel >=0.34.2 ; extra == 'sql'
279
277
  Requires-Dist: setuptools >=63.3.0 ; extra == 'sql'
@@ -48,7 +48,7 @@ meerschaum/actions/tag.py,sha256=SJf5qFW0ccLXjqlTdkK_0MCcrCMdg6xhYrhKdco0hdA,305
48
48
  meerschaum/actions/uninstall.py,sha256=2fUd5ZK45VGGCI8V4NLmSnavdKjOv7cGM22x2WlTStw,6068
49
49
  meerschaum/actions/upgrade.py,sha256=VQKyjCGioEF2FYbQmldHh21imDqApNl0xal0rhxzrJk,6302
50
50
  meerschaum/actions/verify.py,sha256=tY5slGpHiWiE0v9TDnjbmxSKn86zBnu9WBpixUgKNQU,4885
51
- meerschaum/api/__init__.py,sha256=1PA77oMR9HAgrj2tIYvxwWjpYnx3H4lwm7jq0JVTYmM,7694
51
+ meerschaum/api/__init__.py,sha256=TfhjWIyg3AbG74LWhMBpcr487L_4Aak7YNO1rGIV5Vc,7410
52
52
  meerschaum/api/_chain.py,sha256=h8-WXUGXX6AqzdALfsBC5uv0FkAcLdHJXCGzqzuq89k,875
53
53
  meerschaum/api/_events.py,sha256=NrjiabEr7rmHMfxnX07DOGzr9sPiEbRkFqPjuA_8Zx8,1603
54
54
  meerschaum/api/_oauth2.py,sha256=eH5r2rWuDmMHJPuukG0hj1E-LqtcDf5-lzp1YShOQOo,961
@@ -128,23 +128,23 @@ meerschaum/config/_default.py,sha256=DSbyVcAL55Xf8MJCTxpgF7ZXL-O9peeMEqL29WFfsJ4
128
128
  meerschaum/config/_edit.py,sha256=CE8gumBiOtnZZdTATCLAZgUnhL04yJdReaNCrv1yuJc,8218
129
129
  meerschaum/config/_environment.py,sha256=Vv4DLDfc2vKLbCLsMvkQDj77K4kEvHKEBmUBo-wCrgo,4419
130
130
  meerschaum/config/_formatting.py,sha256=RT_oU0OSfUkzeqbY5jvEJwuove_t9sP4MyUb1Px250U,6635
131
- meerschaum/config/_jobs.py,sha256=ORoMQQeYQe_zWdgId4OL9R_n6KCFwKGM8T6KarCZDXI,1228
131
+ meerschaum/config/_jobs.py,sha256=ki3Wb3QejAXyJhbBfme21EBhEntiOa6Pynp5OMfffp0,1227
132
132
  meerschaum/config/_patch.py,sha256=21N30q1ANmWMDQ-2RUjpMx7KafWfPQ3lKx9rrMqg1s4,1526
133
- meerschaum/config/_paths.py,sha256=Y8r60TdTGn9W9JBu5Ct0SsrsT1vu8rvfPBeC3kr-CNY,8064
133
+ meerschaum/config/_paths.py,sha256=TihDGA6IQou3ms1xKYHmRTU48kOflqV5ZOMguG25NM4,8154
134
134
  meerschaum/config/_preprocess.py,sha256=-AEA8m_--KivZwTQ1sWN6LTn5sio_fUr2XZ51BO6wLs,1220
135
135
  meerschaum/config/_read_config.py,sha256=WFZKIXZMDe_ca0ES7ivgM_mnwShvFxLdoeisT_X5-h0,14720
136
136
  meerschaum/config/_shell.py,sha256=s74cmJl8NrhM_Y1cB_P41_JDUYXV0g4WXnKFZWMtnrY,3551
137
137
  meerschaum/config/_sync.py,sha256=oK2ZujO2T1he08BXCFyiniBUevNGWSQKXLcS_jRv_7Y,4155
138
- meerschaum/config/_version.py,sha256=8rfJ7DJwhu8N0Yi83bVnmcqluaRt_x9NGVOkQZAP5co,74
138
+ meerschaum/config/_version.py,sha256=2-cyfpj01b8gfsTjUSjwd1-HjDjJ9lsq--ZPCqI2ZgA,74
139
139
  meerschaum/config/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
140
140
  meerschaum/config/stack/__init__.py,sha256=c_WdTSejVdj8lqSE_pK5MhIBkHoftiZWDuEuB9dmk2I,9007
141
141
  meerschaum/config/stack/grafana/__init__.py,sha256=LNXQw2FvHKrD68RDhqDmi2wJjAHaKw9IWx8rNuyWEPo,2010
142
142
  meerschaum/config/stack/mosquitto/__init__.py,sha256=-OwOjq8KiBoSH_pmgCAAF3Dp3CRD4KgAEdimZSadROs,186
143
143
  meerschaum/config/stack/mosquitto/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
144
144
  meerschaum/config/stack/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
145
- meerschaum/config/static/__init__.py,sha256=EvLb2Syq3zykmzM7r083HjAXTOfLBKjPuu5ZIMVVs9M,4435
145
+ meerschaum/config/static/__init__.py,sha256=zsSq54OkLqjfG_QGyH6zXzZnoaGpDyjmV0OCvbygFr8,4474
146
146
  meerschaum/connectors/Connector.py,sha256=cJKinmk7eWZwCBvtX4H9r66macTZOY1qjxR7JUEmDmg,6381
147
- meerschaum/connectors/__init__.py,sha256=0uAfGRyLhUI19p161wytmzAurx3znhh4I9O9PyZ4w_E,12023
147
+ meerschaum/connectors/__init__.py,sha256=OCNfIEeuom6g7GOO3xj37YAuxLOesGCZ-VrcltyHsSY,12071
148
148
  meerschaum/connectors/parse.py,sha256=gidA2jvOLTvEeL5hM9JjcUwkMRBadUhd4IfA5Jy1tgg,4044
149
149
  meerschaum/connectors/poll.py,sha256=gIY9TvFBqMvMNQvR0O2No7koLLz2PjfExBr_Dsosgpg,7363
150
150
  meerschaum/connectors/api/APIConnector.py,sha256=pj-RncLhDUEBK7R4tByH6nrxZWU8zxGVOS2Wyik36zs,4355
@@ -160,15 +160,15 @@ meerschaum/connectors/api/_uri.py,sha256=h4Gj63f0q2V-TNMd8aAkQZMIj_-pA6uGacg_RNE
160
160
  meerschaum/connectors/api/_users.py,sha256=kzb7ENgXwQ19OJYKOuuWzx2rwVuUZCly9dTnyvVuT2Q,5275
161
161
  meerschaum/connectors/plugin/PluginConnector.py,sha256=aQ1QaB7MordCFimZqoGLb0R12PfDUN_nWks2J5mzeAs,2084
162
162
  meerschaum/connectors/plugin/__init__.py,sha256=pwF7TGY4WNz2_HaVdmK4rPQ9ZwTOEuPHgzOqsGcoXJw,198
163
- meerschaum/connectors/sql/SQLConnector.py,sha256=dPMgJXLghzBmqHaT9qxvn8pzerQLpzkUPRx42W6mJIU,11522
163
+ meerschaum/connectors/sql/SQLConnector.py,sha256=QvsFgAv2NAUdsJrcNYZIkQ9x_ow5JRe4Ie_UgSrDz3M,11709
164
164
  meerschaum/connectors/sql/__init__.py,sha256=xwSYhYuketTXhQLXyD9pZ0NNBPboW5Oqv9zrKfjx0Ic,175
165
165
  meerschaum/connectors/sql/_cli.py,sha256=XaWjWZzGIfhMiYoXAs2FrwHUGNyZpxIzH4g4xugLKsw,4123
166
- meerschaum/connectors/sql/_create_engine.py,sha256=b1ICKl3ZnPX5R2vqbKcClsCnHih9SghF6ghpy19FEpQ,10375
166
+ meerschaum/connectors/sql/_create_engine.py,sha256=vPVwR3cpfcQm0aW9xo_DUWA3gMdlkuB0wJ8JoTpRYD0,10396
167
167
  meerschaum/connectors/sql/_fetch.py,sha256=NYYWDoEd-aGIS337KwH-D9_3KVWVCZOHAspGLfdEuUE,13086
168
- meerschaum/connectors/sql/_instance.py,sha256=jAGq_qjz2WyBrjtnI7FyGRLOs2l3o630vonPM5Pp4Mg,6447
169
- meerschaum/connectors/sql/_pipes.py,sha256=4v1-2AwW8QqOTRjNUEoDJJH8KsJa6FxMNSekEDWJu6o,100925
168
+ meerschaum/connectors/sql/_instance.py,sha256=r_S96vzSuKnBVGROSKQAPl-DnFnOOEPUkz1KFDFPHFQ,6509
169
+ meerschaum/connectors/sql/_pipes.py,sha256=WruMoLGMckUIardNtYzC7P0gavaz_HVlDb-IMv3pF9s,101357
170
170
  meerschaum/connectors/sql/_plugins.py,sha256=OsCmDlZKyJnY77QAWghmpuXR0z_CmFq5uHpZK7Cj-ZI,8321
171
- meerschaum/connectors/sql/_sql.py,sha256=jNdIcwQC2ZMNKT9W6ugavQPtimUpoTr8cBt05z_vHIo,34262
171
+ meerschaum/connectors/sql/_sql.py,sha256=553BTPtvnnLMERYK8IcxR4CS3R4Sq7id-5hbApOEsIU,34199
172
172
  meerschaum/connectors/sql/_uri.py,sha256=0BrhQtqQdzg9mR04gWBZINs_BbPFtSlTECXT_TCUwik,3460
173
173
  meerschaum/connectors/sql/_users.py,sha256=JkD6lKYJO8JVnpVySMqPM20NWUr-XzD_JO-lLMcAD_c,10100
174
174
  meerschaum/connectors/sql/tools.py,sha256=jz8huOaRCwGlYdtGfAqAh7SoK8uydYBrasKQba9FT38,187
@@ -191,10 +191,10 @@ meerschaum/core/Pipe/_show.py,sha256=nG50y8eBT9TVuKkRgAKtNDNIxysJvMNxfu__lkL1F9k
191
191
  meerschaum/core/Pipe/_sync.py,sha256=48qk1xvkcKOqfzzYf3QdA7ojsP80yrso1YU8QIGJxwE,28038
192
192
  meerschaum/core/Pipe/_verify.py,sha256=KSnthUzImRLjt9fxyBaLvArqDuOLRpKBfk0tnseJClc,14262
193
193
  meerschaum/core/Plugin/__init__.py,sha256=UXg64EvJPgI1PCxkY_KM02-ZmBm4FZpLPIQR_uSJJDc,137
194
- meerschaum/core/User/_User.py,sha256=JZaIvRq32Hfp369s-TfouFUHzlBBsJHhcv3ug7DRwFM,6456
194
+ meerschaum/core/User/_User.py,sha256=CApB7Y0QJL6S9QOCCfrG4SbPuPXJ9AsAYQ5pASMP_Aw,6527
195
195
  meerschaum/core/User/__init__.py,sha256=lJ7beIZTG9sO4dAi3367fFBl17dXYEWHKi7HoaPlDyk,193
196
196
  meerschaum/plugins/_Plugin.py,sha256=LpplVPviSskKqf_igl4yIsD72H2C9vaFPQgU7-93ytg,34039
197
- meerschaum/plugins/__init__.py,sha256=pVRgbBk1UMlqLrM5p1s7_x_mN70epdDBZOa4vrt6C6w,20760
197
+ meerschaum/plugins/__init__.py,sha256=g-KFejGEqcfBukqqF13Vsj8TdjGo99nrv_Z6CB8-mCg,21866
198
198
  meerschaum/utils/__init__.py,sha256=QrK1K9hIbPCRCM5k2nZGFqGnrqhA0Eh-iSmCU7FG6Cs,612
199
199
  meerschaum/utils/_get_pipes.py,sha256=dlPckpYYyM0IwRZ2VL0u9DiEeYhr5Ho9gkzvWxzNVwI,11460
200
200
  meerschaum/utils/dataframe.py,sha256=vxZ72ME7IWuadtktgjFZF5bc9fXW_0TuynjFlJljlLU,31955
@@ -203,18 +203,18 @@ meerschaum/utils/interactive.py,sha256=t-6jWozXSqL7lYGDHuwiOjTgr-UKhdcg61q_eR5mi
203
203
  meerschaum/utils/misc.py,sha256=H26hLtCP8QHwXoHlvkxjWu6cPTwudDbbsbRkGw6ultg,43296
204
204
  meerschaum/utils/networking.py,sha256=Sr_eYUGW8_UV9-k9LqRFf7xLtbUcsDucODyLCRsFRUc,1006
205
205
  meerschaum/utils/pool.py,sha256=vkE42af4fjrTEJTxf6Ek3xGucm1MtEkpsSEiaVzNKHs,2655
206
- meerschaum/utils/process.py,sha256=tbEutHAg_Kn5UetOI-fduRjsafGOYX5tkLvpzqosgvc,7098
206
+ meerschaum/utils/process.py,sha256=IHnUhX79XofHwMsOs1A_irLMa7i48xXB-GCL83MtZOY,7192
207
207
  meerschaum/utils/prompt.py,sha256=0mBFbgi_l9rCou9UnC_6qKTHkqyl1Z_jSRzfmc0xRXM,16490
208
- meerschaum/utils/schedule.py,sha256=nXiOAHNq51WDS5NS4MPMDmv1MY7RV0TxRNXN69S_w1w,10020
208
+ meerschaum/utils/schedule.py,sha256=coJm8s91DZjezv4h9dkgUkU3R_O7gWJUBojxjItkrCQ,10654
209
209
  meerschaum/utils/sql.py,sha256=4sCNEpgUd6uFz6ySs4nnUMVaOT0YAvPM1ZlQYJTSF-0,46656
210
210
  meerschaum/utils/threading.py,sha256=3N8JXPAnwqJiSjuQcbbJg3Rv9-CCUMJpeQRfKFR7MaA,2489
211
211
  meerschaum/utils/typing.py,sha256=L05wOXfWdn_nJ0KnZVr-2zdMYcqjdyOW_7InT3xe6-s,2807
212
212
  meerschaum/utils/warnings.py,sha256=0b5O2DBbhEAGnu6RAB1hlHSVmwL_hcR3EiMkExXmBJ0,6535
213
213
  meerschaum/utils/yaml.py,sha256=vbCrFjdapKsZ9wRRaI9Ih8dVUwZ-KHpSzfGhRcpDBgQ,3162
214
- meerschaum/utils/daemon/Daemon.py,sha256=KRhK9n-UHVNHUBRKBkBFv-SB_DQpgZyqDQ712VmG7f4,33728
215
- meerschaum/utils/daemon/FileDescriptorInterceptor.py,sha256=eEOMxm9fO94rae4BSmkZjPj6evfwPLdJ2CS56ToiZl4,3180
216
- meerschaum/utils/daemon/RotatingFile.py,sha256=F-xXR7MATa-6roZUHVVoaPixOPKCjvWbpdfo_F8X1LU,23376
217
- meerschaum/utils/daemon/__init__.py,sha256=y4AZ0ddFJjAgT4yzkaJfH4qygF-Ypoj94F1YFjVrrPU,8262
214
+ meerschaum/utils/daemon/Daemon.py,sha256=2e929fSd79TokURbgw5G3Cyopp_ztmV2_YxrJZYJB2Y,34414
215
+ meerschaum/utils/daemon/FileDescriptorInterceptor.py,sha256=1cn5nQYLImL-BHXlLoxN_TadgN7XmGRLl1b7xecYMPc,4544
216
+ meerschaum/utils/daemon/RotatingFile.py,sha256=qFGlpPVa_j1VCc_S6UtsAQ4nZyxGYLajFyXmAh-UfT4,23499
217
+ meerschaum/utils/daemon/__init__.py,sha256=I_ki51yml4vsh9OoH7BWTaz9SnATD8qM0i0fN3aUMn0,8375
218
218
  meerschaum/utils/daemon/_names.py,sha256=Prf7xA2GWDbKR_9Xq9_5RTTIf9GNWY3Yt0s4tEU3JgM,4330
219
219
  meerschaum/utils/dtypes/__init__.py,sha256=JR9PViJTzhukZhq0QoPIs73HOnXZZr8OmfhAAD4OAUA,6261
220
220
  meerschaum/utils/dtypes/sql.py,sha256=IkEOyB63je-rCLHM6WwFzGbCerYk1zobL1cXkWqmTa4,14638
@@ -223,16 +223,16 @@ meerschaum/utils/formatting/_jobs.py,sha256=s1lVcdMkzNj5Bqw-GsUhcguUFtahi5nQ-kg1
223
223
  meerschaum/utils/formatting/_pipes.py,sha256=wy0iWJFsFl3X2VloaiA_gp9Yx9w6tD3FQZvAQAqef4A,19492
224
224
  meerschaum/utils/formatting/_pprint.py,sha256=tgrT3FyGyu5CWJYysqK3kX1xdZYorlbOk9fcU_vt9Qg,3096
225
225
  meerschaum/utils/formatting/_shell.py,sha256=ox75O7VHDAiwzSvdMSJZhXLadvAqYJVeihU6WeZ2Ogc,3677
226
- meerschaum/utils/packages/__init__.py,sha256=Ohwzw1GufuqQd-N2fkPSCXXGaSSDKB5_zVZ3S9atviM,57032
227
- meerschaum/utils/packages/_packages.py,sha256=eSTwPNe2llLZFdBSofuKz903mxL7feNNUYb8YlkYjhQ,7968
226
+ meerschaum/utils/packages/__init__.py,sha256=HEJYz_rceqljpyRFlnToLR6vc_b7r-2d2K8zh_th2lg,57185
227
+ meerschaum/utils/packages/_packages.py,sha256=EJ6nBHECMUj0UBeMZ0BWA5c1NlGPgvHE_37VcjPvUoo,7923
228
228
  meerschaum/utils/packages/lazy_loader.py,sha256=VHnph3VozH29R4JnSSBfwtA5WKZYZQFT_GeQSShCnuc,2540
229
229
  meerschaum/utils/venv/_Venv.py,sha256=sBnlmxHdAh2bx8btfVoD79-H9-cYsv5lP02IIXkyECs,3553
230
230
  meerschaum/utils/venv/__init__.py,sha256=sj-n8scWH2NPDJGAxfpqzsYqVUt2jMEr-7Uq9G7YUNQ,23183
231
- meerschaum-2.2.0rc2.dist-info/LICENSE,sha256=jG2zQEdRNt88EgHUWPpXVWmOrOduUQRx7MnYV9YIPaw,11359
232
- meerschaum-2.2.0rc2.dist-info/METADATA,sha256=awkeChQGlTmd76GyDGXCUsiBMNxD7WxpHpuI1ZRClQ0,23964
233
- meerschaum-2.2.0rc2.dist-info/NOTICE,sha256=OTA9Fcthjf5BRvWDDIcBC_xfLpeDV-RPZh3M-HQBRtQ,114
234
- meerschaum-2.2.0rc2.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
235
- meerschaum-2.2.0rc2.dist-info/entry_points.txt,sha256=5YBVzibw-0rNA_1VjB16z5GABsOGf-CDhW4yqH8C7Gc,88
236
- meerschaum-2.2.0rc2.dist-info/top_level.txt,sha256=bNoSiDj0El6buocix-FRoAtJOeq1qOF5rRm2u9i7Q6A,11
237
- meerschaum-2.2.0rc2.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
238
- meerschaum-2.2.0rc2.dist-info/RECORD,,
231
+ meerschaum-2.2.0rc3.dist-info/LICENSE,sha256=jG2zQEdRNt88EgHUWPpXVWmOrOduUQRx7MnYV9YIPaw,11359
232
+ meerschaum-2.2.0rc3.dist-info/METADATA,sha256=FCRvolp0e892DTcU7zLQzgnG2csLBwyQXPNfR7c41-M,23903
233
+ meerschaum-2.2.0rc3.dist-info/NOTICE,sha256=OTA9Fcthjf5BRvWDDIcBC_xfLpeDV-RPZh3M-HQBRtQ,114
234
+ meerschaum-2.2.0rc3.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
235
+ meerschaum-2.2.0rc3.dist-info/entry_points.txt,sha256=5YBVzibw-0rNA_1VjB16z5GABsOGf-CDhW4yqH8C7Gc,88
236
+ meerschaum-2.2.0rc3.dist-info/top_level.txt,sha256=bNoSiDj0El6buocix-FRoAtJOeq1qOF5rRm2u9i7Q6A,11
237
+ meerschaum-2.2.0rc3.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
238
+ meerschaum-2.2.0rc3.dist-info/RECORD,,