meerschaum 2.9.4__py3-none-any.whl → 3.0.0rc1__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 (154) 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 +17 -1
  5. meerschaum/_internal/entry.py +6 -6
  6. meerschaum/_internal/shell/Shell.py +1 -1
  7. meerschaum/_internal/static.py +372 -0
  8. meerschaum/actions/api.py +12 -2
  9. meerschaum/actions/bootstrap.py +7 -7
  10. meerschaum/actions/edit.py +142 -18
  11. meerschaum/actions/register.py +137 -6
  12. meerschaum/actions/show.py +117 -29
  13. meerschaum/actions/stop.py +4 -1
  14. meerschaum/actions/sync.py +1 -1
  15. meerschaum/actions/tag.py +9 -8
  16. meerschaum/api/__init__.py +9 -2
  17. meerschaum/api/_events.py +39 -2
  18. meerschaum/api/_oauth2.py +118 -8
  19. meerschaum/api/_tokens.py +102 -0
  20. meerschaum/api/dash/__init__.py +0 -1
  21. meerschaum/api/dash/callbacks/custom.py +2 -2
  22. meerschaum/api/dash/callbacks/dashboard.py +133 -18
  23. meerschaum/api/dash/callbacks/plugins.py +0 -1
  24. meerschaum/api/dash/callbacks/register.py +1 -1
  25. meerschaum/api/dash/callbacks/settings/__init__.py +1 -0
  26. meerschaum/api/dash/callbacks/settings/password_reset.py +2 -2
  27. meerschaum/api/dash/callbacks/settings/tokens.py +388 -0
  28. meerschaum/api/dash/components.py +30 -8
  29. meerschaum/api/dash/keys.py +19 -93
  30. meerschaum/api/dash/pages/dashboard.py +1 -20
  31. meerschaum/api/dash/pages/settings/__init__.py +1 -0
  32. meerschaum/api/dash/pages/settings/password_reset.py +1 -1
  33. meerschaum/api/dash/pages/settings/tokens.py +55 -0
  34. meerschaum/api/dash/pipes.py +156 -58
  35. meerschaum/api/dash/sessions.py +12 -0
  36. meerschaum/api/dash/tokens.py +606 -0
  37. meerschaum/api/dash/websockets.py +1 -1
  38. meerschaum/api/dash/webterm.py +4 -0
  39. meerschaum/api/models/__init__.py +23 -3
  40. meerschaum/api/models/_actions.py +22 -0
  41. meerschaum/api/models/_pipes.py +85 -7
  42. meerschaum/api/models/_tokens.py +81 -0
  43. meerschaum/api/resources/static/css/dash.css +16 -0
  44. meerschaum/api/resources/templates/termpage.html +12 -0
  45. meerschaum/api/routes/__init__.py +1 -0
  46. meerschaum/api/routes/_actions.py +3 -4
  47. meerschaum/api/routes/_connectors.py +3 -7
  48. meerschaum/api/routes/_jobs.py +14 -35
  49. meerschaum/api/routes/_login.py +49 -12
  50. meerschaum/api/routes/_misc.py +5 -10
  51. meerschaum/api/routes/_pipes.py +134 -111
  52. meerschaum/api/routes/_plugins.py +38 -28
  53. meerschaum/api/routes/_tokens.py +236 -0
  54. meerschaum/api/routes/_users.py +47 -35
  55. meerschaum/api/routes/_version.py +3 -3
  56. meerschaum/config/__init__.py +43 -20
  57. meerschaum/config/_default.py +32 -5
  58. meerschaum/config/_edit.py +28 -24
  59. meerschaum/config/_environment.py +1 -1
  60. meerschaum/config/_patch.py +6 -6
  61. meerschaum/config/_paths.py +5 -1
  62. meerschaum/config/_read_config.py +65 -34
  63. meerschaum/config/_sync.py +6 -3
  64. meerschaum/config/_version.py +1 -1
  65. meerschaum/config/stack/__init__.py +24 -5
  66. meerschaum/config/static.py +18 -0
  67. meerschaum/connectors/_Connector.py +10 -4
  68. meerschaum/connectors/__init__.py +4 -20
  69. meerschaum/connectors/api/_APIConnector.py +34 -6
  70. meerschaum/connectors/api/_actions.py +2 -2
  71. meerschaum/connectors/api/_jobs.py +1 -1
  72. meerschaum/connectors/api/_login.py +33 -7
  73. meerschaum/connectors/api/_misc.py +2 -2
  74. meerschaum/connectors/api/_pipes.py +15 -14
  75. meerschaum/connectors/api/_plugins.py +2 -2
  76. meerschaum/connectors/api/_request.py +1 -1
  77. meerschaum/connectors/api/_tokens.py +146 -0
  78. meerschaum/connectors/api/_users.py +70 -58
  79. meerschaum/connectors/instance/_InstanceConnector.py +83 -0
  80. meerschaum/connectors/instance/__init__.py +10 -0
  81. meerschaum/connectors/instance/_pipes.py +442 -0
  82. meerschaum/connectors/instance/_plugins.py +151 -0
  83. meerschaum/connectors/instance/_tokens.py +296 -0
  84. meerschaum/connectors/instance/_users.py +181 -0
  85. meerschaum/connectors/parse.py +4 -1
  86. meerschaum/connectors/sql/_SQLConnector.py +8 -5
  87. meerschaum/connectors/sql/_cli.py +12 -11
  88. meerschaum/connectors/sql/_create_engine.py +6 -154
  89. meerschaum/connectors/sql/_fetch.py +2 -18
  90. meerschaum/connectors/sql/_pipes.py +42 -31
  91. meerschaum/connectors/sql/_plugins.py +29 -0
  92. meerschaum/connectors/sql/_sql.py +9 -2
  93. meerschaum/connectors/sql/_users.py +29 -2
  94. meerschaum/connectors/sql/tables/__init__.py +1 -1
  95. meerschaum/connectors/valkey/_ValkeyConnector.py +2 -4
  96. meerschaum/connectors/valkey/_pipes.py +9 -10
  97. meerschaum/connectors/valkey/_plugins.py +2 -26
  98. meerschaum/core/Pipe/__init__.py +31 -14
  99. meerschaum/core/Pipe/_attributes.py +156 -58
  100. meerschaum/core/Pipe/_bootstrap.py +54 -24
  101. meerschaum/core/Pipe/_data.py +41 -1
  102. meerschaum/core/Pipe/_dtypes.py +29 -14
  103. meerschaum/core/Pipe/_edit.py +12 -4
  104. meerschaum/core/Pipe/_show.py +5 -5
  105. meerschaum/core/Pipe/_sync.py +48 -53
  106. meerschaum/core/Pipe/_verify.py +1 -1
  107. meerschaum/{plugins → core/Plugin}/_Plugin.py +9 -11
  108. meerschaum/core/Plugin/__init__.py +1 -1
  109. meerschaum/core/Token/_Token.py +221 -0
  110. meerschaum/core/Token/__init__.py +12 -0
  111. meerschaum/core/User/_User.py +34 -8
  112. meerschaum/core/User/__init__.py +9 -1
  113. meerschaum/core/__init__.py +1 -0
  114. meerschaum/jobs/_Job.py +3 -2
  115. meerschaum/jobs/__init__.py +3 -2
  116. meerschaum/jobs/systemd.py +1 -1
  117. meerschaum/models/__init__.py +35 -0
  118. meerschaum/models/pipes.py +247 -0
  119. meerschaum/models/tokens.py +38 -0
  120. meerschaum/models/users.py +26 -0
  121. meerschaum/plugins/__init__.py +22 -7
  122. meerschaum/plugins/bootstrap.py +2 -1
  123. meerschaum/utils/_get_pipes.py +68 -27
  124. meerschaum/utils/daemon/Daemon.py +2 -1
  125. meerschaum/utils/daemon/__init__.py +30 -2
  126. meerschaum/utils/dataframe.py +96 -15
  127. meerschaum/utils/dtypes/__init__.py +93 -21
  128. meerschaum/utils/dtypes/sql.py +44 -0
  129. meerschaum/utils/formatting/__init__.py +1 -1
  130. meerschaum/utils/formatting/_pipes.py +5 -4
  131. meerschaum/utils/formatting/_shell.py +11 -9
  132. meerschaum/utils/misc.py +237 -80
  133. meerschaum/utils/packages/__init__.py +3 -6
  134. meerschaum/utils/packages/_packages.py +34 -32
  135. meerschaum/utils/pipes.py +181 -0
  136. meerschaum/utils/process.py +1 -1
  137. meerschaum/utils/prompt.py +3 -1
  138. meerschaum/utils/schedule.py +1 -0
  139. meerschaum/utils/sql.py +115 -39
  140. meerschaum/utils/typing.py +1 -4
  141. meerschaum/utils/venv/_Venv.py +2 -2
  142. meerschaum/utils/venv/__init__.py +5 -7
  143. {meerschaum-2.9.4.dist-info → meerschaum-3.0.0rc1.dist-info}/METADATA +88 -80
  144. meerschaum-3.0.0rc1.dist-info/RECORD +282 -0
  145. {meerschaum-2.9.4.dist-info → meerschaum-3.0.0rc1.dist-info}/WHEEL +1 -1
  146. meerschaum/api/models/_interfaces.py +0 -15
  147. meerschaum/api/models/_locations.py +0 -15
  148. meerschaum/api/models/_metrics.py +0 -15
  149. meerschaum/config/static/__init__.py +0 -186
  150. meerschaum-2.9.4.dist-info/RECORD +0 -263
  151. {meerschaum-2.9.4.dist-info → meerschaum-3.0.0rc1.dist-info}/entry_points.txt +0 -0
  152. {meerschaum-2.9.4.dist-info → meerschaum-3.0.0rc1.dist-info}/licenses/LICENSE +0 -0
  153. {meerschaum-2.9.4.dist-info → meerschaum-3.0.0rc1.dist-info}/top_level.txt +0 -0
  154. {meerschaum-2.9.4.dist-info → meerschaum-3.0.0rc1.dist-info}/zip-safe +0 -0
