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.
- meerschaum/api/_chunks.py +67 -0
- meerschaum/api/dash/callbacks/__init__.py +5 -2
- meerschaum/api/dash/callbacks/custom.py +21 -8
- meerschaum/api/dash/callbacks/dashboard.py +26 -4
- meerschaum/api/dash/callbacks/settings/__init__.py +8 -0
- meerschaum/api/dash/callbacks/settings/password_reset.py +76 -0
- meerschaum/api/dash/components.py +136 -25
- meerschaum/api/dash/pages/__init__.py +1 -0
- meerschaum/api/dash/pages/dashboard.py +11 -9
- meerschaum/api/dash/pages/plugins.py +31 -27
- meerschaum/api/dash/pages/settings/__init__.py +8 -0
- meerschaum/api/dash/pages/settings/password_reset.py +63 -0
- meerschaum/api/dash/webterm.py +6 -3
- meerschaum/api/resources/static/css/dash.css +8 -1
- meerschaum/api/resources/templates/termpage.html +4 -0
- meerschaum/api/routes/_pipes.py +232 -79
- meerschaum/config/_default.py +4 -0
- meerschaum/config/_version.py +1 -1
- meerschaum/connectors/__init__.py +1 -0
- meerschaum/connectors/api/_APIConnector.py +12 -1
- meerschaum/connectors/api/_pipes.py +106 -45
- meerschaum/connectors/api/_plugins.py +51 -45
- meerschaum/connectors/api/_request.py +1 -1
- meerschaum/connectors/parse.py +1 -2
- meerschaum/connectors/sql/_SQLConnector.py +3 -0
- meerschaum/connectors/sql/_cli.py +1 -0
- meerschaum/connectors/sql/_create_engine.py +51 -4
- meerschaum/connectors/sql/_pipes.py +38 -6
- meerschaum/connectors/sql/_sql.py +35 -4
- meerschaum/connectors/valkey/_ValkeyConnector.py +2 -0
- meerschaum/connectors/valkey/_pipes.py +51 -39
- meerschaum/core/Pipe/__init__.py +1 -0
- meerschaum/core/Pipe/_data.py +1 -2
- meerschaum/core/Pipe/_sync.py +64 -4
- meerschaum/plugins/_Plugin.py +21 -5
- meerschaum/plugins/__init__.py +32 -8
- meerschaum/utils/dataframe.py +139 -2
- meerschaum/utils/dtypes/__init__.py +211 -1
- meerschaum/utils/dtypes/sql.py +296 -5
- meerschaum/utils/formatting/_shell.py +1 -4
- meerschaum/utils/misc.py +1 -1
- meerschaum/utils/packages/_packages.py +7 -1
- meerschaum/utils/sql.py +139 -11
- meerschaum/utils/venv/__init__.py +6 -1
- {meerschaum-2.8.4.dist-info → meerschaum-2.9.0.dist-info}/METADATA +17 -3
- {meerschaum-2.8.4.dist-info → meerschaum-2.9.0.dist-info}/RECORD +52 -52
- {meerschaum-2.8.4.dist-info → meerschaum-2.9.0.dist-info}/WHEEL +1 -1
- meerschaum/_internal/gui/__init__.py +0 -43
- meerschaum/_internal/gui/app/__init__.py +0 -50
- meerschaum/_internal/gui/app/_windows.py +0 -74
- meerschaum/_internal/gui/app/actions.py +0 -30
- meerschaum/_internal/gui/app/pipes.py +0 -47
- {meerschaum-2.8.4.dist-info → meerschaum-2.9.0.dist-info}/LICENSE +0 -0
- {meerschaum-2.8.4.dist-info → meerschaum-2.9.0.dist-info}/NOTICE +0 -0
- {meerschaum-2.8.4.dist-info → meerschaum-2.9.0.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.8.4.dist-info → meerschaum-2.9.0.dist-info}/top_level.txt +0 -0
- {meerschaum-2.8.4.dist-info → meerschaum-2.9.0.dist-info}/zip-safe +0 -0
meerschaum/api/dash/webterm.py
CHANGED
@@ -53,19 +53,22 @@ def get_webterm(state: WebState) -> Tuple[Any, Any]:
|
|
53
53
|
html.Div(
|
54
54
|
[
|
55
55
|
dbc.Button(
|
56
|
-
|
56
|
+
"⟳",
|
57
57
|
color='black',
|
58
|
+
size='sm',
|
58
59
|
id='webterm-refresh-button',
|
59
60
|
),
|
60
61
|
dbc.Button(
|
61
|
-
'
|
62
|
+
'⛶',
|
62
63
|
color='black',
|
64
|
+
size='sm',
|
63
65
|
id='webterm-fullscreen-button',
|
64
66
|
),
|
65
67
|
] + [
|
66
68
|
dbc.Button(
|
67
|
-
'
|
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:
|
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
|
+
}
|
meerschaum/api/routes/_pipes.py
CHANGED
@@ -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(
|
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.
|
84
|
-
|
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
|
-
|
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
|
-
|
111
|
-
|
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
|
-
|
145
|
-
|
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
|
-
|
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
|
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(
|
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(
|
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(
|
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(
|
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 ('
|
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(
|
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(
|
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(
|
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(
|
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=['
|
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(
|
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(
|
951
|
+
).get_indices()
|
meerschaum/config/_default.py
CHANGED
@@ -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,
|
meerschaum/config/_version.py
CHANGED
@@ -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
|