meerschaum 2.8.4__py3-none-any.whl → 2.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. meerschaum/api/_chunks.py +67 -0
  2. meerschaum/api/dash/callbacks/__init__.py +5 -2
  3. meerschaum/api/dash/callbacks/custom.py +21 -8
  4. meerschaum/api/dash/callbacks/dashboard.py +26 -4
  5. meerschaum/api/dash/callbacks/settings/__init__.py +8 -0
  6. meerschaum/api/dash/callbacks/settings/password_reset.py +76 -0
  7. meerschaum/api/dash/components.py +136 -25
  8. meerschaum/api/dash/pages/__init__.py +1 -0
  9. meerschaum/api/dash/pages/dashboard.py +11 -9
  10. meerschaum/api/dash/pages/plugins.py +31 -27
  11. meerschaum/api/dash/pages/settings/__init__.py +8 -0
  12. meerschaum/api/dash/pages/settings/password_reset.py +63 -0
  13. meerschaum/api/dash/webterm.py +6 -3
  14. meerschaum/api/resources/static/css/dash.css +8 -1
  15. meerschaum/api/resources/templates/termpage.html +4 -0
  16. meerschaum/api/routes/_pipes.py +232 -79
  17. meerschaum/config/_default.py +4 -0
  18. meerschaum/config/_version.py +1 -1
  19. meerschaum/connectors/__init__.py +1 -0
  20. meerschaum/connectors/api/_APIConnector.py +12 -1
  21. meerschaum/connectors/api/_pipes.py +106 -45
  22. meerschaum/connectors/api/_plugins.py +51 -45
  23. meerschaum/connectors/api/_request.py +1 -1
  24. meerschaum/connectors/parse.py +1 -2
  25. meerschaum/connectors/sql/_SQLConnector.py +3 -0
  26. meerschaum/connectors/sql/_cli.py +1 -0
  27. meerschaum/connectors/sql/_create_engine.py +51 -4
  28. meerschaum/connectors/sql/_pipes.py +38 -6
  29. meerschaum/connectors/sql/_sql.py +35 -4
  30. meerschaum/connectors/valkey/_ValkeyConnector.py +2 -0
  31. meerschaum/connectors/valkey/_pipes.py +51 -39
  32. meerschaum/core/Pipe/__init__.py +1 -0
  33. meerschaum/core/Pipe/_data.py +1 -2
  34. meerschaum/core/Pipe/_sync.py +64 -4
  35. meerschaum/plugins/_Plugin.py +21 -5
  36. meerschaum/plugins/__init__.py +32 -8
  37. meerschaum/utils/dataframe.py +139 -2
  38. meerschaum/utils/dtypes/__init__.py +211 -1
  39. meerschaum/utils/dtypes/sql.py +296 -5
  40. meerschaum/utils/formatting/_shell.py +1 -4
  41. meerschaum/utils/misc.py +1 -1
  42. meerschaum/utils/packages/_packages.py +7 -1
  43. meerschaum/utils/sql.py +139 -11
  44. meerschaum/utils/venv/__init__.py +6 -1
  45. {meerschaum-2.8.4.dist-info → meerschaum-2.9.0.dist-info}/METADATA +17 -3
  46. {meerschaum-2.8.4.dist-info → meerschaum-2.9.0.dist-info}/RECORD +52 -52
  47. {meerschaum-2.8.4.dist-info → meerschaum-2.9.0.dist-info}/WHEEL +1 -1
  48. meerschaum/_internal/gui/__init__.py +0 -43
  49. meerschaum/_internal/gui/app/__init__.py +0 -50
  50. meerschaum/_internal/gui/app/_windows.py +0 -74
  51. meerschaum/_internal/gui/app/actions.py +0 -30
  52. meerschaum/_internal/gui/app/pipes.py +0 -47
  53. {meerschaum-2.8.4.dist-info → meerschaum-2.9.0.dist-info}/LICENSE +0 -0
  54. {meerschaum-2.8.4.dist-info → meerschaum-2.9.0.dist-info}/NOTICE +0 -0
  55. {meerschaum-2.8.4.dist-info → meerschaum-2.9.0.dist-info}/entry_points.txt +0 -0
  56. {meerschaum-2.8.4.dist-info → meerschaum-2.9.0.dist-info}/top_level.txt +0 -0
  57. {meerschaum-2.8.4.dist-info → meerschaum-2.9.0.dist-info}/zip-safe +0 -0
