meerschaum 2.9.5__py3-none-any.whl → 3.0.0rc2__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 (158) hide show
  1. meerschaum/__init__.py +5 -2
  2. meerschaum/_internal/__init__.py +1 -0
  3. meerschaum/_internal/arguments/_parse_arguments.py +4 -4
  4. meerschaum/_internal/arguments/_parser.py +19 -2
  5. meerschaum/_internal/docs/index.py +49 -2
  6. meerschaum/_internal/entry.py +6 -6
  7. meerschaum/_internal/shell/Shell.py +1 -1
  8. meerschaum/_internal/static.py +356 -0
  9. meerschaum/actions/api.py +12 -2
  10. meerschaum/actions/bootstrap.py +7 -7
  11. meerschaum/actions/edit.py +142 -18
  12. meerschaum/actions/register.py +137 -6
  13. meerschaum/actions/show.py +117 -29
  14. meerschaum/actions/stop.py +4 -1
  15. meerschaum/actions/sync.py +1 -1
  16. meerschaum/actions/tag.py +9 -8
  17. meerschaum/actions/verify.py +5 -8
  18. meerschaum/api/__init__.py +11 -3
  19. meerschaum/api/_events.py +39 -2
  20. meerschaum/api/_oauth2.py +118 -8
  21. meerschaum/api/_tokens.py +102 -0
  22. meerschaum/api/dash/__init__.py +0 -3
  23. meerschaum/api/dash/callbacks/custom.py +2 -2
  24. meerschaum/api/dash/callbacks/dashboard.py +103 -19
  25. meerschaum/api/dash/callbacks/plugins.py +0 -1
  26. meerschaum/api/dash/callbacks/register.py +1 -1
  27. meerschaum/api/dash/callbacks/settings/__init__.py +1 -0
  28. meerschaum/api/dash/callbacks/settings/password_reset.py +2 -2
  29. meerschaum/api/dash/callbacks/settings/tokens.py +388 -0
  30. meerschaum/api/dash/components.py +30 -8
  31. meerschaum/api/dash/keys.py +19 -93
  32. meerschaum/api/dash/pages/dashboard.py +1 -20
  33. meerschaum/api/dash/pages/settings/__init__.py +1 -0
  34. meerschaum/api/dash/pages/settings/password_reset.py +1 -1
  35. meerschaum/api/dash/pages/settings/tokens.py +55 -0
  36. meerschaum/api/dash/pipes.py +94 -59
  37. meerschaum/api/dash/sessions.py +12 -0
  38. meerschaum/api/dash/tokens.py +606 -0
  39. meerschaum/api/dash/websockets.py +1 -1
  40. meerschaum/api/dash/webterm.py +4 -0
  41. meerschaum/api/models/__init__.py +23 -3
  42. meerschaum/api/models/_actions.py +22 -0
  43. meerschaum/api/models/_pipes.py +85 -7
  44. meerschaum/api/models/_tokens.py +81 -0
  45. meerschaum/api/resources/templates/termpage.html +12 -0
  46. meerschaum/api/routes/__init__.py +1 -0
  47. meerschaum/api/routes/_actions.py +3 -4
  48. meerschaum/api/routes/_connectors.py +3 -7
  49. meerschaum/api/routes/_jobs.py +14 -35
  50. meerschaum/api/routes/_login.py +49 -12
  51. meerschaum/api/routes/_misc.py +5 -10
  52. meerschaum/api/routes/_pipes.py +173 -140
  53. meerschaum/api/routes/_plugins.py +38 -28
  54. meerschaum/api/routes/_tokens.py +236 -0
  55. meerschaum/api/routes/_users.py +47 -35
  56. meerschaum/api/routes/_version.py +3 -3
  57. meerschaum/config/__init__.py +43 -20
  58. meerschaum/config/_default.py +43 -6
  59. meerschaum/config/_edit.py +28 -24
  60. meerschaum/config/_environment.py +1 -1
  61. meerschaum/config/_patch.py +6 -6
  62. meerschaum/config/_paths.py +5 -1
  63. meerschaum/config/_read_config.py +65 -34
  64. meerschaum/config/_sync.py +6 -3
  65. meerschaum/config/_version.py +1 -1
  66. meerschaum/config/stack/__init__.py +31 -11
  67. meerschaum/config/static.py +18 -0
  68. meerschaum/connectors/_Connector.py +10 -4
  69. meerschaum/connectors/__init__.py +4 -20
  70. meerschaum/connectors/api/_APIConnector.py +34 -6
  71. meerschaum/connectors/api/_actions.py +2 -2
  72. meerschaum/connectors/api/_jobs.py +1 -1
  73. meerschaum/connectors/api/_login.py +33 -7
  74. meerschaum/connectors/api/_misc.py +2 -2
  75. meerschaum/connectors/api/_pipes.py +16 -31
  76. meerschaum/connectors/api/_plugins.py +2 -2
  77. meerschaum/connectors/api/_request.py +1 -1
  78. meerschaum/connectors/api/_tokens.py +146 -0
  79. meerschaum/connectors/api/_users.py +70 -58
  80. meerschaum/connectors/instance/_InstanceConnector.py +83 -0
  81. meerschaum/connectors/instance/__init__.py +10 -0
  82. meerschaum/connectors/instance/_pipes.py +442 -0
  83. meerschaum/connectors/instance/_plugins.py +151 -0
  84. meerschaum/connectors/instance/_tokens.py +296 -0
  85. meerschaum/connectors/instance/_users.py +181 -0
  86. meerschaum/connectors/parse.py +4 -1
  87. meerschaum/connectors/sql/_SQLConnector.py +8 -5
  88. meerschaum/connectors/sql/_cli.py +12 -11
  89. meerschaum/connectors/sql/_create_engine.py +9 -168
  90. meerschaum/connectors/sql/_fetch.py +2 -18
  91. meerschaum/connectors/sql/_pipes.py +156 -190
  92. meerschaum/connectors/sql/_plugins.py +29 -0
  93. meerschaum/connectors/sql/_sql.py +46 -21
  94. meerschaum/connectors/sql/_users.py +29 -2
  95. meerschaum/connectors/sql/tables/__init__.py +1 -1
  96. meerschaum/connectors/valkey/_ValkeyConnector.py +2 -4
  97. meerschaum/connectors/valkey/_pipes.py +53 -26
  98. meerschaum/connectors/valkey/_plugins.py +2 -26
  99. meerschaum/core/Pipe/__init__.py +59 -19
  100. meerschaum/core/Pipe/_attributes.py +412 -90
  101. meerschaum/core/Pipe/_bootstrap.py +54 -24
  102. meerschaum/core/Pipe/_data.py +96 -18
  103. meerschaum/core/Pipe/_dtypes.py +48 -18
  104. meerschaum/core/Pipe/_edit.py +14 -4
  105. meerschaum/core/Pipe/_fetch.py +1 -1
  106. meerschaum/core/Pipe/_show.py +5 -5
  107. meerschaum/core/Pipe/_sync.py +118 -193
  108. meerschaum/core/Pipe/_verify.py +4 -4
  109. meerschaum/{plugins → core/Plugin}/_Plugin.py +9 -11
  110. meerschaum/core/Plugin/__init__.py +1 -1
  111. meerschaum/core/Token/_Token.py +220 -0
  112. meerschaum/core/Token/__init__.py +12 -0
  113. meerschaum/core/User/_User.py +34 -8
  114. meerschaum/core/User/__init__.py +9 -1
  115. meerschaum/core/__init__.py +1 -0
  116. meerschaum/jobs/_Job.py +3 -2
  117. meerschaum/jobs/__init__.py +3 -2
  118. meerschaum/jobs/systemd.py +1 -1
  119. meerschaum/models/__init__.py +35 -0
  120. meerschaum/models/pipes.py +247 -0
  121. meerschaum/models/tokens.py +38 -0
  122. meerschaum/models/users.py +26 -0
  123. meerschaum/plugins/__init__.py +22 -7
  124. meerschaum/plugins/bootstrap.py +2 -1
  125. meerschaum/utils/_get_pipes.py +68 -27
  126. meerschaum/utils/daemon/Daemon.py +2 -1
  127. meerschaum/utils/daemon/__init__.py +30 -2
  128. meerschaum/utils/dataframe.py +473 -81
  129. meerschaum/utils/debug.py +15 -15
  130. meerschaum/utils/dtypes/__init__.py +473 -34
  131. meerschaum/utils/dtypes/sql.py +368 -28
  132. meerschaum/utils/formatting/__init__.py +1 -1
  133. meerschaum/utils/formatting/_pipes.py +5 -4
  134. meerschaum/utils/formatting/_shell.py +11 -9
  135. meerschaum/utils/misc.py +246 -148
  136. meerschaum/utils/packages/__init__.py +10 -27
  137. meerschaum/utils/packages/_packages.py +41 -34
  138. meerschaum/utils/pipes.py +181 -0
  139. meerschaum/utils/process.py +1 -1
  140. meerschaum/utils/prompt.py +3 -1
  141. meerschaum/utils/schedule.py +2 -1
  142. meerschaum/utils/sql.py +121 -44
  143. meerschaum/utils/typing.py +1 -4
  144. meerschaum/utils/venv/_Venv.py +2 -2
  145. meerschaum/utils/venv/__init__.py +5 -7
  146. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc2.dist-info}/METADATA +92 -96
  147. meerschaum-3.0.0rc2.dist-info/RECORD +283 -0
  148. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc2.dist-info}/WHEEL +1 -1
  149. meerschaum-3.0.0rc2.dist-info/licenses/NOTICE +2 -0
  150. meerschaum/api/models/_interfaces.py +0 -15
  151. meerschaum/api/models/_locations.py +0 -15
  152. meerschaum/api/models/_metrics.py +0 -15
  153. meerschaum/config/static/__init__.py +0 -186
  154. meerschaum-2.9.5.dist-info/RECORD +0 -263
  155. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc2.dist-info}/entry_points.txt +0 -0
  156. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc2.dist-info}/licenses/LICENSE +0 -0
  157. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc2.dist-info}/top_level.txt +0 -0
  158. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc2.dist-info}/zip-safe +0 -0