@@ -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,6 +111,7 @@ class Pipe:
104
111
  indexes,
105
112
  dtypes,
106
113
  autoincrement,
114
+ autotime,
107
115
  upsert,
108
116
  static,
109
117
  tzinfo,
@@ -113,6 +121,8 @@ class Pipe:
113
121
  get_columns_types,
114
122
  get_columns_indices,
115
123
  get_indices,
124
+ get_parameters,
125
+ update_parameters,
116
126
  tags,
117
127
  get_id,
118
128
  id,
@@ -168,6 +178,7 @@ class Pipe:
168
178
  temporary: bool = False,
169
179
  upsert: Optional[bool] = None,
170
180
  autoincrement: Optional[bool] = None,
181
+ autotime: Optional[bool] = None,
171
182
  static: Optional[bool] = None,
172
183
  enforce: Optional[bool] = None,
173
184
  null_indices: Optional[bool] = None,
@@ -226,6 +237,9 @@ class Pipe:
226
237
  autoincrement: Optional[bool], default None
227
238
  If `True`, set `autoincrement` in the parameters.
228
239
 
240
+ autotime: Optional[bool], default None
241
+ If `True`, set `autotime` in the parameters.
242
+
229
243
  static: Optional[bool], default None
230
244
  If `True`, set `static` in the parameters.