@@ -53,19 +53,22 @@ def get_webterm(state: WebState) -> Tuple[Any, Any]:
53
53
  html.Div(
54
54
  [
55
55
  dbc.Button(
56
- 'Refresh',
56
+ "⟳",
57
57
  color='black',
58
+ size='sm',
58
59
  id='webterm-refresh-button',
59
60
  ),
60
61
  dbc.Button(
61
- 'Full View',
62
+ '',
62
63
  color='black',
64
+ size='sm',
63
65
  id='webterm-fullscreen-button',
64
66
  ),
65
67
  ] + [
66
68
  dbc.Button(
67
- 'New Tab',
69
+ html.B('+'),
68
70
  color='black',
71
+ size='sm',
69
72
  id='webterm-new-tab-button',
70
73
  ),
71
74
  ] if TMUX_IS_ENABLED else [],
@@ -23,7 +23,7 @@ a {
23
23
  padding: 0.5em;
24
24
  }
25
25
  #webterm-iframe {
26
- width: 102%;
26
+ width: 100%;
27
27
  height: 83vh !important;
28
28
  max-height: 83vh;
29
29
  }
@@ -73,3 +73,10 @@ a {
73
73
  .input-text {
74
74
  font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, serif !important;
75
75
  }
76
+ .pages-offcanvas-accordion button {
77
+ background-color: var(--bs-dark) !important;
78
+ color: white !important;
79
+ }
80
+ .pages-offcanvas-accordion div {
81
+ padding: 0 !important;
82
+ }
@@ -111,6 +111,10 @@ window.addEventListener(
111
111
  false,
112
112
  );
113
113
 
114
+ document.addEventListener('contextmenu', function(e) {
115
+ e.preventDefault();
116
+ });
117
+
114
118
  let reconnectTimeout;
115
119
 
116
120
  function cleanUpWebSocket(socket) {
@@ -27,8 +27,10 @@ from meerschaum.api import (
27
27
  debug,
28
28
  no_auth,
29
29
  )
30
+ from meerschaum.api._chunks import generate_chunks_cursor_token
30
31
  from meerschaum.utils.packages import attempt_import
31
32
  from meerschaum.utils.dataframe import to_json
33
+ from meerschaum.utils.dtypes import are_dtypes_equal, json_serialize_value
32
34
  from meerschaum.utils.misc import (
33
35
  is_pipe_registered,
34
36
  is_int,
@@ -38,14 +40,16 @@ from meerschaum.connectors.sql.tables import get_tables
38
40
 
39
41
  fastapi_responses = attempt_import('fastapi.responses', lazy=False)
40
42
  StreamingResponse = fastapi_responses.StreamingResponse
41
- dateutil_parser = attempt_import('dateutil.parser', lazy=False)
42
43
  pipes_endpoint = endpoints['pipes']
43
44
  pd = attempt_import('pandas', lazy=False)
44
45
 
45
46
  MAX_RESPONSE_ROW_LIMIT: int = mrsm.get_config('system', 'api', 'data', 'max_response_row_limit')
46
47
 
47
48
 
48
- @app.post(pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/register', tags=['Pipes'])
49
+ @app.post(
50
+ pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/register',
51
+ tags=['Pipes: Attributes'],
52
+ )
49
53
  def register_pipe(
50
54
  connector_keys: str,
51
55
  metric_key: str,
@@ -80,18 +84,23 @@ def register_pipe(
80
84
  return results
81
85
 
82
86
 
83
- @app.delete(pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/delete', tags=['Pipes'])
84
- def delete_pipe(
87
+ @app.patch(
88
+ pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/edit',
89
+ tags=['Pipes: Attributes'],
90
+ )
91
+ def edit_pipe(
85
92
  connector_keys: str,
86
93
  metric_key: str,
87
94
  location_key: str,
95
+ parameters: dict,
88
96
  instance_keys: Optional[str] = None,
97
+ patch: bool = False,
89
98
  curr_user = (
90
99
  fastapi.Depends(manager) if not no_auth else None
91
100
  ),
92
101
  ):
93
102
  """
94
- Delete a Pipe (without dropping its table).
103
+ Edit an existing pipe's parameters.
95
104
  """
96
105
  allow_actions = mrsm.get_config('system', 'api', 'permissions', 'actions', 'non_admin')
97
106
  if not allow_actions:
@@ -107,54 +116,27 @@ def delete_pipe(
107
116
  raise fastapi.HTTPException(
108
117
  status_code=409, detail=f"{pipe} is not registered."
109
118
  )
110
- results = get_api_connector(instance_keys).delete_pipe(pipe, debug=debug)
111
- pipes(instance_keys, refresh=True)
112
- return results
113
-
114
-
115
- @app.delete(pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/drop', tags=['Pipes'])
116
- def drop_pipe(
117
- connector_keys: str,
118
- metric_key: str,
119
- location_key: str,
120
- instance_keys: Optional[str] = None,
121
- curr_user = (
122
- fastapi.Depends(manager) if not no_auth else None
123
- ),
124
- ):
125
- """
126
- Drop a pipes' underlying target table.
127
- """
128
- allow_actions = mrsm.get_config('system', 'api', 'permissions', 'actions', 'non_admin')
129
- if not allow_actions:
130
- return False, (
131
- "The administrator for this server has not allowed actions.\n\n"
132
- "Please contact the system administrator, or if you are running this server, "
133
- "open the configuration file with `edit config system` and search for 'permissions'."
134
- " Under the keys `api:permissions:actions`, " +
135
- "you can toggle non-admin actions."
136
- )
137
- pipe_object = get_pipe(connector_keys, metric_key, location_key, instance_keys)
138
- results = get_api_connector(instance_keys=instance_keys).drop_pipe(pipe_object, debug=debug)
119
+ pipe.parameters = parameters
120
+ results = get_api_connector(instance_keys).edit_pipe(pipe, patch=patch, debug=debug)
139
121
  pipes(instance_keys, refresh=True)
140
122
  return results
141
123
 
142
124
 
143
-
144
- @app.patch(pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/edit', tags=['Pipes'])
145
- def edit_pipe(
125
+ @app.delete(
126
+ pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/delete',
127
+ tags=['Pipes: Attributes'],
128
+ )
129
+ def delete_pipe(
146
130
  connector_keys: str,
147
131
  metric_key: str,
148
132
  location_key: str,
149
- parameters: dict,
150
133
  instance_keys: Optional[str] = None,
151
- patch: bool = False,
152
134
  curr_user = (
153
135
  fastapi.Depends(manager) if not no_auth else None
154
136
  ),
155
137
  ):
156
138
  """
157
- Edit an existing pipe's parameters.
139
+ Delete a Pipe (without dropping its table).
158
140
  """
159
141
  allow_actions = mrsm.get_config('system', 'api', 'permissions', 'actions', 'non_admin')
160
142
  if not allow_actions:
@@ -170,13 +152,12 @@ def edit_pipe(
170
152
  raise fastapi.HTTPException(
171
153
  status_code=409, detail=f"{pipe} is not registered."
172
154
  )
173
- pipe.parameters = parameters
174
- results = get_api_connector(instance_keys).edit_pipe(pipe, patch=patch, debug=debug)
155
+ results = get_api_connector(instance_keys).delete_pipe(pipe, debug=debug)
175
156
  pipes(instance_keys, refresh=True)
176
157
  return results
177
158
 
178
159
 
179
- @app.get(pipes_endpoint + '/keys', tags=['Pipes'])
160
+ @app.get(pipes_endpoint + '/keys', tags=['Pipes: Attributes'])
180
161
  async def fetch_pipes_keys(
181
162
  connector_keys: str = "[]",
182
163
  metric_keys: str = "[]",
@@ -201,7 +182,7 @@ async def fetch_pipes_keys(
201
182
  return keys
202
183
 
203
184
 
204
- @app.get(pipes_endpoint, tags=['Pipes'])
185
+ @app.get(pipes_endpoint, tags=['Pipes: Attributes'])
205
186
  async def get_pipes(
206
187
  connector_keys: str = "",
207
188
  metric_keys: str = "",
@@ -225,7 +206,7 @@ async def get_pipes(
225
206
  return replace_pipes_in_dict(_get_pipes(**kw), lambda p: p.attributes)
226
207
 
227
208
 
228
- @app.get(pipes_endpoint + '/{connector_keys}', tags=['Pipes'])
209
+ @app.get(pipes_endpoint + '/{connector_keys}', tags=['Pipes: Attributes'])
229
210
  async def get_pipes_by_connector(
230
211
  connector_keys: str,
231
212
  instance_keys: Optional[str] = None,
@@ -243,7 +224,7 @@ async def get_pipes_by_connector(
243
224
  return replace_pipes_in_dict(pipes(instance_keys)[connector_keys], lambda p: p.attributes)
244
225
 
245
226
 
246
- @app.get(pipes_endpoint + '/{connector_keys}/{metric_key}', tags=['Pipes'])
227
+ @app.get(pipes_endpoint + '/{connector_keys}/{metric_key}', tags=['Pipes: Attributes'])
247
228
  async def get_pipes_by_connector_and_metric(
248
229
  connector_keys: str,
249
230
  metric_key: str,
@@ -271,7 +252,10 @@ async def get_pipes_by_connector_and_metric(
271
252
  )
272
253
 
273
254
 
274
- @app.get(pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}', tags=['Pipes'])
255
+ @app.get(
256
+ pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}',
257
+ tags=['Pipes: Attributes'],
258
+ )
275
259
  async def get_pipe_by_connector_and_metric_and_location(
276
260
  connector_keys: str,
277
261
  metric_key: str,
@@ -302,7 +286,10 @@ async def get_pipe_by_connector_and_metric_and_location(
302
286
  return pipes(instance_keys)[connector_keys][metric_key][location_key].attributes
303
287
 
304
288
 
305
- @app.get(pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/sync_time', tags=['Pipes'])
289
+ @app.get(
290
+ pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/sync_time',
291
+ tags=['Pipes: Data'],
292
+ )
306
293
  def get_sync_time(
307
294
  connector_keys: str,
308
295
  metric_key: str,
@@ -333,7 +320,10 @@ def get_sync_time(
333
320
  return sync_time
334
321
 
335
322
 
336
- @app.post(pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/data', tags=['Pipes'])
323
+ @app.post(
324
+ pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/data',
325
+ tags=['Pipes: Data'],
326
+ )
337
327
  def sync_pipe(
338
328
  connector_keys: str,
339
329
  metric_key: str,
@@ -377,7 +367,10 @@ def sync_pipe(
377
367
  return list((success, msg))
378
368
 
379
369
 
380
- @app.get(pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/data', tags=['Pipes'])
370
+ @app.get(
371
+ pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/data',
372
+ tags=['Pipes: Data'],
373
+ )
381
374
  def get_pipe_data(
382
375
  connector_keys: str,
383
376
  metric_key: str,
@@ -389,6 +382,11 @@ def get_pipe_data(
389
382
  end: Union[str, int, None] = None,
390
383
  params: Optional[str] = None,
391
384
  limit: int = MAX_RESPONSE_ROW_LIMIT,
385
+ order: str = 'asc',
386
+ date_format: str = 'iso',
387
+ date_unit: str = 'us',
388
+ double_precision: int = 15,
389
+ geometry_format: str = 'wkb_hex',
392
390
  curr_user = (
393
391
  fastapi.Depends(manager) if not no_auth else None
394
392
  ),
@@ -398,11 +396,28 @@ def get_pipe_data(
398
396
  See [`Pipe.get_data()`](https://docs.meerschaum.io/meerschaum.html#Pipe.get_data).
399
397
 
400
398
  Note that `select_columns`, `omit_columns`, and `params` are JSON-encoded strings.
399
+
400
+ Parameters
401
+ ----------
402
+ instance_keys: Optional[str], default None
403
+ The connector key to the instance on which the pipe is registered.
404
+ Defaults to the configured value for `meerschaum:api_instance`.
405
+
406
+ date_format: str, default 'iso'
407
+ Serialzation format for datetime values.
408
+ Accepted values are `'iso`' (ISO8601) and `'epoch'` (epoch milliseconds).
409
+
410
+ date_unit: str, default 'us'
411
+ Timestamp precision for serialization. Accepted values are `'s'` (seconds),
412
+ `'ms'` (milliseconds), `'us'` (microseconds), and `'ns'`.
413
+
414
+ double_precision: int, default 15
415
+ The number of decimal places to use when encoding floating point values (maximum `15`).
416
+
417
+ geometry_format: str, default 'wkb_hex'
418
+ The serialization format for geometry data.
419
+ Accepted values are `geojson`, `wkb_hex`, and `wkt`.
401
420
  """
402
- if is_int(begin):
403
- begin = int(begin)
404
- if is_int(end):
405
- end = int(end)
406
421
  if limit > MAX_RESPONSE_ROW_LIMIT:
407
422
  raise fastapi.HTTPException(
408
423
  status_code=413,
@@ -455,13 +470,14 @@ def get_pipe_data(
455
470
  )
456
471
 
457
472
  pipe = get_pipe(connector_keys, metric_key, location_key, instance_keys)
473
+ begin, end = pipe.parse_date_bounds(begin, end)
458
474
  if not is_pipe_registered(pipe, pipes(instance_keys, refresh=True)):
459
475
  raise fastapi.HTTPException(
460
476
  status_code=409,
461
477
  detail="Pipe must be registered with the datetime column specified."
462
478
  )
463
479
 
464
- if pipe.target in ('users', 'plugins', 'pipes'):
480
+ if pipe.target in ('mrsm_users', 'mrsm_plugins', 'mrsm_pipes'):
465
481
  raise fastapi.HTTPException(
466
482
  status_code=409,
467
483
  detail=f"Cannot retrieve data from protected table '{pipe.target}'.",
@@ -474,6 +490,7 @@ def get_pipe_data(
474
490
  end=end,
475
491
  params=_params,
476
492
  limit=min(limit, MAX_RESPONSE_ROW_LIMIT),
493
+ order=order,
477
494
  debug=debug,
478
495
  )
479
496
  if df is None:
@@ -482,14 +499,152 @@ def get_pipe_data(
482
499
  detail="Could not fetch data with the given parameters.",
483
500
  )
484
501
 
485
- json_content = to_json(df)
502
+ json_content = to_json(
503
+ df,
504
+ date_format=date_format,
505
+ date_unit=date_unit,
506
+ geometry_format=geometry_format,
507
+ double_precision=double_precision,
508
+ )
486
509
  return fastapi.Response(
487
510
  json_content,
488
511
  media_type='application/json',
489
512
  )
490
513
 
491
514
 
492
- @app.get(pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/csv', tags=['Pipes'])
515
+ @app.get(
516
+ pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/chunk_bounds',
517
+ tags=['Pipes: Data'],
518
+ )
519
+ def get_pipe_chunk_bounds(
520
+ connector_keys: str,
521
+ metric_key: str,
522
+ location_key: str,
523
+ instance_keys: Optional[str] = None,
524
+ begin: Union[str, int, None] = None,
525
+ end: Union[str, int, None] = None,
526
+ bounded: bool = True,
527
+ chunk_interval_minutes: Union[int, None] = None,
528
+ ) -> List[List[Union[str, int, None]]]:
529
+ """
530
+ Return a list of request boundaries between `begin` and `end` (or the pipe's sync times).
531
+ Optionally specify the interval between chunk bounds
532
+ (defaults to the pipe's configured chunk interval).
533
+ """
534
+ pipe = get_pipe(connector_keys, metric_key, location_key, instance_keys)
535
+ begin, end = pipe.parse_date_bounds(begin, end)
536
+ dt_col = pipe.columns.get('datetime', None)
537
+ dt_typ = pipe.dtypes.get(dt_col, 'datetime')
538
+ chunk_interval = None if chunk_interval_minutes is None else (
539
+ chunk_interval_minutes
540
+ if are_dtypes_equal(dt_typ, 'int')
541
+ else timedelta(minutes=chunk_interval_minutes)
542
+ )
543
+
544
+ chunk_bounds = pipe.get_chunk_bounds(
545
+ begin=begin,
546
+ end=end,
547
+ bounded=bounded,
548
+ chunk_interval=chunk_interval,
549
+ debug=debug,
550
+ )
551
+
552
+ return fastapi.Response(
553
+ json.dumps(chunk_bounds, default=json_serialize_value),
554
+ media_type='application/json',
555
+ )
556
+
557
+
558
+ @app.delete(
559
+ pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/drop',
560
+ tags=['Pipes: Data'],
561
+ )
562
+ def drop_pipe(
563
+ connector_keys: str,
564
+ metric_key: str,
565
+ location_key: str,
566
+ instance_keys: Optional[str] = None,
567
+ curr_user = (
568
+ fastapi.Depends(manager) if not no_auth else None
569
+ ),
570
+ ):
571
+ """
572
+ Drop a pipe's target table.
573
+ """
574
+ allow_actions = mrsm.get_config('system', 'api', 'permissions', 'actions', 'non_admin')
575
+ if not allow_actions:
576
+ return False, (
577
+ "The administrator for this server has not allowed actions.\n\n"
578
+ "Please contact the system administrator, or if you are running this server, "
579
+ "open the configuration file with `edit config system` and search for 'permissions'."
580
+ " Under the keys `api:permissions:actions`, " +
581
+ "you can toggle non-admin actions."
582
+ )
583
+ pipe_object = get_pipe(connector_keys, metric_key, location_key, instance_keys)
584
+ results = get_api_connector(instance_keys=instance_keys).drop_pipe(pipe_object, debug=debug)
585
+ pipes(instance_keys, refresh=True)
586
+ return results
587
+
588
+
589
+ @app.delete(
590
+ pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/clear',
591
+ tags=['Pipes: Data'],
592
+ )
593
+ def clear_pipe(
594
+ connector_keys: str,
595
+ metric_key: str,
596
+ location_key: str,
597
+ instance_keys: Optional[str] = None,
598
+ begin: Union[str, int, None] = None,
599
+ end: Union[str, int, None] = None,
600
+ params: Optional[str] = None,
601
+ curr_user = (
602
+ fastapi.Depends(manager) if not no_auth else None
603
+ ),
604
+ ):
605
+ """
606
+ Delete rows from a pipe's target table.
607
+ """
608
+ _params = {}
609
+ if params == 'null':
610
+ params = None
611
+ if params is not None:
612
+ try:
613
+ _params = json.loads(params)
614
+ except Exception:
615
+ _params = None
616
+ if not isinstance(_params, dict):
617
+ raise fastapi.HTTPException(
618
+ status_code=409,
619
+ detail="Params must be a valid JSON-encoded dictionary.",
620
+ )
621
+
622
+ allow_actions = mrsm.get_config('system', 'api', 'permissions', 'actions', 'non_admin')
623
+ if not allow_actions:
624
+ return False, (
625
+ "The administrator for this server has not allowed actions.\n\n"
626
+ "Please contact the system administrator, or if you are running this server, "
627
+ "open the configuration file with `edit config system` and search for 'permissions'."
628
+ " Under the keys `api:permissions:actions`, " +
629
+ "you can toggle non-admin actions."
630
+ )
631
+ pipe = get_pipe(connector_keys, metric_key, location_key, instance_keys)
632
+ begin, end = pipe.parse_date_bounds(begin, end)
633
+ results = get_api_connector(instance_keys=instance_keys).clear_pipe(
634
+ pipe,
635
+ begin=begin,
636
+ end=end,
637
+ params=_params,
638
+ debug=debug,
639
+ )
640
+ pipes(instance_keys, refresh=True)
641
+ return results
642
+
643
+
644
+ @app.get(
645
+ pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/csv',
646
+ tags=['Pipes: Data'],
647
+ )
493
648
  def get_pipe_csv(
494
649
  connector_keys: str,
495
650
  metric_key: str,
@@ -505,18 +660,6 @@ def get_pipe_csv(
505
660
  """
506
661
  Get a pipe's data as a CSV file. Optionally set query boundaries.
507
662
  """
508
- if begin is not None:
509
- begin = (
510
- int(begin)
511
- if is_int(begin)
512
- else dateutil_parser.parse(begin)
513
- )
514
- if end is not None:
515
- end = (
516
- int(end)
517
- if is_int(end)
518
- else dateutil_parser.parse(end)
519
- )
520
663
 
521
664
  _params = {}
522
665
  if params == 'null':
@@ -540,6 +683,7 @@ def get_pipe_csv(
540
683
  detail="Pipe must be registered."
541
684
  )
542
685
 
686
+ begin, end = pipe.parse_date_bounds(begin, end)
543
687
  dt_col = pipe.columns.get('datetime', None)
544
688
  if dt_col:
545
689
  if begin is None:
@@ -567,7 +711,10 @@ def get_pipe_csv(
567
711
  return response
568
712
 
569
713
 
570
- @app.get(pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/id', tags=['Pipes'])
714
+ @app.get(
715
+ pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/id',
716
+ tags=['Pipes: Attributes'],
717
+ )
571
718
  def get_pipe_id(
572
719
  connector_keys: str,
573
720
  metric_key: str,
@@ -588,7 +735,7 @@ def get_pipe_id(
588
735
 
589
736
  @app.get(
590
737
  pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/attributes',
591
- tags=['Pipes']
738
+ tags=['Pipes: Attributes'],
592
739
  )
593
740
  def get_pipe_attributes(
594
741
  connector_keys: str,
@@ -609,7 +756,10 @@ def get_pipe_attributes(
609
756
  ).attributes
610
757
 
611
758
 
612
- @app.get(pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/exists', tags=['Pipes'])
759
+ @app.get(
760
+ pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/exists',
761
+ tags=['Pipes: Data'],
762
+ )
613
763
  def get_pipe_exists(
614
764
  connector_keys: str,
615
765
  metric_key: str,
@@ -623,7 +773,7 @@ def get_pipe_exists(
623
773
  return get_pipe(connector_keys, metric_key, location_key, instance_keys).exists(debug=debug)
624
774
 
625
775
 
626
- @app.post(endpoints['metadata'], tags=['Pipes'])
776
+ @app.post(endpoints['metadata'], tags=['Misc'])
627
777
  def create_metadata(
628
778
  instance_keys: Optional[str] = None,
629
779
  curr_user = (
@@ -641,7 +791,10 @@ def create_metadata(
641
791
  return True
642
792
 
643
793
 
644
- @app.get(pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/rowcount', tags=['Pipes'])
794
+ @app.get(
795
+ pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/rowcount',
796
+ tags=['Pipes: Data'],
797
+ )
645
798
  def get_pipe_rowcount(
646
799
  connector_keys: str,
647
800
  metric_key: str,
@@ -692,7 +845,7 @@ def get_pipe_rowcount(
692
845
 
693
846
  @app.get(
694
847
  pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/columns/types',
695
- tags=['Pipes']
848
+ tags=['Pipes: Data'],
696
849
  )
697
850
  def get_pipe_columns_types(
698
851
  connector_keys: str,
@@ -720,7 +873,7 @@ def get_pipe_columns_types(
720
873
 
721
874
  @app.get(
722
875
  pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/columns/indices',
723
- tags=['Pipes']
876
+ tags=['Pipes: Data'],
724
877
  )
725
878
  def get_pipe_columns_indices(
726
879
  connector_keys: str,
@@ -774,7 +927,7 @@ def get_pipe_columns_indices(
774
927
 
775
928
  @app.get(
776
929
  pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/indices/names',
777
- tags=['Pipes']
930
+ tags=['Pipes: Data']
778
931
  )
779
932
  def get_pipe_index_names(
780
933
  connector_keys: str,
@@ -784,7 +937,7 @@ def get_pipe_index_names(
784
937
  curr_user=(
785
938
  fastapi.Depends(manager) if not no_auth else None
786
939
  ),
787
- ) -> Dict[str, List[Dict[str, str]]]:
940
+ ) -> Dict[str, Union[str, Dict[str, str], List[Dict[str, str]]]]:
788
941
  """
789
942
  Return a dictionary of index keys and index names.
790
943
 
@@ -795,4 +948,4 @@ def get_pipe_index_names(
795
948
  metric_key,
796
949
  location_key,
797
950
  instance_keys,
798
- ).get_indices(debug=debug)
951
+ ).get_indices()
@@ -70,6 +70,7 @@ default_system_config = {
70
70
  'sql': {
71
71
  'bulk_insert': {
72
72
  'postgresql': True,
73
+ 'postgis': True,
73
74
  'citus': True,
74
75
  'timescaledb': True,
75
76
  'mssql': True,
@@ -113,6 +114,9 @@ default_system_config = {
113
114
  },
114
115
  'data': {
115
116
  'max_response_row_limit': 100_000,
117
+ 'chunks': {
118
+ 'ttl_seconds': 1800,
119
+ },
116
120
  },
117
121
  'endpoints': {
118
122
  'docs_in_production': True,
@@ -2,4 +2,4 @@
2
2
  Specify the Meerschaum release version.
3
3
  """
4
4
 
5
- __version__ = "2.8.4"
5
+ __version__ = "2.9.0"
@@ -34,6 +34,7 @@ __all__ = (
34
34
  "api",
35
35
  "sql",
36
36
  "valkey",
37
+ "parse",
37
38
  )
38
39
 
39
40
  ### store connectors partitioned by
@@ -6,8 +6,10 @@
6
6
  Interact with Meerschaum APIs. May be chained together (see 'meerschaum:api_instance' in your config.yaml).
7
7
  """
8
8
 
9
+ from __future__ import annotations
10
+
9
11
  from datetime import datetime, timedelta, timezone
10
- from meerschaum.utils.typing import Optional, List
12
+ from meerschaum.utils.typing import Optional, List, Union
11
13
  from meerschaum.connectors import Connector
12
14
  from meerschaum.utils.warnings import warn, error
13
15
  from meerschaum.utils.packages import attempt_import
@@ -43,6 +45,7 @@ class APIConnector(Connector):
43
45
  )
44
46
  from ._misc import get_mrsm_version, get_chaining_status
45
47
  from ._pipes import (
48
+ get_pipe_instance_keys,
46
49
  register_pipe,
47
50
  fetch_pipes_keys,
48
51
  edit_pipe,
@@ -143,6 +146,7 @@ class APIConnector(Connector):
143
146
  self._token = None
144
147
  self._expires = None
145
148
  self._session = None
149
+ self._instance_keys = self.__dict__.get('instance_keys', None)
146
150
 
147
151
 
148
152
  @property
@@ -195,3 +199,10 @@ class APIConnector(Connector):
195
199
  warn(msg, stack=False)
196
200
  self._emitted_warning = True
197
201
  return self._token
202
+
203
+ @property
204
+ def instance_keys(self) -> Union[str, None]:
205
+ """
206
+ Return the instance keys to be sent alongside pipe requests.
207
+ """
208
+ return self._instance_keys