@@ -17,7 +17,14 @@ from meerschaum.utils.debug import dprint
17
17
  from meerschaum.utils.warnings import warn
18
18
 
19
19
  ### database flavors that can use bulk insert
20
- _bulk_flavors = {'postgresql', 'postgis', 'timescaledb', 'citus', 'mssql'}
20
+ _bulk_flavors = {
21
+ 'postgresql',
22
+ 'postgis',
23
+ 'timescaledb',
24
+ 'timescaledb-ha',
25
+ 'citus',
26
+ 'mssql',
27
+ }
21
28
  ### flavors that do not support chunks
22
29
  _disallow_chunks_flavors = ['duckdb']
23
30
  _max_chunks_flavors = {'sqlite': 1000}
@@ -124,23 +131,28 @@ def read(
124
131
  """
125
132
  if chunks is not None and chunks <= 0:
126
133
  return []
134
+
127
135
  from meerschaum.utils.sql import sql_item_name, truncate_item_name
128
136
  from meerschaum.utils.dtypes import are_dtypes_equal, coerce_timezone
129
137
  from meerschaum.utils.dtypes.sql import TIMEZONE_NAIVE_FLAVORS
130
138
  from meerschaum.utils.packages import attempt_import, import_pandas
131
139
  from meerschaum.utils.pool import get_pool
132
140
  from meerschaum.utils.dataframe import chunksize_to_npartitions, get_numeric_cols
141
+ from meerschaum.utils.misc import filter_arguments
133
142
  import warnings
134
143
  import traceback
135
144
  from decimal import Decimal
145
+
136
146
  pd = import_pandas()
137
147
  dd = None
148
+
138
149
  is_dask = 'dask' in pd.__name__
139
150
  pandas = attempt_import('pandas')
140
151
  is_dask = dd is not None
141
152
  npartitions = chunksize_to_npartitions(chunksize)
142
153
  if is_dask:
143
154
  chunksize = None
155
+
144
156
  schema = schema or self.schema
145
157
  utc_dt_cols = [
146
158
  col
@@ -151,7 +163,7 @@ def read(
151
163
  if dtype and utc_dt_cols and self.flavor in TIMEZONE_NAIVE_FLAVORS:
152
164
  dtype = dtype.copy()
153
165
  for col in utc_dt_cols:
154
- dtype[col] = 'datetime64[ns]'
166
+ dtype[col] = 'datetime64[us]'
155
167
 
156
168
  pool = get_pool(workers=workers)
157
169
  sqlalchemy = attempt_import("sqlalchemy", lazy=False)
@@ -215,26 +227,33 @@ def read(
215
227
  else format_sql_query_for_dask(str_query)
216
228
  )
217
229
 
230
+ def _get_chunk_args_kwargs(_chunk):
231
+ return filter_arguments(
232
+ chunk_hook,
233
+ _chunk,
234
+ workers=workers,
235
+ chunksize=chunksize,
236
+ debug=debug,
237
+ **kw
238
+ )
239
+
218
240
  chunk_list = []
219
241
  chunk_hook_results = []
220
242
  def _process_chunk(_chunk, _retry_on_failure: bool = True):
221
243
  if self.flavor in TIMEZONE_NAIVE_FLAVORS:
222
244
  for col in utc_dt_cols:
223
- _chunk[col] = coerce_timezone(_chunk[col], strip_timezone=False)
245
+ _chunk[col] = coerce_timezone(_chunk[col], strip_utc=False)
224
246
  if not as_hook_results:
225
247
  chunk_list.append(_chunk)
248
+
226
249
  if chunk_hook is None:
227
250
  return None
228
251
 
252
+ chunk_args, chunk_kwargs = _get_chunk_args_kwargs(_chunk)
253
+
229
254
  result = None
230
255
  try:
231
- result = chunk_hook(
232
- _chunk,
233
- workers=workers,
234
- chunksize=chunksize,
235
- debug=debug,
236
- **kw
237
- )
256
+ result = chunk_hook(*chunk_args, **chunk_kwargs)
238
257
  except Exception:
239
258
  result = False, traceback.format_exc()
240
259
  from meerschaum.utils.formatting import get_console
@@ -285,8 +304,16 @@ def read(
285
304
  self.engine,
286
305
  **read_sql_query_kwargs
287
306
  )
307
+
288
308
  to_return = (
289
- chunk_generator
309
+ (
310
+ chunk_generator
311
+ if not (as_hook_results or chunksize is None)
312
+ else (
313
+ _process_chunk(_chunk)
314
+ for _chunk in chunk_generator
315
+ )
316
+ )
290
317
  if as_iterator or chunksize is None
291
318
  else (
292
319
  list(pool.imap(_process_chunk, chunk_generator))
@@ -332,9 +359,8 @@ def read(
332
359
  try:
333
360
  for chunk in chunk_generator:
334
361
  if chunk_hook is not None:
335
- chunk_hook_results.append(
336
- chunk_hook(chunk, chunksize=chunksize, debug=debug, **kw)
337
- )
362
+ chunk_args, chunk_kwargs = _get_chunk_args_kwargs(chunk)
363
+ chunk_hook_results.append(chunk_hook(*chunk_args, **chunk_kwargs))
338
364
  chunk_list.append(chunk)
339
365
  read_chunks += 1
340
366
  if chunks is not None and read_chunks >= chunks:
@@ -349,9 +375,8 @@ def read(
349
375
  try:
350
376
  for chunk in chunk_generator:
351
377
  if chunk_hook is not None:
352
- chunk_hook_results.append(
353
- chunk_hook(chunk, chunksize=chunksize, debug=debug, **kw)
354
- )
378
+ chunk_args, chunk_kwargs = _get_chunk_args_kwargs(chunk)
379
+ chunk_hook_results.append(chunk_hook(*chunk_args, **chunk_kwargs))
355
380
  chunk_list.append(chunk)
356
381
  read_chunks += 1
357
382
  if chunks is not None and read_chunks >= chunks:
@@ -382,9 +407,8 @@ def read(
382
407
  ### call the hook on any missed chunks.
383
408
  if chunk_hook is not None and len(chunk_list) > len(chunk_hook_results):
384
409
  for c in chunk_list[len(chunk_hook_results):]:
385
- chunk_hook_results.append(
386
- chunk_hook(c, chunksize=chunksize, debug=debug, **kw)
387
- )
410
+ chunk_args, chunk_kwargs = _get_chunk_args_kwargs(c)
411
+ chunk_hook_results.append(chunk_hook(*chunk_args, **chunk_kwargs))
388
412
 
389
413
  ### chunksize is not None so must iterate
390
414
  if debug:
@@ -777,6 +801,7 @@ def to_sql(
777
801
  from meerschaum.utils.warnings import error, warn
778
802
  import warnings
779
803
  import functools
804
+ import traceback
780
805
 
781
806
  if name is None:
782
807
  error(f"Name must not be `None` to insert data into {self}.")
@@ -1050,7 +1075,7 @@ def to_sql(
1050
1075
  except Exception as e:
1051
1076
  if not silent:
1052
1077
  warn(str(e))
1053
- success, msg = False, str(e)
1078
+ success, msg = False, traceback.format_exc()
1054
1079
 
1055
1080
  end = time.perf_counter()
1056
1081
  if success:
@@ -12,6 +12,32 @@ import meerschaum as mrsm
12
12
  from meerschaum.utils.typing import SuccessTuple, Optional, Any, Dict, List, Union
13
13
 
14
14
 
15
+ def get_users_pipe(self) -> mrsm.Pipe:
16
+ """
17
+ Return the internal metadata pipe for users management.
18
+ """
19
+ return mrsm.Pipe(
20
+ 'mrsm', 'users',
21
+ temporary=True,
22
+ static=True,
23
+ null_indices=False,
24
+ enforce=False,
25
+ autoincrement=True,
26
+ columns={
27
+ 'primary': 'user_id',
28
+ },
29
+ dtypes={
30
+ 'user_id': 'int',
31
+ 'username': 'string',
32
+ 'attributes': 'json',
33
+ 'user_type': 'string',
34
+ },
35
+ indices={
36
+ 'unique': 'username',
37
+ },
38
+ )
39
+
40
+
15
41
  def register_user(
16
42
  self,
17
43
  user: mrsm.core.User,
@@ -64,7 +90,7 @@ def register_user(
64
90
 
65
91
  def valid_username(username: str) -> SuccessTuple:
66
92
  """Verify that a given username is valid."""
67
- from meerschaum.config.static import STATIC_CONFIG
93
+ from meerschaum._internal.static import STATIC_CONFIG
68
94
  fail_reasons = []
69
95
 
70
96
  min_length = STATIC_CONFIG['users']['min_username_length']
@@ -104,6 +130,7 @@ def edit_user(
104
130
  ) -> SuccessTuple:
105
131
  """Update an existing user's metadata."""
106
132
  from meerschaum.utils.packages import attempt_import
133
+ from meerschaum.utils.sql import json_flavors
107
134
  sqlalchemy = attempt_import('sqlalchemy', lazy=False)
108
135
  from meerschaum.connectors.sql.tables import get_tables
109
136
  users_tbl = get_tables(mrsm_instance=self, debug=debug)['users']
@@ -131,7 +158,7 @@ def edit_user(
131
158
  bind_variables['email'] = user.email
132
159
  if user.attributes is not None and user.attributes != {}:
133
160
  bind_variables['attributes'] = (
134
- json.dumps(user.attributes) if self.flavor in ('duckdb',)
161
+ json.dumps(user.attributes) if self.flavor not in json_flavors
135
162
  else user.attributes
136
163
  )
137
164
  if user.type != '':
@@ -231,7 +231,7 @@ def create_schemas(
231
231
  """
232
232
  Create the internal Meerschaum schema on the database.
233
233
  """
234
- from meerschaum.config.static import STATIC_CONFIG
234
+ from meerschaum._internal.static import STATIC_CONFIG
235
235
  from meerschaum.utils.packages import attempt_import
236
236
  from meerschaum.utils.sql import sql_item_name, NO_SCHEMA_FLAVORS, SKIP_IF_EXISTS_FLAVORS
237
237
  if conn.flavor in NO_SCHEMA_FLAVORS:
@@ -10,19 +10,18 @@ import json
10
10
  from datetime import datetime, timezone
11
11
 
12
12
  import meerschaum as mrsm
13
- from meerschaum.connectors import Connector, make_connector
13
+ from meerschaum.connectors import InstanceConnector, make_connector
14
14
  from meerschaum.utils.typing import List, Dict, Any, Optional, Union
15
15
  from meerschaum.utils.warnings import dprint
16
16
 
17
17
 
18
18
  @make_connector
19
- class ValkeyConnector(Connector):
19
+ class ValkeyConnector(InstanceConnector):
20
20
  """
21
21
  Manage a Valkey instance.
22
22
 
23
23
  Build a `ValkeyConnector` from connection attributes or a URI string.
24
24
  """
25
- IS_INSTANCE: bool = True
26
25
  REQUIRED_ATTRIBUTES: List[str] = ['host']
27
26
  OPTIONAL_ATTRIBUTES: List[str] = [
28
27
  'port', 'username', 'password', 'db', 'socket_timeout',
@@ -80,7 +79,6 @@ class ValkeyConnector(Connector):
80
79
  get_plugin_user_id,
81
80
  get_plugin_username,
82
81
  get_plugin_attributes,
83
- get_plugins,
84
82
  delete_plugin,
85
83
  )
86
84
 
@@ -13,7 +13,7 @@ from meerschaum.utils.typing import SuccessTuple, Any, Union, Optional, Dict, Li
13
13
  from meerschaum.utils.misc import string_to_dict
14
14
  from meerschaum.utils.dtypes import json_serialize_value
15
15
  from meerschaum.utils.warnings import warn, dprint
16
- from meerschaum.config.static import STATIC_CONFIG
16
+ from meerschaum._internal.static import STATIC_CONFIG
17
17
 
18
18
  PIPES_TABLE: str = 'mrsm_pipes'
19
19
  PIPES_COUNTER: str = 'mrsm_pipes:counter'
@@ -276,7 +276,7 @@ def edit_pipe(
276
276
  return False, f"{pipe} is not registered."
277
277
 
278
278
  parameters_key = get_pipe_parameters_key(pipe)
279
- parameters_str = json.dumps(pipe.parameters, separators=(',', ':'))
279
+ parameters_str = json.dumps(pipe.get_parameters(apply_symlinks=False), separators=(',', ':'))
280
280
  self.set(parameters_key, parameters_str)
281
281
  return True, "Success"
282
282
 
@@ -321,14 +321,40 @@ def drop_pipe(
321
321
  -------
322
322
  A `SuccessTuple` indicating success.
323
323
  """
324
- for chunk_begin, chunk_end in pipe.get_chunk_bounds(debug=debug):
325
- clear_chunk_success, clear_chunk_msg = pipe.clear(
326
- begin=chunk_begin,
327
- end=chunk_end,
328
- debug=debug,
324
+ if not pipe.exists(debug=debug):
325
+ return True, f"{pipe} does not exist, so it was not dropped."
326
+
327
+ table_name = self.quote_table(pipe.target)
328
+ dt_col = pipe.columns.get('datetime', None)
329
+
330
+ try:
331
+ members = (
332
+ self.client.zrange(table_name, 0, -1)
333
+ if dt_col
334
+ else self.client.smembers(table_name)
329
335
  )
330
- if not clear_chunk_success:
331
- return clear_chunk_success, clear_chunk_msg
336
+
337
+ keys_to_delete = []
338
+ for member_bytes in members:
339
+ member_str = member_bytes.decode('utf-8')
340
+ member_doc = json.loads(member_str)
341
+ ix_str = member_doc.get('ix')
342
+ if not ix_str:
343
+ continue
344
+
345
+ ix_doc = string_to_dict(ix_str.replace(COLON, ':'))
346
+ doc_key = self.get_document_key(ix_doc, list(ix_doc.keys()), table_name)
347
+ keys_to_delete.append(doc_key)
348
+
349
+ if keys_to_delete:
350
+ batch_size = 1000
351
+ for i in range(0, len(keys_to_delete), batch_size):
352
+ batch = keys_to_delete[i:i+batch_size]
353
+ self.client.delete(*batch)
354
+
355
+ except Exception as e:
356
+ return False, f"Failed to delete documents for {pipe}:\n{e}"
357
+
332
358
  try:
333
359
  self.drop_table(pipe.target, debug=debug)
334
360
  except Exception as e:
@@ -337,7 +363,7 @@ def drop_pipe(
337
363
  if 'valkey' not in pipe.parameters:
338
364
  return True, "Success"
339
365
 
340
- pipe.parameters['valkey']['dtypes'] = {}
366
+ pipe._attributes['parameters']['valkey']['dtypes'] = {}
341
367
  if not pipe.temporary:
342
368
  edit_success, edit_msg = pipe.edit(debug=debug)
343
369
  if not edit_success:
@@ -558,11 +584,7 @@ def sync_pipe(
558
584
 
559
585
  valkey_dtypes = pipe.parameters.get('valkey', {}).get('dtypes', {})
560
586
  new_dtypes = {
561
- str(key): (
562
- str(val)
563
- if not are_dtypes_equal(str(val), 'datetime')
564
- else 'datetime64[ns, UTC]'
565
- )
587
+ str(key): str(val)
566
588
  for key, val in df.dtypes.items()
567
589
  if str(key) not in valkey_dtypes
568
590
  }
@@ -571,19 +593,20 @@ def sync_pipe(
571
593
  try:
572
594
  df[col] = df[col].astype(typ)
573
595
  except Exception:
596
+ import traceback
597
+ traceback.print_exc()
574
598
  valkey_dtypes[col] = 'string'
575
599
  new_dtypes[col] = 'string'
576
600
  df[col] = df[col].astype('string')
577
601
 
578
602
  if new_dtypes and (not static or not valkey_dtypes):
579
603
  valkey_dtypes.update(new_dtypes)
580
- if 'valkey' not in pipe.parameters:
581
- pipe.parameters['valkey'] = {}
582
- pipe.parameters['valkey']['dtypes'] = valkey_dtypes
583
- if not pipe.temporary:
584
- edit_success, edit_msg = pipe.edit(debug=debug)
585
- if not edit_success:
586
- return edit_success, edit_msg
604
+ update_success, update_msg = pipe.update_parameters(
605
+ {'valkey': {'dtypes': valkey_dtypes}},
606
+ debug=debug,
607
+ )
608
+ if not update_success:
609
+ return False, update_msg
587
610
 
588
611
  unseen_df, update_df, delta_df = (
589
612
  pipe.filter_existing(df, include_unchanged_columns=True, debug=debug)
@@ -781,7 +804,7 @@ def get_sync_time(
781
804
  """
782
805
  from meerschaum.utils.dtypes import are_dtypes_equal
783
806
  dt_col = pipe.columns.get('datetime', None)
784
- dt_typ = pipe.dtypes.get(dt_col, 'datetime64[ns, UTC]')
807
+ dt_typ = pipe.dtypes.get(dt_col, 'datetime')
785
808
  if not dt_col:
786
809
  return None
787
810
 
@@ -789,14 +812,18 @@ def get_sync_time(
789
812
  table_name = self.quote_table(pipe.target)
790
813
  try:
791
814
  vals = (
792
- self.client.zrevrange(table_name, 0, 0)
815
+ self.client.zrevrange(table_name, 0, 0, withscores=True)
793
816
  if newest
794
- else self.client.zrange(table_name, 0, 0)
817
+ else self.client.zrange(table_name, 0, 0, withscores=True)
795
818
  )
796
819
  if not vals:
797
820
  return None
798
- val = vals[0]
821
+ val = vals[0][0]
822
+ if isinstance(val, bytes):
823
+ val = val.decode('utf-8')
799
824
  except Exception:
825
+ import traceback
826
+ traceback.print_exc()
800
827
  return None
801
828
 
802
829
  doc = json.loads(val)
@@ -127,7 +127,8 @@ def get_plugin_id(
127
127
  """
128
128
  Return a plugin's ID.
129
129
  """
130
- return plugin.name
130
+ user_id = self.get_plugin_user_id(plugin, debug=debug)
131
+ return plugin.name if user_id is not None else None
131
132
 
132
133
 
133
134
  def get_plugin_version(
@@ -199,31 +200,6 @@ def get_plugin_attributes(
199
200
  return {}
200
201
 
201
202
 
202
- def get_plugins(
203
- self,
204
- user_id: Optional[int] = None,
205
- search_term: Optional[str] = None,
206
- debug: bool = False,
207
- **kw: Any
208
- ) -> List[str]:
209
- """
210
- Return a list of plugin names.
211
- """
212
- plugins_pipe = self.get_plugins_pipe()
213
- params = {}
214
- if user_id:
215
- params['user_id'] = user_id
216
-
217
- df = plugins_pipe.get_data(['plugin_name'], params=params, debug=debug)
218
- docs = df.to_dict(orient='records')
219
-
220
- return [
221
- doc['plugin_name']
222
- for doc in docs
223
- if (plugin_name := doc['plugin_name']).startswith(search_term or '')
224
- ]
225
-
226
-
227
203
  def delete_plugin(
228
204
  self,
229
205
  plugin: 'mrsm.core.Plugin',
@@ -50,12 +50,16 @@ with correct credentials, as well as a network connection and valid permissions.
50
50
  """
51
51
 
52
52
  from __future__ import annotations
53
+
53
54
  import sys
54
55
  import copy
55
- from meerschaum.utils.typing import Optional, Dict, Any, Union, InstanceConnector, List
56
+
57
+ import meerschaum as mrsm
58
+ from meerschaum.utils.typing import Optional, Dict, Any, Union, List, InstanceConnector
56
59
  from meerschaum.utils.formatting._pipes import pipe_repr
57
60
  from meerschaum.config import get_config
58
61
 
62
+
59
63
  class Pipe:
60
64
  """
61
65
  Access Meerschaum pipes via Pipe objects.
@@ -89,6 +93,9 @@ class Pipe:
89
93
  get_data,
90
94
  get_backtrack_data,
91
95
  get_rowcount,
96
+ get_data,
97
+ get_doc,
98
+ get_value,
92
99
  _get_data_as_iterator,
93
100
  get_chunk_interval,
94
101
  get_chunk_bounds,
@@ -104,15 +111,20 @@ class Pipe:
104
111
  indexes,
105
112
  dtypes,
106
113
  autoincrement,
114
+ autotime,
107
115
  upsert,
108
116
  static,
109
117
  tzinfo,
110
118
  enforce,
111
119
  null_indices,
120
+ mixed_numerics,
112
121
  get_columns,
113
122
  get_columns_types,
114
123
  get_columns_indices,
115
124
  get_indices,
125
+ get_parameters,
126
+ get_dtypes,
127
+ update_parameters,
116
128
  tags,
117
129
  get_id,
118
130
  id,
@@ -123,6 +135,9 @@ class Pipe:
123
135
  target,
124
136
  _target_legacy,
125
137
  guess_datetime,
138
+ precision,
139
+ get_precision,
140
+ _invalidate_cache,
126
141
  )
127
142
  from ._show import show
128
143
  from ._edit import edit, edit_definition, update
@@ -133,11 +148,7 @@ class Pipe:
133
148
  filter_existing,
134
149
  _get_chunk_label,
135
150
  get_num_workers,
136
- _persist_new_json_columns,
137
- _persist_new_numeric_columns,
138
- _persist_new_uuid_columns,
139
- _persist_new_bytes_columns,
140
- _persist_new_geometry_columns,
151
+ _persist_new_special_columns,
141
152
  )
142
153
  from ._verify import (
143
154
  verify,
@@ -168,9 +179,12 @@ class Pipe:
168
179
  temporary: bool = False,
169
180
  upsert: Optional[bool] = None,
170
181
  autoincrement: Optional[bool] = None,
182
+ autotime: Optional[bool] = None,
183
+ precision: Union[str, Dict[str, Union[str, int]], None] = None,
171
184
  static: Optional[bool] = None,
172
185
  enforce: Optional[bool] = None,
173
186
  null_indices: Optional[bool] = None,
187
+ mixed_numerics: Optional[bool] = None,
174
188
  mrsm_instance: Optional[Union[str, InstanceConnector]] = None,
175
189
  cache: bool = False,
176
190
  debug: bool = False,
@@ -226,6 +240,16 @@ class Pipe:
226
240
  autoincrement: Optional[bool], default None
227
241
  If `True`, set `autoincrement` in the parameters.
228
242
 
243
+ autotime: Optional[bool], default None
244
+ If `True`, set `autotime` in the parameters.
245
+
246
+ precision: Union[str, Dict[str, Union[str, int]], None], default None
247
+ If provided, set `precision` in the parameters.
248
+ This may be either a string (the precision unit) or a dictionary of in the form
249
+ `{'unit': <unit>, 'interval': <interval>}`.
250
+ Default is determined by the `datetime` column dtype
251
+ (e.g. `datetime64[us]` is `microsecond` precision).
252
+
229
253
  static: Optional[bool], default None
230
254
  If `True`, set `static` in the parameters.
231
255
 
@@ -237,6 +261,11 @@ class Pipe:
237
261
  Set to `False` if there will be no null values in the index columns.
238
262
  Defaults to `True`.
239
263
 
264
+ mixed_numerics: bool, default None
265
+ If `True`, integer columns will be converted to `numeric` when floats are synced.
266
+ Set to `False` to disable this behavior.
267
+ Defaults to `True`.
268
+
240
269
  temporary: bool, default False
241
270
  If `True`, prevent instance tables (pipes, users, plugins) from being created.
242
271
 
@@ -264,7 +293,7 @@ class Pipe:
264
293
  if location in ('[None]', 'None'):
265
294
  location = None
266
295
 
267
- from meerschaum.config.static import STATIC_CONFIG
296
+ from meerschaum._internal.static import STATIC_CONFIG
268
297
  negation_prefix = STATIC_CONFIG['system']['fetch_pipes_keys']['negation_prefix']
269
298
  for k in (connector, metric, location, *(tags or [])):
270
299
  if str(k).startswith(negation_prefix):
@@ -291,11 +320,13 @@ class Pipe:
291
320
  warn(f"The provided parameters are of invalid type '{type(parameters)}'.")
292
321
  self._attributes['parameters'] = {}
293
322
 
294
- columns = columns or self._attributes.get('parameters', {}).get('columns', {})
295
- if isinstance(columns, list):
323
+ columns = columns or self._attributes.get('parameters', {}).get('columns', None)
324
+ if isinstance(columns, (list, tuple)):
296
325
  columns = {str(col): str(col) for col in columns}
297
326
  if isinstance(columns, dict):
298
327
  self._attributes['parameters']['columns'] = columns
328
+ elif isinstance(columns, str) and 'Pipe(' in columns:
329
+ pass
299
330
  elif columns is not None:
300
331
  warn(f"The provided columns are of invalid type '{type(columns)}'.")
301
332
 
@@ -334,6 +365,14 @@ class Pipe:
334
365
  if isinstance(autoincrement, bool):
335
366
  self._attributes['parameters']['autoincrement'] = autoincrement
336
367
 
368
+ if isinstance(autotime, bool):
369
+ self._attributes['parameters']['autotime'] = autotime
370
+
371
+ if isinstance(precision, dict):
372
+ self._attributes['parameters']['precision'] = precision
373
+ elif isinstance(precision, str):
374
+ self._attributes['parameters']['precision'] = {'unit': precision}
375
+
337
376
  if isinstance(static, bool):
338
377
  self._attributes['parameters']['static'] = static
339
378
 
@@ -343,6 +382,9 @@ class Pipe:
343
382
  if isinstance(null_indices, bool):
344
383
  self._attributes['parameters']['null_indices'] = null_indices
345
384
 
385
+ if isinstance(mixed_numerics, bool):
386
+ self._attributes['parameters']['mixed_numerics'] = mixed_numerics
387
+
346
388
  ### NOTE: The parameters dictionary is {} by default.
347
389
  ### A Pipe may be registered without parameters, then edited,
348
390
  ### or a Pipe may be registered with parameters set in-memory first.
@@ -383,9 +425,7 @@ class Pipe:
383
425
  @property
384
426
  def instance_connector(self) -> Union[InstanceConnector, None]:
385
427
  """
386
- The connector to where this pipe resides.
387
- May either be of type `meerschaum.connectors.sql.SQLConnector` or
388
- `meerschaum.connectors.api.APIConnector`.
428
+ The instance connector on which this pipe resides.
389
429
  """
390
430
  if '_instance_connector' not in self.__dict__:
391
431
  from meerschaum.connectors.parse import parse_instance_keys
@@ -397,7 +437,7 @@ class Pipe:
397
437
  return self._instance_connector
398
438
 
399
439
  @property
400
- def connector(self) -> Union[meerschaum.connectors.Connector, None]:
440
+ def connector(self) -> Union['Connector', None]:
401
441
  """
402
442
  The connector to the data source.
403
443
  """
@@ -417,7 +457,7 @@ class Pipe:
417
457
  return self._connector
418
458
 
419
459
  @property
420
- def cache_connector(self) -> Union[meerschaum.connectors.sql.SQLConnector, None]:
460
+ def cache_connector(self) -> Union['Connector', None]:
421
461
  """
422
462
  If the pipe was created with `cache=True`, return the connector to the pipe's
423
463
  SQLite database for caching.
@@ -438,7 +478,7 @@ class Pipe:
438
478
  return self._cache_connector
439
479
 
440
480
  @property
441
- def cache_pipe(self) -> Union['meerschaum.Pipe', None]:
481
+ def cache_pipe(self) -> Union[mrsm.Pipe, None]:
442
482
  """
443
483
  If the pipe was created with `cache=True`, return another `meerschaum.Pipe` used to
444
484
  manage the local data.
@@ -470,10 +510,10 @@ class Pipe:
470
510
  self.instance_keys,
471
511
  (self.connector_keys + '_' + self.metric_key + '_cache'),
472
512
  self.location_key,
473
- mrsm_instance = self.cache_connector,
474
- parameters = _parameters,
475
- cache = False,
476
- temporary = True,
513
+ mrsm_instance=self.cache_connector,
514
+ parameters=_parameters,
515
+ cache=False,
516
+ temporary=True,
477
517
  )
478
518
 
479
519
  return self._cache_pipe