231
245
 
@@ -264,7 +278,7 @@ class Pipe:
264
278
  if location in ('[None]', 'None'):
265
279
  location = None
266
280
 
267
- from meerschaum.config.static import STATIC_CONFIG
281
+ from meerschaum._internal.static import STATIC_CONFIG
268
282
  negation_prefix = STATIC_CONFIG['system']['fetch_pipes_keys']['negation_prefix']
269
283
  for k in (connector, metric, location, *(tags or [])):
270
284
  if str(k).startswith(negation_prefix):
@@ -291,11 +305,13 @@ class Pipe:
291
305
  warn(f"The provided parameters are of invalid type '{type(parameters)}'.")
292
306
  self._attributes['parameters'] = {}
293
307
 
294
- columns = columns or self._attributes.get('parameters', {}).get('columns', {})
295
- if isinstance(columns, list):
308
+ columns = columns or self._attributes.get('parameters', {}).get('columns', None)
309
+ if isinstance(columns, (list, tuple)):
296
310
  columns = {str(col): str(col) for col in columns}
297
311
  if isinstance(columns, dict):
298
312
  self._attributes['parameters']['columns'] = columns
313
+ elif isinstance(columns, str) and 'Pipe(' in columns:
314
+ pass
299
315
  elif columns is not None:
300
316
  warn(f"The provided columns are of invalid type '{type(columns)}'.")
301
317
 
@@ -334,6 +350,9 @@ class Pipe:
334
350
  if isinstance(autoincrement, bool):
335
351
  self._attributes['parameters']['autoincrement'] = autoincrement
336
352
 
353
+ if isinstance(autotime, bool):
354
+ self._attributes['parameters']['autotime'] = autotime
355
+
337
356
  if isinstance(static, bool):
338
357
  self._attributes['parameters']['static'] = static
339
358
 
@@ -383,9 +402,7 @@ class Pipe:
383
402
  @property
384
403
  def instance_connector(self) -> Union[InstanceConnector, None]:
385
404
  """
386
- The connector to where this pipe resides.
387
- May either be of type `meerschaum.connectors.sql.SQLConnector` or
388
- `meerschaum.connectors.api.APIConnector`.
405
+ The instance connector on which this pipe resides.
389
406
  """
390
407
  if '_instance_connector' not in self.__dict__:
