meerschaum 2.4.13__py3-none-any.whl → 2.5.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.
@@ -109,6 +109,7 @@ def _upgrade_meerschaum(
109
109
  class NoVenv:
110
110
  pass
111
111
 
112
+
112
113
  def _upgrade_packages(
113
114
  action: Optional[List[str]] = None,
114
115
  venv: Union[str, None, NoVenv] = NoVenv,
@@ -121,7 +122,7 @@ def _upgrade_packages(
121
122
  """
122
123
  Upgrade and install dependencies.
123
124
  If provided, upgrade only a dependency group, otherwise default to `full`.
124
-
125
+
125
126
  Examples:
126
127
  upgrade packages
127
128
  upgrade packages full
@@ -160,7 +161,7 @@ def _upgrade_packages(
160
161
  to_install = [
161
162
  install_name
162
163
  for install_name in to_install
163
- if install_name not in prereleases_to_install
164
+ if (install_name not in prereleases_to_install) or group == '_internal'
164
165
  ]
165
166
 
166
167
  success, msg = False, f"Nothing installed."
@@ -2,4 +2,4 @@
2
2
  Specify the Meerschaum release version.
3
3
  """
4
4
 
5
- __version__ = "2.4.13"
5
+ __version__ = "2.5.0"
@@ -63,6 +63,8 @@ flavor_configs = {
63
63
  'fast_executemany': True,
64
64
  'isolation_level': 'AUTOCOMMIT',
65
65
  'use_setinputsizes': False,
66
+ 'pool_pre_ping': True,
67
+ 'ignore_no_transaction_on_rollback': True,
66
68
  },
67
69
  'omit_create_engine': {'method',},
68
70
  'to_sql': {
@@ -189,15 +191,18 @@ def create_engine(
189
191
  ### Install and patch required drivers.
190
192
  if self.flavor in install_flavor_drivers:
191
193
  attempt_import(*install_flavor_drivers[self.flavor], debug=debug, lazy=False, warn=False)
194
+ if self.flavor == 'mssql':
195
+ pyodbc = attempt_import('pyodbc', debug=debug, lazy=False, warn=False)
196
+ pyodbc.pooling = False
192
197
  if self.flavor in require_patching_flavors:
193
198
  from meerschaum.utils.packages import determine_version, _monkey_patch_get_distribution
194
199
  import pathlib
195
200
  for install_name, import_name in require_patching_flavors[self.flavor]:
196
201
  pkg = attempt_import(
197
202
  import_name,
198
- debug = debug,
199
- lazy = False,
200
- warn = False
203
+ debug=debug,
204
+ lazy=False,
205
+ warn=False
201
206
  )
202
207
  _monkey_patch_get_distribution(
203
208
  install_name, determine_version(pathlib.Path(pkg.__file__), venv='mrsm')
@@ -31,7 +31,7 @@ def fetch(
31
31
  ----------
32
32
  pipe: mrsm.Pipe
33
33
  The pipe object which contains the `fetch` metadata.
34
-
34
+
35
35
  - pipe.columns['datetime']: str
36
36
  - Name of the datetime column for the remote table.
37
37
  - pipe.parameters['fetch']: Dict[str, Any]
@@ -196,7 +196,7 @@ def get_pipe_metadef(
196
196
  dateadd_str(
197
197
  flavor=self.flavor,
198
198
  datepart='minute',
199
- number=((-1 * btm) if apply_backtrack else 0),
199
+ number=((-1 * btm) if apply_backtrack else 0),
200
200
  begin=begin,
201
201
  )
202
202
  if begin
@@ -88,9 +88,9 @@ def _drop_temporary_tables(self, debug: bool = False) -> SuccessTuple:
88
88
  from meerschaum.connectors.sql.tables import get_tables
89
89
  sqlalchemy = mrsm.attempt_import('sqlalchemy')
90
90
  temp_tables_table = get_tables(
91
- mrsm_instance = self,
92
- create = False,
93
- debug = debug,
91
+ mrsm_instance=self,
92
+ create=False,
93
+ debug=debug,
94
94
  )['temp_tables']
95
95
  query = (
96
96
  sqlalchemy.select(temp_tables_table.c.table)
@@ -384,7 +384,7 @@ def get_create_index_queries(
384
384
 
385
385
  Returns
386
386
  -------
387
- A dictionary of column names mapping to lists of queries.
387
+ A dictionary of index names mapping to lists of queries.
388
388
  """
389
389
  ### NOTE: Due to recent breaking changes in DuckDB, indices don't behave properly.
390
390
  if self.flavor == 'duckdb':
@@ -400,7 +400,8 @@ def get_create_index_queries(
400
400
  index_queries = {}
401
401
 
402
402
  upsert = pipe.parameters.get('upsert', False) and (self.flavor + '-upsert') in update_queries
403
- indices = pipe.get_indices()
403
+ index_names = pipe.get_indices()
404
+ indices = pipe.indices
404
405
 
405
406
  _datetime = pipe.get_columns('datetime', error=False)
406
407
  _datetime_type = pipe.dtypes.get(_datetime, 'datetime64[ns]')
@@ -409,8 +410,8 @@ def get_create_index_queries(
409
410
  if _datetime is not None else None
410
411
  )
411
412
  _datetime_index_name = (
412
- sql_item_name(indices['datetime'], self.flavor, None)
413
- if indices.get('datetime', None)
413
+ sql_item_name(index_names['datetime'], self.flavor, None)
414
+ if index_names.get('datetime', None)
414
415
  else None
415
416
  )
416
417
  _id = pipe.get_columns('id', error=False)
@@ -421,8 +422,8 @@ def get_create_index_queries(
421
422
  )
422
423
 
423
424
  _id_index_name = (
424
- sql_item_name(indices['id'], self.flavor, None)
425
- if indices.get('id', None)
425
+ sql_item_name(index_names['id'], self.flavor, None)
426
+ if index_names.get('id', None)
426
427
  else None
427
428
  )
428
429
  _pipe_name = sql_item_name(pipe.target, self.flavor, self.get_pipe_schema(pipe))
@@ -491,18 +492,22 @@ def get_create_index_queries(
491
492
  if id_query is not None:
492
493
  index_queries[_id] = id_query if isinstance(id_query, list) else [id_query]
493
494
 
494
-
495
495
  ### Create indices for other labels in `pipe.columns`.
496
- other_indices = {
496
+ other_index_names = {
497
497
  ix_key: ix_unquoted
498
- for ix_key, ix_unquoted in pipe.get_indices().items()
498
+ for ix_key, ix_unquoted in index_names.items()
499
499
  if ix_key not in ('datetime', 'id')
500
500
  }
501
- for ix_key, ix_unquoted in other_indices.items():
501
+ for ix_key, ix_unquoted in other_index_names.items():
502
502
  ix_name = sql_item_name(ix_unquoted, self.flavor, None)
503
- col = pipe.columns[ix_key]
504
- col_name = sql_item_name(col, self.flavor, None)
505
- index_queries[col] = [f"CREATE INDEX {ix_name} ON {_pipe_name} ({col_name})"]
503
+ cols = indices[ix_key]
504
+ if not isinstance(cols, (list, tuple)):
505
+ cols = [cols]
506
+ cols_names = [sql_item_name(col, self.flavor, None) for col in cols if col]
507
+ if not cols_names:
508
+ continue
509
+ cols_names_str = ", ".join(cols_names)
510
+ index_queries[ix_key] = [f"CREATE INDEX {ix_name} ON {_pipe_name} ({cols_names_str})"]
506
511
 
507
512
  existing_cols_types = pipe.get_columns_types(debug=debug)
508
513
  indices_cols_str = ', '.join(
@@ -17,8 +17,9 @@ from meerschaum.utils.warnings import warn
17
17
  ### database flavors that can use bulk insert
18
18
  _bulk_flavors = {'postgresql', 'timescaledb', 'citus'}
19
19
  ### flavors that do not support chunks
20
- _disallow_chunks_flavors = {'duckdb', 'mssql'}
20
+ _disallow_chunks_flavors = ['duckdb']
21
21
  _max_chunks_flavors = {'sqlite': 1000,}
22
+ SKIP_READ_TRANSACTION_FLAVORS: list[str] = ['mssql']
22
23
 
23
24
 
24
25
  def read(
@@ -97,7 +98,7 @@ def read(
97
98
  Defaults to `SQLConnector.schema`.
98
99
 
99
100
  as_chunks: bool, default False
100
- If `True`, return a list of DataFrames.
101
+ If `True`, return a list of DataFrames.
101
102
  Otherwise return a single DataFrame.
102
103
 
103
104
  as_iterator: bool, default False
@@ -127,7 +128,6 @@ def read(
127
128
  from meerschaum.utils.pool import get_pool
128
129
  from meerschaum.utils.dataframe import chunksize_to_npartitions, get_numeric_cols
129
130
  import warnings
130
- import inspect
131
131
  import traceback
132
132
  from decimal import Decimal
133
133
  pd = import_pandas()
@@ -140,6 +140,7 @@ def read(
140
140
  chunksize = None
141
141
  schema = schema or self.schema
142
142
 
143
+ pool = get_pool(workers=workers)
143
144
  sqlalchemy = attempt_import("sqlalchemy")
144
145
  default_chunksize = self._sys_config.get('chunksize', None)
145
146
  chunksize = chunksize if chunksize != -1 else default_chunksize
@@ -157,7 +158,7 @@ def read(
157
158
  f"The specified chunksize of {chunksize} exceeds the maximum of "
158
159
  + f"{_max_chunks_flavors[self.flavor]} for flavor '{self.flavor}'.\n"
159
160
  + f" Falling back to a chunksize of {_max_chunks_flavors[self.flavor]}.",
160
- stacklevel = 3,
161
+ stacklevel=3,
161
162
  )
162
163
  chunksize = _max_chunks_flavors[self.flavor]
163
164
 
@@ -184,8 +185,8 @@ def read(
184
185
  truncated_table_name = truncate_item_name(str(query_or_table), self.flavor)
185
186
  if truncated_table_name != str(query_or_table) and not silent:
186
187
  warn(
187
- f"Table '{name}' is too long for '{self.flavor}',"
188
- + f" will instead create the table '{truncated_name}'."
188
+ f"Table '{query_or_table}' is too long for '{self.flavor}',"
189
+ + f" will instead read the table '{truncated_table_name}'."
189
190
  )
190
191
 
191
192
  query_or_table = sql_item_name(str(query_or_table), self.flavor, schema)
@@ -204,6 +205,34 @@ def read(
204
205
 
205
206
  chunk_list = []
206
207
  chunk_hook_results = []
208
+ def _process_chunk(_chunk, _retry_on_failure: bool = True):
209
+ if not as_hook_results:
210
+ chunk_list.append(_chunk)
211
+ if chunk_hook is None:
212
+ return None
213
+
214
+ result = None
215
+ try:
216
+ result = chunk_hook(
217
+ _chunk,
218
+ workers=workers,
219
+ chunksize=chunksize,
220
+ debug=debug,
221
+ **kw
222
+ )
223
+ except Exception:
224
+ result = False, traceback.format_exc()
225
+ from meerschaum.utils.formatting import get_console
226
+ if not silent:
227
+ get_console().print_exception()
228
+
229
+ ### If the chunk fails to process, try it again one more time.
230
+ if isinstance(result, tuple) and result[0] is False:
231
+ if _retry_on_failure:
232
+ return _process_chunk(_chunk, _retry_on_failure=False)
233
+
234
+ return result
235
+
207
236
  try:
208
237
  stream_results = not as_iterator and chunk_hook is not None and chunksize is not None
209
238
  with warnings.catch_warnings():
@@ -235,52 +264,32 @@ def read(
235
264
  )
236
265
  else:
237
266
 
238
- with self.engine.begin() as transaction:
239
- with transaction.execution_options(stream_results=stream_results) as connection:
240
- chunk_generator = pd.read_sql_query(
241
- formatted_query,
242
- connection,
243
- **read_sql_query_kwargs
267
+ def get_chunk_generator(connectable):
268
+ chunk_generator = pd.read_sql_query(
269
+ formatted_query,
270
+ self.engine,
271
+ **read_sql_query_kwargs
272
+ )
273
+ to_return = (
274
+ chunk_generator
275
+ if as_iterator or chunksize is None
276
+ else (
277
+ list(pool.imap(_process_chunk, chunk_generator))
278
+ if as_hook_results
279
+ else None
244
280
  )
281
+ )
282
+ return chunk_generator, to_return
283
+
284
+ if self.flavor in SKIP_READ_TRANSACTION_FLAVORS:
285
+ chunk_generator, to_return = get_chunk_generator(self.engine)
286
+ else:
287
+ with self.engine.begin() as transaction:
288
+ with transaction.execution_options(stream_results=stream_results) as connection:
289
+ chunk_generator, to_return = get_chunk_generator(connection)
245
290
 
246
- ### `stream_results` must be False (will load everything into memory).
247
- if as_iterator or chunksize is None:
248
- return chunk_generator
249
-
250
- ### We must consume the generator in this context if using server-side cursors.
251
- if stream_results:
252
-
253
- pool = get_pool(workers=workers)
254
-
255
- def _process_chunk(_chunk, _retry_on_failure: bool = True):
256
- if not as_hook_results:
257
- chunk_list.append(_chunk)
258
- result = None
259
- if chunk_hook is not None:
260
- try:
261
- result = chunk_hook(
262
- _chunk,
263
- workers = workers,
264
- chunksize = chunksize,
265
- debug = debug,
266
- **kw
267
- )
268
- except Exception as e:
269
- result = False, traceback.format_exc()
270
- from meerschaum.utils.formatting import get_console
271
- if not silent:
272
- get_console().print_exception()
273
-
274
- ### If the chunk fails to process, try it again one more time.
275
- if isinstance(result, tuple) and result[0] is False:
276
- if _retry_on_failure:
277
- return _process_chunk(_chunk, _retry_on_failure=False)
278
-
279
- return result
280
-
281
- chunk_hook_results = list(pool.imap(_process_chunk, chunk_generator))
282
- if as_hook_results:
283
- return chunk_hook_results
291
+ if to_return is not None:
292
+ return to_return
284
293
 
285
294
  except Exception as e:
286
295
  if debug:
@@ -98,6 +98,8 @@ class Pipe:
98
98
  attributes,
99
99
  parameters,
100
100
  columns,
101
+ indices,
102
+ indexes,
101
103
  dtypes,
102
104
  get_columns,
103
105
  get_columns_types,
@@ -145,6 +147,7 @@ class Pipe:
145
147
  location: Optional[str] = None,
146
148
  parameters: Optional[Dict[str, Any]] = None,
147
149
  columns: Union[Dict[str, str], List[str], None] = None,
150
+ indices: Optional[Dict[str, Union[str, List[str]]]] = None,
148
151
  tags: Optional[List[str]] = None,
149
152
  target: Optional[str] = None,
150
153
  dtypes: Optional[Dict[str, str]] = None,
@@ -156,6 +159,7 @@ class Pipe:
156
159
  connector_keys: Optional[str] = None,
157
160
  metric_key: Optional[str] = None,
158
161
  location_key: Optional[str] = None,
162
+ indexes: Union[Dict[str, str], List[str], None] = None,
159
163
  ):
160
164
  """
161
165
  Parameters
@@ -174,10 +178,14 @@ class Pipe:
174
178
  e.g. columns and other attributes.
175
179
  You can edit these parameters with `edit pipes`.
176
180
 
177
- columns: Optional[Dict[str, str]], default None
181
+ columns: Union[Dict[str, str], List[str], None], default None
178
182
  Set the `columns` dictionary of `parameters`.
179
183
  If `parameters` is also provided, this dictionary is added under the `'columns'` key.
180
184
 
185
+ indices: Optional[Dict[str, Union[str, List[str]]]], default None
186
+ Set the `indices` dictionary of `parameters`.
187
+ If `parameters` is also provided, this dictionary is added under the `'indices'` key.
188
+
181
189
  tags: Optional[List[str]], default None
182
190
  A list of strings to be added under the `'tags'` key of `parameters`.
183
191
  You can select pipes with certain tags using `--tags`.
@@ -255,6 +263,20 @@ class Pipe:
255
263
  elif columns is not None:
256
264
  warn(f"The provided columns are of invalid type '{type(columns)}'.")
257
265
 
266
+ indices = (
267
+ indices
268
+ or indexes
269
+ or self._attributes.get('parameters', {}).get('indices', None)
270
+ or self._attributes.get('parameters', {}).get('indexes', None)
271
+ ) or columns
272
+ if isinstance(indices, dict):
273
+ indices_key = (
274
+ 'indexes'
275
+ if 'indexes' in self._attributes['parameters']
276
+ else 'indices'
277
+ )
278
+ self._attributes['parameters'][indices_key] = indices
279
+
258
280
  if isinstance(tags, (list, tuple)):
259
281
  self._attributes['parameters']['tags'] = tags
260
282
  elif tags is not None:
@@ -77,15 +77,69 @@ def columns(self) -> Union[Dict[str, str], None]:
77
77
 
78
78
 
79
79
  @columns.setter
80
- def columns(self, columns: Dict[str, str]) -> None:
80
+ def columns(self, _columns: Union[Dict[str, str], List[str]]) -> None:
81
81
  """
82
82
  Override the columns dictionary of the in-memory pipe.
83
83
  Call `meerschaum.Pipe.edit()` to persist changes.
84
84
  """
85
+ if isinstance(_columns, (list, tuple)):
86
+ _columns = {col: col for col in _columns}
85
87
  if not isinstance(columns, dict):
86
- warn(f"{self}.columns must be a dictionary, received {type(columns)}")
88
+ warn(f"{self}.columns must be a dictionary, received {type(_columns)}.")
87
89
  return
88
- self.parameters['columns'] = columns
90
+ self.parameters['columns'] = _columns
91
+
92
+
93
+ @property
94
+ def indices(self) -> Union[Dict[str, Union[str, List[str]]], None]:
95
+ """
96
+ Return the `indices` dictionary defined in `meerschaum.Pipe.parameters`.
97
+ """
98
+ indices_key = (
99
+ 'indexes'
100
+ if 'indexes' in self.parameters
101
+ else 'indices'
102
+ )
103
+ if indices_key not in self.parameters:
104
+ self.parameters[indices_key] = {}
105
+ _indices = self.parameters[indices_key]
106
+ if not isinstance(_indices, dict):
107
+ _indices = {}
108
+ self.parameters[indices_key] = _indices
109
+ return {**self.columns, **_indices}
110
+
111
+
112
+ @property
113
+ def indexes(self) -> Union[Dict[str, Union[str, List[str]]], None]:
114
+ """
115
+ Alias for `meerschaum.Pipe.indices`.
116
+ """
117
+ return self.indices
118
+
119
+
120
+ @indices.setter
121
+ def indices(self, _indices: Union[Dict[str, Union[str, List[str]]], List[str]]) -> None:
122
+ """
123
+ Override the indices dictionary of the in-memory pipe.
124
+ Call `meerschaum.Pipe.edit()` to persist changes.
125
+ """
126
+ if not isinstance(_indices, dict):
127
+ warn(f"{self}.indices must be a dictionary, received {type(_indices)}.")
128
+ return
129
+ indices_key = (
130
+ 'indexes'
131
+ if 'indexes' in self.parameters
132
+ else 'indices'
133
+ )
134
+ self.parameters[indices_key] = _indices
135
+
136
+
137
+ @indexes.setter
138
+ def indexes(self, _indexes: Union[Dict[str, Union[str, List[str]]], List[str]]) -> None:
139
+ """
140
+ Alias for `meerschaum.Pipe.indices`.
141
+ """
142
+ self.indices = _indexes
89
143
 
90
144
 
91
145
  @property
@@ -415,27 +469,55 @@ def guess_datetime(self) -> Union[str, None]:
415
469
  """
416
470
  Try to determine a pipe's datetime column.
417
471
  """
418
- dtypes = self.dtypes
472
+ _dtypes = self.dtypes
419
473
 
420
474
  ### Abort if the user explictly disallows a datetime index.
421
- if 'datetime' in dtypes:
422
- if dtypes['datetime'] is None:
475
+ if 'datetime' in _dtypes:
476
+ if _dtypes['datetime'] is None:
423
477
  return None
424
478
 
479
+ from meerschaum.utils.dtypes import are_dtypes_equal
425
480
  dt_cols = [
426
- col for col, typ in self.dtypes.items()
427
- if str(typ).startswith('datetime')
481
+ col
482
+ for col, typ in _dtypes.items()
483
+ if are_dtypes_equal(typ, 'datetime')
428
484
  ]
429
485
  if not dt_cols:
430
486
  return None
431
- return dt_cols[0]
487
+ return dt_cols[0]
432
488
 
433
489
 
434
490
  def get_indices(self) -> Dict[str, str]:
435
491
  """
436
- Return a dictionary in the form of `pipe.columns` but map to index names.
437
- """
438
- return {
439
- ix: (self.target + '_' + col + '_index')
440
- for ix, col in self.columns.items() if col
492
+ Return a dictionary mapping index keys to their names on the database.
493
+
494
+ Returns
495
+ -------
496
+ A dictionary of index keys to column names.
497
+ """
498
+ _parameters = self.parameters
499
+ _index_template = _parameters.get('index_template', "IX_{target}_{column_names}")
500
+ _indices = self.indices
501
+ _target = self.target
502
+ _column_names = {
503
+ ix: (
504
+ '_'.join(cols)
505
+ if isinstance(cols, (list, tuple))
506
+ else str(cols)
507
+ )
508
+ for ix, cols in _indices.items()
509
+ if cols
510
+ }
511
+ _index_names = {
512
+ ix: (
513
+ _index_template.format(
514
+ target=_target,
515
+ column_names=column_names,
516
+ connector_keys=self.connector_keys,
517
+ metric_key=self.connector_key,
518
+ location_key=self.location_key,
519
+ )
520
+ )
521
+ for ix, column_names in _column_names.items()
441
522
  }
523
+ return _index_names
@@ -413,6 +413,7 @@ def parse_df_datetimes(
413
413
  from meerschaum.utils.packages import import_pandas, attempt_import
414
414
  from meerschaum.utils.debug import dprint
415
415
  from meerschaum.utils.warnings import warn
416
+ from meerschaum.utils.misc import items_str
416
417
  import traceback
417
418
  pd = import_pandas()
418
419
  pandas = attempt_import('pandas')
@@ -503,14 +504,14 @@ def parse_df_datetimes(
503
504
  else:
504
505
  df[datetime_cols] = df[datetime_cols].apply(
505
506
  pd.to_datetime,
506
- utc = True,
507
- axis = 1,
508
- meta = {
507
+ utc=True,
508
+ axis=1,
509
+ meta={
509
510
  col: 'datetime64[ns]'
510
511
  for col in datetime_cols
511
512
  }
512
513
  )
513
- except Exception as e:
514
+ except Exception:
514
515
  warn(
515
516
  f"Unable to apply `pd.to_datetime` to {items_str(datetime_cols)}:\n"
516
517
  + f"{traceback.format_exc()}"
@@ -519,7 +520,7 @@ def parse_df_datetimes(
519
520
  for dt in datetime_cols:
520
521
  try:
521
522
  df[dt] = df[dt].dt.tz_localize(None)
522
- except Exception as e:
523
+ except Exception:
523
524
  warn(f"Unable to convert column '{dt}' to naive datetime:\n{traceback.format_exc()}")
524
525
 
525
526
  return df
@@ -567,6 +568,9 @@ def get_json_cols(df: 'pd.DataFrame') -> List[str]:
567
568
  -------
568
569
  A list of columns to be encoded as JSON.
569
570
  """
571
+ if df is None:
572
+ return []
573
+
570
574
  is_dask = 'dask' in df.__module__ if hasattr(df, '__module__') else False
571
575
  if is_dask:
572
576
  df = get_first_valid_dask_partition(df)
@@ -602,6 +606,8 @@ def get_numeric_cols(df: 'pd.DataFrame') -> List[str]:
602
606
  -------
603
607
  A list of columns to treat as numerics.
604
608
  """
609
+ if df is None:
610
+ return []
605
611
  from decimal import Decimal
606
612
  is_dask = 'dask' in df.__module__
607
613
  if is_dask:
@@ -638,6 +644,8 @@ def get_uuid_cols(df: 'pd.DataFrame') -> List[str]:
638
644
  -------
639
645
  A list of columns to treat as numerics.
640
646
  """
647
+ if df is None:
648
+ return []
641
649
  from uuid import UUID
642
650
  is_dask = 'dask' in df.__module__
643
651
  if is_dask:
@@ -883,6 +891,8 @@ def get_datetime_bound_from_df(
883
891
  -------
884
892
  The minimum or maximum datetime value in the dataframe, or `None`.
885
893
  """
894
+ if df is None:
895
+ return None
886
896
  if not datetime_column:
887
897
  return None
888
898
 
@@ -955,6 +965,8 @@ def get_unique_index_values(
955
965
  -------
956
966
  A dictionary mapping indices to unique values.
957
967
  """
968
+ if df is None:
969
+ return {}
958
970
  if 'dataframe' in str(type(df)).lower():
959
971
  pandas = mrsm.attempt_import('pandas')
960
972
  return {
@@ -11,7 +11,12 @@ from __future__ import annotations
11
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
- from meerschaum.utils.packages._packages import packages, all_packages, get_install_names
14
+ from meerschaum.utils.packages._packages import (
15
+ packages,
16
+ all_packages,
17
+ get_install_names,
18
+ _MRSM_PACKAGE_ARCHIVES_PREFIX,
19
+ )
15
20
  from meerschaum.utils.venv import (
16
21
  activate_venv,
17
22
  deactivate_venv,
@@ -35,14 +40,14 @@ _locks = {
35
40
  }
36
41
  _checked_for_updates = set()
37
42
  _is_installed_first_check: Dict[str, bool] = {}
38
- _MRSM_PACKAGE_ARCHIVES_PREFIX: str = "https://meerschaum.io/files/archives/"
43
+
39
44
 
40
45
  def get_module_path(
41
- import_name: str,
42
- venv: Optional[str] = 'mrsm',
43
- debug: bool = False,
44
- _try_install_name_on_fail: bool = True,
45
- ) -> Union[pathlib.Path, None]:
46
+ import_name: str,
47
+ venv: Optional[str] = 'mrsm',
48
+ debug: bool = False,
49
+ _try_install_name_on_fail: bool = True,
50
+ ) -> Union[pathlib.Path, None]:
46
51
  """
47
52
  Get a module's path without importing.
48
53
  """
@@ -232,10 +237,10 @@ def manually_import_module(
232
237
  if check_update:
233
238
  if need_update(
234
239
  None,
235
- import_name = root_name,
236
- version = _version,
237
- check_pypi = check_pypi,
238
- debug = debug,
240
+ import_name=root_name,
241
+ version=_version,
242
+ check_pypi=check_pypi,
243
+ debug=debug,
239
244
  ):
240
245
  if install:
241
246
  if not pip_install(
@@ -491,6 +496,8 @@ def _get_package_metadata(import_name: str, venv: Optional[str]) -> Dict[str, st
491
496
  import re
492
497
  from meerschaum.config._paths import VIRTENV_RESOURCES_PATH
493
498
  install_name = _import_to_install_name(import_name)
499
+ if install_name.startswith(_MRSM_PACKAGE_ARCHIVES_PREFIX):
500
+ return {}
494
501
  _args = ['pip', 'show', install_name]
495
502
  if venv is not None:
496
503
  cache_dir_path = VIRTENV_RESOURCES_PATH / venv / 'cache'
@@ -586,7 +593,10 @@ def need_update(
586
593
  _checked_for_updates.add(install_name)
587
594
 
588
595
  _install_no_version = get_install_no_version(install_name)
589
- required_version = install_name.replace(_install_no_version, '')
596
+ required_version = (
597
+ install_name
598
+ .replace(_install_no_version, '')
599
+ )
590
600
  if ']' in required_version:
591
601
  required_version = required_version.split(']')[1]
592
602
 
@@ -681,8 +691,8 @@ def need_update(
681
691
  )
682
692
 
683
693
  if 'a' in required_version:
684
- required_version = required_version.replace('a', '-dev').replace('+mrsm', '')
685
- version = version.replace('a', '-dev').replace('+mrsm', '')
694
+ required_version = required_version.replace('a', '-pre.').replace('+mrsm', '')
695
+ version = version.replace('a', '-pre.').replace('+mrsm', '')
686
696
  try:
687
697
  return (
688
698
  (not semver.Version.parse(version).match(required_version))
@@ -780,7 +790,7 @@ def pip_install(
780
790
  This includes version restrictions.
781
791
  Use `_import_to_install_name()` to get the predefined `install_name` for a package
782
792
  from its import name.
783
-
793
+
784
794
  args: Optional[List[str]], default None
785
795
  A list of command line arguments to pass to `pip`.
786
796
  If not provided, default to `['--upgrade']` if `_uninstall` is `False`, else `[]`.
@@ -975,7 +985,11 @@ def pip_install(
975
985
  pass
976
986
 
977
987
  _packages = [
978
- (install_name if not _uninstall else get_install_no_version(install_name))
988
+ (
989
+ get_install_no_version(install_name)
990
+ if _uninstall or install_name.startswith(_MRSM_PACKAGE_ARCHIVES_PREFIX)
991
+ else install_name
992
+ )
979
993
  for install_name in install_names
980
994
  ]
981
995
  msg = "Installing packages:" if not _uninstall else "Uninstalling packages:"
@@ -1774,13 +1788,17 @@ def is_installed(
1774
1788
 
1775
1789
  found = (
1776
1790
  not need_update(
1777
- None, import_name = root_name,
1778
- _run_determine_version = False,
1779
- check_pypi = False,
1780
- version = determine_version(
1781
- spec_path, venv=venv, debug=debug, import_name=root_name
1791
+ None,
1792
+ import_name=root_name,
1793
+ _run_determine_version=False,
1794
+ check_pypi=False,
1795
+ version=determine_version(
1796
+ spec_path,
1797
+ venv=venv,
1798
+ debug=debug,
1799
+ import_name=root_name,
1782
1800
  ),
1783
- debug = debug,
1801
+ debug=debug,
1784
1802
  )
1785
1803
  ) if spec_path is not None else False
1786
1804
 
@@ -16,9 +16,10 @@ packages dictionary is structured in the following schema:
16
16
  from __future__ import annotations
17
17
  from meerschaum.utils.typing import Dict
18
18
 
19
+ _MRSM_PACKAGE_ARCHIVES_PREFIX: str = "https://meerschaum.io/files/archives/wheels/"
20
+
19
21
  packages: Dict[str, Dict[str, str]] = {
20
- 'required': {
21
- },
22
+ 'required': {},
22
23
  'minimal': {},
23
24
  'formatting': {
24
25
  'pprintpp' : 'pprintpp>=0.4.0',
@@ -36,7 +37,7 @@ packages: Dict[str, Dict[str, str]] = {
36
37
  'yaml' : 'PyYAML>=5.3.1',
37
38
  'pip' : 'pip>=22.0.4',
38
39
  'update_checker' : 'update-checker>=0.18.0',
39
- 'semver' : 'semver>=3.0.0',
40
+ 'semver' : 'semver>=3.0.2',
40
41
  'pathspec' : 'pathspec>=0.9.0',
41
42
  'dateutil' : 'python-dateutil>=2.7.5',
42
43
  'requests' : 'requests>=2.32.3',
@@ -51,10 +52,15 @@ packages: Dict[str, Dict[str, str]] = {
51
52
  'more_itertools' : 'more-itertools>=8.7.0',
52
53
  'fasteners' : 'fasteners>=0.19.0',
53
54
  'virtualenv' : 'virtualenv>=20.1.0',
54
- 'attrs' : 'attrs<24.2.0',
55
- 'apscheduler' : 'APScheduler>=4.0.0a5',
55
+ 'attrs' : 'attrs>=24.2.0',
56
56
  'uv' : 'uv>=0.2.11',
57
57
  },
58
+ '_internal' : {
59
+ 'apscheduler' : (
60
+ f"{_MRSM_PACKAGE_ARCHIVES_PREFIX}"
61
+ "APScheduler-4.0.0a5.post75+mrsm-py3-none-any.whl>=4.0.0a5"
62
+ ),
63
+ },
58
64
  'jobs': {
59
65
  'dill' : 'dill>=0.3.3',
60
66
  'daemon' : 'python-daemon>=0.2.3',
@@ -179,15 +185,25 @@ def get_install_names():
179
185
  install_names[get_install_no_version(_install_name)] = _import_name
180
186
  return install_names
181
187
 
182
- skip_groups = {'docs', 'build', 'cli', 'dev-tools', 'portable', 'extras', 'stack', 'drivers-extras'}
188
+
189
+ skip_groups = {
190
+ 'docs',
191
+ 'build',
192
+ 'cli',
193
+ 'dev-tools',
194
+ 'portable',
195
+ 'extras',
196
+ 'stack',
197
+ 'drivers-extras',
198
+ '_internal',
199
+ }
183
200
  full = []
184
201
  _full = {}
185
202
  for group, import_names in packages.items():
186
203
  ### omit 'cli' and 'docs' from 'full'
187
- if group in skip_groups:
204
+ if group in skip_groups or group.startswith('_'):
188
205
  continue
189
206
  full += [ install_name for import_name, install_name in import_names.items() ]
190
207
  for import_name, install_name in import_names.items():
191
208
  _full[import_name] = install_name
192
209
  packages['full'] = _full
193
-
@@ -140,7 +140,7 @@ def parse_schedule(schedule: str, now: Optional[datetime] = None):
140
140
  """
141
141
  Parse a schedule string (e.g. 'daily') into a Trigger object.
142
142
  """
143
- from meerschaum.utils.misc import items_str, is_int
143
+ from meerschaum.utils.misc import items_str, is_int, filter_keywords
144
144
  (
145
145
  apscheduler_triggers_cron,
146
146
  apscheduler_triggers_interval,
@@ -204,10 +204,14 @@ def parse_schedule(schedule: str, now: Optional[datetime] = None):
204
204
 
205
205
  trigger = (
206
206
  apscheduler_triggers_interval.IntervalTrigger(
207
- **{
208
- schedule_unit: schedule_num,
209
- 'start_time': starting_ts,
210
- }
207
+ **filter_keywords(
208
+ apscheduler_triggers_interval.IntervalTrigger.__init__,
209
+ **{
210
+ schedule_unit: schedule_num,
211
+ 'start_time': starting_ts,
212
+ 'start_date': starting_ts,
213
+ }
214
+ )
211
215
  )
212
216
  if schedule_unit not in ('months', 'years') else (
213
217
  apscheduler_triggers_calendarinterval.CalendarIntervalTrigger(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: meerschaum
3
- Version: 2.4.13
3
+ Version: 2.5.0
4
4
  Summary: Sync Time-Series Pipes with Meerschaum
5
5
  Home-page: https://meerschaum.io
6
6
  Author: Bennett Meares
@@ -64,7 +64,7 @@ Requires-Dist: setuptools >=63.3.0 ; extra == 'api'
64
64
  Requires-Dist: PyYAML >=5.3.1 ; extra == 'api'
65
65
  Requires-Dist: pip >=22.0.4 ; extra == 'api'
66
66
  Requires-Dist: update-checker >=0.18.0 ; extra == 'api'
67
- Requires-Dist: semver >=3.0.0 ; extra == 'api'
67
+ Requires-Dist: semver >=3.0.2 ; extra == 'api'
68
68
  Requires-Dist: pathspec >=0.9.0 ; extra == 'api'
69
69
  Requires-Dist: python-dateutil >=2.7.5 ; extra == 'api'
70
70
  Requires-Dist: requests >=2.32.3 ; extra == 'api'
@@ -79,8 +79,7 @@ Requires-Dist: prompt-toolkit >=3.0.39 ; extra == 'api'
79
79
  Requires-Dist: more-itertools >=8.7.0 ; extra == 'api'
80
80
  Requires-Dist: fasteners >=0.19.0 ; extra == 'api'
81
81
  Requires-Dist: virtualenv >=20.1.0 ; extra == 'api'
82
- Requires-Dist: attrs <24.2.0 ; extra == 'api'
83
- Requires-Dist: APScheduler >=4.0.0a5 ; extra == 'api'
82
+ Requires-Dist: attrs >=24.2.0 ; extra == 'api'
84
83
  Requires-Dist: uv >=0.2.11 ; extra == 'api'
85
84
  Requires-Dist: pprintpp >=0.4.0 ; extra == 'api'
86
85
  Requires-Dist: asciitree >=0.3.3 ; extra == 'api'
@@ -117,7 +116,7 @@ Requires-Dist: setuptools >=63.3.0 ; extra == 'core'
117
116
  Requires-Dist: PyYAML >=5.3.1 ; extra == 'core'
118
117
  Requires-Dist: pip >=22.0.4 ; extra == 'core'
119
118
  Requires-Dist: update-checker >=0.18.0 ; extra == 'core'
120
- Requires-Dist: semver >=3.0.0 ; extra == 'core'
119
+ Requires-Dist: semver >=3.0.2 ; extra == 'core'
121
120
  Requires-Dist: pathspec >=0.9.0 ; extra == 'core'
122
121
  Requires-Dist: python-dateutil >=2.7.5 ; extra == 'core'
123
122
  Requires-Dist: requests >=2.32.3 ; extra == 'core'
@@ -132,8 +131,7 @@ Requires-Dist: prompt-toolkit >=3.0.39 ; extra == 'core'
132
131
  Requires-Dist: more-itertools >=8.7.0 ; extra == 'core'
133
132
  Requires-Dist: fasteners >=0.19.0 ; extra == 'core'
134
133
  Requires-Dist: virtualenv >=20.1.0 ; extra == 'core'
135
- Requires-Dist: attrs <24.2.0 ; extra == 'core'
136
- Requires-Dist: APScheduler >=4.0.0a5 ; extra == 'core'
134
+ Requires-Dist: attrs >=24.2.0 ; extra == 'core'
137
135
  Requires-Dist: uv >=0.2.11 ; extra == 'core'
138
136
  Provides-Extra: dash
139
137
  Requires-Dist: Flask-Compress >=1.10.1 ; extra == 'dash'
@@ -203,7 +201,7 @@ Requires-Dist: setuptools >=63.3.0 ; extra == 'full'
203
201
  Requires-Dist: PyYAML >=5.3.1 ; extra == 'full'
204
202
  Requires-Dist: pip >=22.0.4 ; extra == 'full'
205
203
  Requires-Dist: update-checker >=0.18.0 ; extra == 'full'
206
- Requires-Dist: semver >=3.0.0 ; extra == 'full'
204
+ Requires-Dist: semver >=3.0.2 ; extra == 'full'
207
205
  Requires-Dist: pathspec >=0.9.0 ; extra == 'full'
208
206
  Requires-Dist: python-dateutil >=2.7.5 ; extra == 'full'
209
207
  Requires-Dist: requests >=2.32.3 ; extra == 'full'
@@ -218,8 +216,7 @@ Requires-Dist: prompt-toolkit >=3.0.39 ; extra == 'full'
218
216
  Requires-Dist: more-itertools >=8.7.0 ; extra == 'full'
219
217
  Requires-Dist: fasteners >=0.19.0 ; extra == 'full'
220
218
  Requires-Dist: virtualenv >=20.1.0 ; extra == 'full'
221
- Requires-Dist: attrs <24.2.0 ; extra == 'full'
222
- Requires-Dist: APScheduler >=4.0.0a5 ; extra == 'full'
219
+ Requires-Dist: attrs >=24.2.0 ; extra == 'full'
223
220
  Requires-Dist: uv >=0.2.11 ; extra == 'full'
224
221
  Requires-Dist: dill >=0.3.3 ; extra == 'full'
225
222
  Requires-Dist: python-daemon >=0.2.3 ; extra == 'full'
@@ -299,7 +296,7 @@ Requires-Dist: setuptools >=63.3.0 ; extra == 'sql'
299
296
  Requires-Dist: PyYAML >=5.3.1 ; extra == 'sql'
300
297
  Requires-Dist: pip >=22.0.4 ; extra == 'sql'
301
298
  Requires-Dist: update-checker >=0.18.0 ; extra == 'sql'
302
- Requires-Dist: semver >=3.0.0 ; extra == 'sql'
299
+ Requires-Dist: semver >=3.0.2 ; extra == 'sql'
303
300
  Requires-Dist: pathspec >=0.9.0 ; extra == 'sql'
304
301
  Requires-Dist: python-dateutil >=2.7.5 ; extra == 'sql'
305
302
  Requires-Dist: requests >=2.32.3 ; extra == 'sql'
@@ -314,8 +311,7 @@ Requires-Dist: prompt-toolkit >=3.0.39 ; extra == 'sql'
314
311
  Requires-Dist: more-itertools >=8.7.0 ; extra == 'sql'
315
312
  Requires-Dist: fasteners >=0.19.0 ; extra == 'sql'
316
313
  Requires-Dist: virtualenv >=20.1.0 ; extra == 'sql'
317
- Requires-Dist: attrs <24.2.0 ; extra == 'sql'
318
- Requires-Dist: APScheduler >=4.0.0a5 ; extra == 'sql'
314
+ Requires-Dist: attrs >=24.2.0 ; extra == 'sql'
319
315
  Requires-Dist: uv >=0.2.11 ; extra == 'sql'
320
316
  Provides-Extra: stack
321
317
  Requires-Dist: docker-compose >=1.29.2 ; extra == 'stack'
@@ -49,7 +49,7 @@ meerschaum/actions/stop.py,sha256=5fdUw70YN-yuUWrC-NhA88cxr9FZ5NbssbQ8xXO8nFU,46
49
49
  meerschaum/actions/sync.py,sha256=uXneLSykNH0LapE4kn_9fzwUQ-kJcRUzJ42pyuoFNaI,17161
50
50
  meerschaum/actions/tag.py,sha256=SJf5qFW0ccLXjqlTdkK_0MCcrCMdg6xhYrhKdco0hdA,3053
51
51
  meerschaum/actions/uninstall.py,sha256=tBXhdXggSieGEQe4EPGxpgMK0MZTJCweNvAJ9-59El0,5776
52
- meerschaum/actions/upgrade.py,sha256=1oXzK05WH-l8wq8Dxidr7c0hn7UQa3SuwRk0yyQH5vI,6462
52
+ meerschaum/actions/upgrade.py,sha256=AjuC93Te-I_GWwIfuNkFJ2q1zVHDQ2Oco34S4JgK2iA,6485
53
53
  meerschaum/actions/verify.py,sha256=tY5slGpHiWiE0v9TDnjbmxSKn86zBnu9WBpixUgKNQU,4885
54
54
  meerschaum/api/__init__.py,sha256=5q2XelZvqZSyfCyGIf1mlrWVxrsDaG2_I4L5ACeqMso,8456
55
55
  meerschaum/api/_chain.py,sha256=h8-WXUGXX6AqzdALfsBC5uv0FkAcLdHJXCGzqzuq89k,875
@@ -143,7 +143,7 @@ meerschaum/config/_preprocess.py,sha256=-AEA8m_--KivZwTQ1sWN6LTn5sio_fUr2XZ51BO6
143
143
  meerschaum/config/_read_config.py,sha256=oxnLjuhy6JBBld886FkBX07wUdkpzEzTItYMUa9qw1Q,14688
144
144
  meerschaum/config/_shell.py,sha256=46_m49Txc5q1rGfCgO49ca48BODx45DQJi8D0zz1R18,4245
145
145
  meerschaum/config/_sync.py,sha256=jHcWRkxd82_BgX8Xo8agsWvf7BSbv3qHLWmYl6ehp_0,4242
146
- meerschaum/config/_version.py,sha256=kz1QZdlaaIVseDrdmhCpAtbsywLZfvnZsEoYBk9hyyE,72
146
+ meerschaum/config/_version.py,sha256=wtE11BYLrlYRUUbwWnx12ho0mgFDv-guHaABMWLfMLY,71
147
147
  meerschaum/config/paths.py,sha256=JjibeGN3YAdSNceRwsd42aNmeUrIgM6ndzC8qZAmNI0,621
148
148
  meerschaum/config/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
149
149
  meerschaum/config/stack/__init__.py,sha256=gGVxXgNnGb9u25iF__IiNPlZt1BLUVmHmFJ0jvnJg3Q,10548
@@ -173,12 +173,12 @@ meerschaum/connectors/plugin/__init__.py,sha256=pwF7TGY4WNz2_HaVdmK4rPQ9ZwTOEuPH
173
173
  meerschaum/connectors/sql/_SQLConnector.py,sha256=QlC2Af7AM7bIlPHjUO57mTyzot3zU7o83jegADMxnBE,11829
174
174
  meerschaum/connectors/sql/__init__.py,sha256=3cqYiDkVasn7zWdtOTAZbT4bo95AuvGOmDD2TkaAxtw,205
175
175
  meerschaum/connectors/sql/_cli.py,sha256=1SgnWeMIAihoxp4FzbNrcq1npXf0dSOQnCntpU9hUXA,4405
176
- meerschaum/connectors/sql/_create_engine.py,sha256=pZPjy-ne8DtVfu-wqMJopIGkgm8vul-y3E9d4tUyTgM,10215
177
- meerschaum/connectors/sql/_fetch.py,sha256=4-Bc4X8dFo6bikVGE5X16s4qmdqbN7NpGPeYB3MlqAA,13014
178
- meerschaum/connectors/sql/_instance.py,sha256=zXPZnEqvOAeOUPMeh6CcfkB1pOjjwJxdUOwXccRbuwk,6465
179
- meerschaum/connectors/sql/_pipes.py,sha256=-jbuBjZ8EET2bkhgAWQmqci5FNcBCZUqnOcF_tuHq8M,101055
176
+ meerschaum/connectors/sql/_create_engine.py,sha256=zqeu1xHOw3n3Zgfjx-diy2aoynfdOlfOjwFuRrzB028,10452
177
+ meerschaum/connectors/sql/_fetch.py,sha256=J5e9EZrb70neXq8p78LFTTJiVukMIEKHuTXlILHDDGI,13005
178
+ meerschaum/connectors/sql/_instance.py,sha256=3KJI3ImwWAJkUfdZIrSL24pcW6Nic8wo5IUeGth9HP4,6459
179
+ meerschaum/connectors/sql/_pipes.py,sha256=8qoiv4nmvU4TVMTtvzWbPcqhq-izZ-C2338t4NFGQ0I,101302
180
180
  meerschaum/connectors/sql/_plugins.py,sha256=wbxcNxqTtjfDsxPvdVGTllasYf6NHHzODaQ72hEUSBQ,8135
181
- meerschaum/connectors/sql/_sql.py,sha256=zEh6fbOLJhfLOF-4x9OTQ5Fi3NMSVES3oixmnGYcNG8,36381
181
+ meerschaum/connectors/sql/_sql.py,sha256=gw8dy6-s8VqxANO9-tXIXFqCwPT_GrktG81AAQgqmPE,36032
182
182
  meerschaum/connectors/sql/_uri.py,sha256=0BrhQtqQdzg9mR04gWBZINs_BbPFtSlTECXT_TCUwik,3460
183
183
  meerschaum/connectors/sql/_users.py,sha256=FJjYeJGhr-TDHziNc6p_5mupGRtGjezKPIYgHFEVSnY,9956
184
184
  meerschaum/connectors/sql/tools.py,sha256=jz8huOaRCwGlYdtGfAqAh7SoK8uydYBrasKQba9FT38,187
@@ -191,8 +191,8 @@ meerschaum/connectors/valkey/_pipes.py,sha256=7GNDGoVDIULyGUjUisckErvRlrcnN4WkML
191
191
  meerschaum/connectors/valkey/_plugins.py,sha256=ZqiEW4XZCOpw4G8DUK2IKY6Qrph4mYfTjgXWimgakYY,6267
192
192
  meerschaum/connectors/valkey/_users.py,sha256=AS1vLarrkDA9yPK644GWwRiQiTZVa9x3nlLpyntq40g,7730
193
193
  meerschaum/core/__init__.py,sha256=tjASW10n9uLV6bYhcwP4rggh-ESXSJzgxpSBbVsuISs,251
194
- meerschaum/core/Pipe/__init__.py,sha256=YyLs2ZlvRkyHKQechD6Hs4VaSDgpIbgbnhr8VxG43k4,16737
195
- meerschaum/core/Pipe/_attributes.py,sha256=yAHYvizRSkqqkIRcaGAYvUjE2qe5H4xNKBmxUegLrPk,13143
194
+ meerschaum/core/Pipe/__init__.py,sha256=2dOEf8e3uwkwIac2sMbu3RZDyAAFzD7h6z9X-Lyb1Ds,17649
195
+ meerschaum/core/Pipe/_attributes.py,sha256=tB6v-KT7CbBaNTvgLbYFMpE4M73nicql1KWO9QT2ctg,15490
196
196
  meerschaum/core/Pipe/_bootstrap.py,sha256=evyi07kkzAVMj66HfZkbYdcWk_oHUDsl6f13EnSPMYs,7723
197
197
  meerschaum/core/Pipe/_clear.py,sha256=yFAYQnDmL3m6DzyAutgnBDXSOL9gjIrLvlQXd86YFV0,2193
198
198
  meerschaum/core/Pipe/_copy.py,sha256=PcOeNUb0B4-HehTAAnBpjyT4uLLTR6VQOarY0SFA9wk,2912
@@ -219,7 +219,7 @@ meerschaum/plugins/__init__.py,sha256=6krcqaMKyzuVqesXMqEL0XEy2SJQ4xfNt2-oI_fJ6v
219
219
  meerschaum/plugins/bootstrap.py,sha256=VwjpZAuYdqPJW0YoVgAoM_taHkdQHqP902-8T7OWWCI,11339
220
220
  meerschaum/utils/__init__.py,sha256=QrK1K9hIbPCRCM5k2nZGFqGnrqhA0Eh-iSmCU7FG6Cs,612
221
221
  meerschaum/utils/_get_pipes.py,sha256=tu4xKPoDn79Dz2kWM13cXTP4DSCkn-3G9M8KiLftopw,11073
222
- meerschaum/utils/dataframe.py,sha256=qV_6ecpxMIZOoiUb0pIk8u5ZPBn-h6O1yS_DtdLFX_U,41937
222
+ meerschaum/utils/dataframe.py,sha256=Sg4tUZbqgstwQnnnNR-zALzxJ6kRMeej_4n29HC9pWU,42157
223
223
  meerschaum/utils/debug.py,sha256=GyIzJmunkoPnOcZNYVQdT4Sgd-aOb5MI2VbIgATOjIQ,3695
224
224
  meerschaum/utils/interactive.py,sha256=t-6jWozXSqL7lYGDHuwiOjTgr-UKhdcg61q_eR5mikI,3196
225
225
  meerschaum/utils/misc.py,sha256=OijhS1TMjlqkDvahbxhqfUdo0Myeor-kTKrvqqG8wN0,46349
@@ -227,7 +227,7 @@ meerschaum/utils/networking.py,sha256=Sr_eYUGW8_UV9-k9LqRFf7xLtbUcsDucODyLCRsFRU
227
227
  meerschaum/utils/pool.py,sha256=vkE42af4fjrTEJTxf6Ek3xGucm1MtEkpsSEiaVzNKHs,2655
228
228
  meerschaum/utils/process.py,sha256=9O8PPPJjY9Q5W2f39I3B3lFU6TlSiRiI3bgrzdOOyOw,7843
229
229
  meerschaum/utils/prompt.py,sha256=6J--mZJ_NcEdSX6KMjtY4fXXezyILLHP24VdxFFqOIc,18985
230
- meerschaum/utils/schedule.py,sha256=6I2TFGa1aPRU9wTdd3YFrJq-DCPpnl-sTWeFEnrINYA,10886
230
+ meerschaum/utils/schedule.py,sha256=zKOtfVdr1QRSZYBOSrO2MRIz8DTMbkWfQA6CjahbSso,11115
231
231
  meerschaum/utils/sql.py,sha256=R_hX92brvZDqID7c85-PNUVVR6RWXGXEgn1vFpHmi88,49869
232
232
  meerschaum/utils/threading.py,sha256=3N8JXPAnwqJiSjuQcbbJg3Rv9-CCUMJpeQRfKFR7MaA,2489
233
233
  meerschaum/utils/typing.py,sha256=U3MC347sh1umpa3Xr1k71eADyDmk4LB6TnVCpq8dVzI,2830
@@ -246,16 +246,16 @@ meerschaum/utils/formatting/_jobs.py,sha256=izsqPJhTtUkXUUtWnbXtReYsUYwulXtci3pB
246
246
  meerschaum/utils/formatting/_pipes.py,sha256=840O5rg2aHhQoraCDOh2ZtBo43_W2W6R60yYufEoXp8,19494
247
247
  meerschaum/utils/formatting/_pprint.py,sha256=tgrT3FyGyu5CWJYysqK3kX1xdZYorlbOk9fcU_vt9Qg,3096
248
248
  meerschaum/utils/formatting/_shell.py,sha256=XH7VFLteNv7NGtWhJl7FdIGt80sKeTiDoJokGSDAwBM,3761
249
- meerschaum/utils/packages/__init__.py,sha256=m3HLTkKJxXco1g-h75q2l5skBwKXWaJtNmfQOsijchI,63965
250
- meerschaum/utils/packages/_packages.py,sha256=8Ox9fiQTVmmKAmlxZBV-7Wtq_jhPnnf3AMXvktGE-KY,8319
249
+ meerschaum/utils/packages/__init__.py,sha256=KNs1Qc8-DX7r6LlZxyl9-Qr2FnsCtZfjSbiwo-sGNf8,64196
250
+ meerschaum/utils/packages/_packages.py,sha256=L8G9mQtiDyFp2pKesHufZrtbPAty0DH4u8k2wmCTxVY,8687
251
251
  meerschaum/utils/packages/lazy_loader.py,sha256=VHnph3VozH29R4JnSSBfwtA5WKZYZQFT_GeQSShCnuc,2540
252
252
  meerschaum/utils/venv/_Venv.py,sha256=sBnlmxHdAh2bx8btfVoD79-H9-cYsv5lP02IIXkyECs,3553
253
253
  meerschaum/utils/venv/__init__.py,sha256=f3oi67lXYPLKJrnRW9lae7M3A8SFiC7DzaMoBdCVUFs,24609
254
- meerschaum-2.4.13.dist-info/LICENSE,sha256=jG2zQEdRNt88EgHUWPpXVWmOrOduUQRx7MnYV9YIPaw,11359
255
- meerschaum-2.4.13.dist-info/METADATA,sha256=y0OKNCTNx9iyL9PI80-yYa_Yg9SYcSK8Td172EBl5Fo,24820
256
- meerschaum-2.4.13.dist-info/NOTICE,sha256=OTA9Fcthjf5BRvWDDIcBC_xfLpeDV-RPZh3M-HQBRtQ,114
257
- meerschaum-2.4.13.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
258
- meerschaum-2.4.13.dist-info/entry_points.txt,sha256=5YBVzibw-0rNA_1VjB16z5GABsOGf-CDhW4yqH8C7Gc,88
259
- meerschaum-2.4.13.dist-info/top_level.txt,sha256=bNoSiDj0El6buocix-FRoAtJOeq1qOF5rRm2u9i7Q6A,11
260
- meerschaum-2.4.13.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
261
- meerschaum-2.4.13.dist-info/RECORD,,
254
+ meerschaum-2.5.0.dist-info/LICENSE,sha256=jG2zQEdRNt88EgHUWPpXVWmOrOduUQRx7MnYV9YIPaw,11359
255
+ meerschaum-2.5.0.dist-info/METADATA,sha256=oUofV2VmX4Ip1K7Sg8Hj-H0JYCwJjfMcLWSJPkLm0-s,24605
256
+ meerschaum-2.5.0.dist-info/NOTICE,sha256=OTA9Fcthjf5BRvWDDIcBC_xfLpeDV-RPZh3M-HQBRtQ,114
257
+ meerschaum-2.5.0.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
258
+ meerschaum-2.5.0.dist-info/entry_points.txt,sha256=5YBVzibw-0rNA_1VjB16z5GABsOGf-CDhW4yqH8C7Gc,88
259
+ meerschaum-2.5.0.dist-info/top_level.txt,sha256=bNoSiDj0El6buocix-FRoAtJOeq1qOF5rRm2u9i7Q6A,11
260
+ meerschaum-2.5.0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
261
+ meerschaum-2.5.0.dist-info/RECORD,,