391
408
  from meerschaum.connectors.parse import parse_instance_keys
@@ -397,7 +414,7 @@ class Pipe:
397
414
  return self._instance_connector
398
415
 
399
416
  @property
400
- def connector(self) -> Union[meerschaum.connectors.Connector, None]:
417
+ def connector(self) -> Union['Connector', None]:
401
418
  """
402
419
  The connector to the data source.
403
420
  """
@@ -417,7 +434,7 @@ class Pipe:
417
434
  return self._connector
418
435
 
419
436
  @property
420
- def cache_connector(self) -> Union[meerschaum.connectors.sql.SQLConnector, None]:
437
+ def cache_connector(self) -> Union['Connector', None]:
421
438
  """
422
439
  If the pipe was created with `cache=True`, return the connector to the pipe's
423
440
  SQLite database for caching.
@@ -438,7 +455,7 @@ class Pipe:
438
455
  return self._cache_connector
439
456
 
440
457
  @property
441
- def cache_pipe(self) -> Union['meerschaum.Pipe', None]:
458
+ def cache_pipe(self) -> Union[mrsm.Pipe, None]:
442
459
  """
443
460
  If the pipe was created with `cache=True`, return another `meerschaum.Pipe` used to
444
461
  manage the local data.
@@ -470,10 +487,10 @@ class Pipe:
470
487
  self.instance_keys,
471
488
  (self.connector_keys + '_' + self.metric_key + '_cache'),
472
489
  self.location_key,
473
- mrsm_instance = self.cache_connector,
474
- parameters = _parameters,
475
- cache = False,
476
- temporary = True,
490
+ mrsm_instance=self.cache_connector,
491
+ parameters=_parameters,
492
+ cache=False,
493
+ temporary=True,
477
494
  )
478
495
 
479
496
  return self._cache_pipe
@@ -8,6 +8,7 @@ Fetch and manipulate Pipes' attributes
8
8
 
9
9
  from __future__ import annotations
10
10
 
11
+ import uuid
11
12
  from datetime import timezone
12
13
 
13
14
  import meerschaum as mrsm
@@ -48,21 +49,74 @@ def attributes(self) -> Dict[str, Any]:
48
49
  return self._attributes
49
50
 
50
51
 
52
+ def get_parameters(
53
+ self,
54
+ apply_symlinks: bool = True,
55
+ _visited: 'Optional[set[mrsm.Pipe]]' = None,
56
+ ) -> Dict[str, Any]:
57
+ """
58
+ Return the `parameters` dictionary of the pipe.
59
+ """
60
+ from meerschaum.config._patch import apply_patch_to_config
61
+ from meerschaum.utils.warnings import warn
62
+ from meerschaum.config._read_config import search_and_substitute_config
63
+
64
+ if _visited is None:
65
+ _visited = {self}
66
+
67
+ raw_parameters = self.attributes.get('parameters', {})
68
+ ref_keys = raw_parameters.get('reference')
69
+ if not apply_symlinks:
70
+ return raw_parameters
71
+
72
+ if ref_keys:
73
+ try:
74
+ ref_pipe = mrsm.Pipe(**ref_keys)
75
+ if ref_pipe in _visited:
76
+ warn(f"Circular reference detected in {self}: chain involves {ref_pipe}.")
77
+ return search_and_substitute_config(raw_parameters)
78
+
79
+ _visited.add(ref_pipe)
80
+ base_params = ref_pipe.get_parameters(_visited=_visited)
81
+ except Exception as e:
82
+ warn(f"Failed to resolve reference pipe for {self}: {e}")
83
+ base_params = {}
84
+
85
+ params_to_apply = {k: v for k, v in raw_parameters.items() if k != 'reference'}
86
+ parameters = apply_patch_to_config(base_params, params_to_apply)
87
+ else:
88
+ parameters = raw_parameters
89
+
90
+ from meerschaum.utils.pipes import replace_pipes_syntax
91
+ self._symlinks = {}
92
+
93
+ def recursive_replace(obj: Any, path: tuple) -> Any:
94
+ if isinstance(obj, dict):
95
+ return {k: recursive_replace(v, path + (k,)) for k, v in obj.items()}
96
+ if isinstance(obj, list):
97
+ return [recursive_replace(elem, path + (i,)) for i, elem in enumerate(obj)]
98
+ if isinstance(obj, str):
99
+ substituted_val = replace_pipes_syntax(obj)
100
+ if substituted_val != obj:
101
+ self._symlinks[path] = {
102
+ 'original': obj,
103
+ 'substituted': substituted_val,
104
+ }
105
+ return substituted_val
106
+ return obj
107
+
108
+ return search_and_substitute_config(recursive_replace(parameters, tuple()))
109
+
110
+
51
111
  @property
52
112
  def parameters(self) -> Optional[Dict[str, Any]]:
53
113
  """
54
114
  Return the parameters dictionary of the pipe.
55
115
  """
56
- if 'parameters' not in self.attributes:
57
- self.attributes['parameters'] = {}
58
- _parameters = self.attributes['parameters']
59
- dt_col = _parameters.get('columns', {}).get('datetime', None)
60
- dt_typ = _parameters.get('dtypes', {}).get(dt_col, None) if dt_col else None
61
- if dt_col and not dt_typ:
62
- if 'dtypes' not in _parameters:
63
- self.attributes['parameters']['dtypes'] = {}
64
- self.attributes['parameters']['dtypes'][dt_col] = 'datetime'
65
- return self.attributes['parameters']
116
+ if (_parameters := self.__dict__.get('_parameters', None)) is not None:
117
+ return _parameters
118
+ self._parameters = self.get_parameters()
119
+ return self._parameters
66
120
 
67
121
 
68
122
  @parameters.setter
@@ -71,7 +125,9 @@ def parameters(self, parameters: Dict[str, Any]) -> None:
71
125
  Set the parameters dictionary of the in-memory pipe.
72
126
  Call `meerschaum.Pipe.edit()` to persist changes.
73
127
  """
74
- self.attributes['parameters'] = parameters
128
+ self._attributes['parameters'] = parameters
129
+ if '_parameters' in self.__dict__:
130
+ del self.__dict__['_parameters']
75
131
 
76
132
 
77
133
  @property
@@ -79,12 +135,9 @@ def columns(self) -> Union[Dict[str, str], None]:
79
135
  """
80
136
  Return the `columns` dictionary defined in `meerschaum.Pipe.parameters`.
81
137
  """
82
- if 'columns' not in self.parameters:
83
- self.parameters['columns'] = {}
84
- cols = self.parameters['columns']
138
+ cols = self.parameters.get('columns', {})
85
139
  if not isinstance(cols, dict):
86
- cols = {}
87
- self.parameters['columns'] = cols
140
+ return {}
88
141
  return {col_ix: col for col_ix, col in cols.items() if col}
89
142
 
90
143
 
@@ -99,7 +152,7 @@ def columns(self, _columns: Union[Dict[str, str], List[str]]) -> None:
99
152
  if not isinstance(_columns, dict):
100
153
  warn(f"{self}.columns must be a dictionary, received {type(_columns)}.")
101
154
  return
102
- self.parameters['columns'] = _columns
155
+ self.update_parameters({'columns': _columns}, persist=False)
103
156
 
104
157
 
105
158
  @property
@@ -158,7 +211,7 @@ def indices(self, _indices: Union[Dict[str, Union[str, List[str]]], List[str]])
158
211
  if 'indexes' in self.parameters
159
212
  else 'indices'
160
213
  )
161
- self.parameters[indices_key] = _indices
214
+ self.update_parameters({indices_key: _indices}, persist=False)
162
215
 
163
216
 
164
217
  @indexes.setter
@@ -174,24 +227,22 @@ def tags(self) -> Union[List[str], None]:
174
227
  """
175
228
  If defined, return the `tags` list defined in `meerschaum.Pipe.parameters`.
176
229
  """
177
- if 'tags' not in self.parameters:
178
- self.parameters['tags'] = []
179
- return self.parameters['tags']
230
+ return self.parameters.get('tags', [])
180
231
 
181
232
 
182
233
  @tags.setter
183
- def tags(self, _tags: List[str, str]) -> None:
234
+ def tags(self, _tags: List[str]) -> None:
184
235
  """
185
236
  Override the tags list of the in-memory pipe.
186
237
  Call `meerschaum.Pipe.edit` to persist changes.
187
238
  """
188
239
  from meerschaum.utils.warnings import error
189
- from meerschaum.config.static import STATIC_CONFIG
240
+ from meerschaum._internal.static import STATIC_CONFIG
190
241
  negation_prefix = STATIC_CONFIG['system']['fetch_pipes_keys']['negation_prefix']
191
242
  for t in _tags:
192
243
  if t.startswith(negation_prefix):
193
244
  error(f"Tags cannot begin with '{negation_prefix}'.")
194
- self.parameters['tags'] = _tags
245
+ self.update_parameters({'tags': _tags}, persist=False)
195
246
 
196
247
 
197
248
  @property
@@ -204,11 +255,18 @@ def dtypes(self) -> Union[Dict[str, Any], None]:
204
255
  configured_dtypes = self.parameters.get('dtypes', {})
205
256
  remote_dtypes = self.infer_dtypes(persist=False)
206
257
  patched_dtypes = apply_patch_to_config(remote_dtypes, configured_dtypes)
207
- return {
258
+ dt_col = self.columns.get('datetime', None)
259
+ primary_col = self.columns.get('primary', None)
260
+ _dtypes = {
208
261
  col: MRSM_ALIAS_DTYPES.get(typ, typ)
209
262
  for col, typ in patched_dtypes.items()
210
263
  if col and typ
211
264
  }
265
+ if dt_col and dt_col not in configured_dtypes:
266
+ _dtypes[dt_col] = 'datetime'
267
+ if primary_col and self.autoincrement and primary_col not in _dtypes:
268
+ _dtypes[primary_col] = 'int'
269
+ return _dtypes
212
270
 
213
271
 
214
272
  @dtypes.setter
@@ -217,7 +275,7 @@ def dtypes(self, _dtypes: Dict[str, Any]) -> None:
217
275
  Override the dtypes dictionary of the in-memory pipe.
218
276
  Call `meerschaum.Pipe.edit()` to persist changes.
219
277
  """
220
- self.parameters['dtypes'] = _dtypes
278
+ self.update_parameters({'dtypes': _dtypes}, persist=False)
221
279
 
222
280
 
223
281
  @property
@@ -225,9 +283,7 @@ def upsert(self) -> bool:
225
283
  """
226
284
  Return whether `upsert` is set for the pipe.
227
285
  """
228
- if 'upsert' not in self.parameters:
229
- self.parameters['upsert'] = False
230
- return self.parameters['upsert']
286
+ return self.parameters.get('upsert', False)
231
287
 
232
288
 
233
289
  @upsert.setter
@@ -235,7 +291,7 @@ def upsert(self, _upsert: bool) -> None:
235
291
  """
236
292
  Set the `upsert` parameter for the pipe.
237
293
  """
238
- self.parameters['upsert'] = _upsert
294
+ self.update_parameters({'upsert': _upsert}, persist=False)
239
295
 
240
296
 
241
297
  @property
@@ -243,9 +299,7 @@ def static(self) -> bool:
243
299
  """
244
300
  Return whether `static` is set for the pipe.
245
301
  """
246
- if 'static' not in self.parameters:
247
- self.parameters['static'] = False
248
- return self.parameters['static']
302
+ return self.parameters.get('static', False)
249
303
 
250
304
 
251
305
  @static.setter
@@ -253,7 +307,7 @@ def static(self, _static: bool) -> None:
253
307
  """
254
308
  Set the `static` parameter for the pipe.
255
309
  """
256
- self.parameters['static'] = _static
310
+ self.update_parameters({'static': _static}, persist=False)
257
311
 
258
312
 
259
313
  @property
@@ -261,10 +315,7 @@ def autoincrement(self) -> bool:
261
315
  """
262
316
  Return the `autoincrement` parameter for the pipe.
263
317
  """
264
- if 'autoincrement' not in self.parameters:
265
- self.parameters['autoincrement'] = False
266
-
267
- return self.parameters['autoincrement']
318
+ return self.parameters.get('autoincrement', False)
268
319
 
269
320
 
270
321
  @autoincrement.setter
@@ -272,7 +323,23 @@ def autoincrement(self, _autoincrement: bool) -> None:
272
323
  """
273
324
  Set the `autoincrement` parameter for the pipe.
274
325
  """
275
- self.parameters['autoincrement'] = _autoincrement
326
+ self.update_parameters({'autoincrement': _autoincrement}, persist=False)
327
+
328
+
329
+ @property
330
+ def autotime(self) -> bool:
331
+ """
332
+ Return the `autotime` parameter for the pipe.
333
+ """
334
+ return self.parameters.get('autotime', False)
335
+
336
+
337
+ @autotime.setter
338
+ def autotime(self, _autotime: bool) -> None:
339
+ """
340
+ Set the `autotime` parameter for the pipe.
341
+ """
342
+ self.update_parameters({'autotime': _autotime}, persist=False)
276
343
 
277
344
 
278
345
  @property
@@ -299,10 +366,7 @@ def enforce(self) -> bool:
299
366
  """
300
367
  Return the `enforce` parameter for the pipe.
301
368
  """
302
- if 'enforce' not in self.parameters:
303
- self.parameters['enforce'] = True
304
-
305
- return self.parameters['enforce']
369
+ return self.parameters.get('enforce', True)
306
370
 
307
371
 
308
372
  @enforce.setter
@@ -310,7 +374,7 @@ def enforce(self, _enforce: bool) -> None:
310
374
  """
311
375
  Set the `enforce` parameter for the pipe.
312
376
  """
313
- self.parameters['enforce'] = _enforce
377
+ self.update_parameters({'enforce': _enforce}, persist=False)
314
378
 
315
379
 
316
380
  @property
@@ -318,10 +382,7 @@ def null_indices(self) -> bool:
318
382
  """
319
383
  Return the `null_indices` parameter for the pipe.
320
384
  """
321
- if 'null_indices' not in self.parameters:
322
- self.parameters['null_indices'] = True
323
-
324
- return self.parameters['null_indices']
385
+ return self.parameters.get('null_indices', True)
325
386
 
326
387
 
327
388
  @null_indices.setter
@@ -329,7 +390,7 @@ def null_indices(self, _null_indices: bool) -> None:
329
390
  """
330
391
  Set the `null_indices` parameter for the pipe.
331
392
  """
332
- self.parameters['null_indices'] = _null_indices
393
+ self.update_parameters({'null_indices': _null_indices}, persist=False)
333
394
 
334
395
 
335
396
  def get_columns(self, *args: str, error: bool = False) -> Union[str, Tuple[str]]:
@@ -357,7 +418,7 @@ def get_columns(self, *args: str, error: bool = False) -> Union[str, Tuple[str]]
357
418
  >>> pipe.get_columns('value', error=True)
358
419
  Exception: 🛑 Missing 'value' column for Pipe('test', 'test').
359
420
  """
360
- from meerschaum.utils.warnings import error as _error, warn
421
+ from meerschaum.utils.warnings import error as _error
361
422
  if not args:
362
423
  args = tuple(self.columns.keys())
363
424
  col_names = []
@@ -367,7 +428,7 @@ def get_columns(self, *args: str, error: bool = False) -> Union[str, Tuple[str]]
367
428
  col_name = self.columns[col]
368
429
  if col_name is None and error:
369
430
  _error(f"Please define the name of the '{col}' column for {self}.")
370
- except Exception as e:
431
+ except Exception:
371
432
  col_name = None
372
433
  if col_name is None and error:
373
434
  _error(f"Missing '{col}'" + f" column for {self}.")
@@ -409,7 +470,7 @@ def get_columns_types(
409
470
  """
410
471
  import time
411
472
  from meerschaum.connectors import get_connector_plugin
412
- from meerschaum.config.static import STATIC_CONFIG
473
+ from meerschaum._internal.static import STATIC_CONFIG
413
474
  from meerschaum.utils.warnings import dprint
414
475
 
415
476
  now = time.perf_counter()
@@ -454,7 +515,7 @@ def get_columns_indices(
454
515
  """
455
516
  import time
456
517
  from meerschaum.connectors import get_connector_plugin
457
- from meerschaum.config.static import STATIC_CONFIG
518
+ from meerschaum._internal.static import STATIC_CONFIG
458
519
  from meerschaum.utils.warnings import dprint
459
520
 
460
521
  now = time.perf_counter()
@@ -509,7 +570,7 @@ def get_id(self, **kw: Any) -> Union[int, None]:
509
570
 
510
571
 
511
572
  @property
512
- def id(self) -> Union[int, None]:
573
+ def id(self) -> Union[int, str, uuid.UUID, None]:
513
574
  """
514
575
  Fetch and cache a pipe's ID.
515
576
  """
@@ -539,7 +600,7 @@ def get_val_column(self, debug: bool = False) -> Union[str, None]:
539
600
  dprint('Attempting to determine the value column...')
540
601
  try:
541
602
  val_name = self.get_columns('value')
542
- except Exception as e:
603
+ except Exception:
543
604
  val_name = None
544
605
  if val_name is not None:
545
606
  if debug:
@@ -553,11 +614,11 @@ def get_val_column(self, debug: bool = False) -> Union[str, None]:
553
614
  return None
554
615
  try:
555
616
  dt_name = self.get_columns('datetime', error=False)
556
- except Exception as e:
617
+ except Exception:
557
618
  dt_name = None
558
619
  try:
559
620
  id_name = self.get_columns('id', errors=False)
560
- except Exception as e:
621
+ except Exception:
561
622
  id_name = None
562
623
 
563
624
  if debug:
@@ -714,7 +775,7 @@ def target(self, _target: str) -> None:
714
775
  Override the target of the in-memory pipe.
715
776
  Call `meerschaum.Pipe.edit` to persist changes.
716
777
  """
717
- self.parameters['target'] = _target
778
+ self.update_parameters({'target': _target}, persist=False)
718
779
 
719
780
 
720
781
  def guess_datetime(self) -> Union[str, None]:
@@ -755,3 +816,40 @@ def get_indices(self) -> Dict[str, str]:
755
816
  result = {}
756
817
 
757
818
  return result
819
+
820
+
821
+ def update_parameters(
822
+ self,
823
+ parameters_patch: Dict[str, Any],
824
+ persist: bool = True,
825
+ debug: bool = False,
826
+ ) -> mrsm.SuccessTuple:
827
+ """
828
+ Apply a patch to a pipe's `parameters` dictionary.
829
+
830
+ Parameters
831
+ ----------
832
+ parameters_patch: Dict[str, Any]
833
+ The patch to be applied to `Pipe.parameters`.
834
+
835
+ persist: bool, default True
836
+ If `True`, call `Pipe.edit()` to persist the new parameters.
837
+ """
838
+ from meerschaum.config import apply_patch_to_config
839
+ if '_parameters' in self.__dict__:
840
+ del self.__dict__['_parameters']
841
+ if 'parameters' not in self._attributes:
842
+ self._attributes['parameters'] = {}
843
+
844
+ self._attributes['parameters'] = apply_patch_to_config(
845
+ self._attributes['parameters'],
846
+ parameters_patch,
847
+ )
848
+
849
+ if self.temporary:
850
+ persist = False
851
+
852
+ if not persist:
853
+ return True, "Success"
854
+
855
+ return self.edit(debug=debug)
@@ -7,6 +7,8 @@ Attempt to create a pipe's requirements in one method.
7
7
  """
8
8
 
9
9
  from __future__ import annotations
10
+
11
+ import meerschaum as mrsm
10
12
  from meerschaum.utils.typing import SuccessTuple, Dict, Any
11
13
 
12
14
 
@@ -207,28 +209,56 @@ def _ask_for_columns(pipe, debug: bool=False) -> Dict[str, str]:
207
209
  """
208
210
  Prompt the user for the column names.
209
211
  """
210
- from meerschaum.utils.warnings import info, warn
211
- from meerschaum.utils.prompt import prompt
212
+ import json
213
+ from meerschaum.utils.warnings import info
214
+ from meerschaum.utils.prompt import prompt, yes_no
215
+ from meerschaum.utils.formatting import get_console
216
+ from meerschaum.utils.formatting._shell import clear_screen
217
+ from meerschaum.utils.misc import to_snake_case
218
+ from meerschaum.config import get_config
219
+ rich_json = mrsm.attempt_import('rich.json')
212
220
 
213
- info(f"Please enter column names for {pipe}:")
214
- while True:
215
- try:
216
- datetime_name = prompt(f"Datetime column (empty to omit):", icon=False)
217
- except KeyboardInterrupt:
218
- return False, f"Cancelled bootstrapping {pipe}."
219
- if datetime_name == '':
220
- datetime_name = None
221
-
222
- try:
223
- id_name = prompt(f"ID column (empty to omit):", icon=False)
224
- except KeyboardInterrupt:
225
- return False, f"Cancelled bootstrapping {pipe}."
226
- if id_name == '':
227
- id_name = None
228
-
229
- break
230
-
231
- return {
232
- 'datetime': datetime_name,
233
- 'id': id_name,
234
- }
221
+ do_clear = get_config('shell', 'clear_screen')
222
+
223
+ cols = {}
224
+
225
+ info(f"Please enter index columns for {pipe}:")
226
+ try:
227
+ datetime_name = prompt("Datetime column (empty to omit):", icon=False)
228
+ except KeyboardInterrupt:
229
+ datetime_name = None
230
+
231
+ if datetime_name:
232
+ cols['datetime'] = datetime_name
233
+
234
+ try:
235
+ id_name = prompt("ID column (empty to omit):", icon=False)
236
+ except KeyboardInterrupt:
237
+ id_name = None
238
+
239
+ if id_name:
240
+ cols['id'] = id_name
241
+
242
+ if yes_no("Add more columns?"):
243
+ while True:
244
+ if do_clear:
245
+ clear_screen(debug=debug)
246
+
247
+ cols_text = json.dumps(cols, indent=4)
248
+ info("Current index columns:")
249
+ get_console().print(rich_json.JSON(cols_text))
250
+
251
+ col_name = prompt("Enter index column (empty to stop):")
252
+ if not col_name:
253
+ break
254
+
255
+ if col_name in cols.values():
256
+ continue
257
+
258
+ col_ix = to_snake_case(col_name)
259
+ if col_ix in cols:
260
+ col_ix = col_ix + '_'
261
+
262
+ cols[col_ix] = col_name
263
+
264
+ return cols