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.
- meerschaum/__init__.py +5 -2
- meerschaum/_internal/__init__.py +1 -0
- meerschaum/_internal/arguments/_parse_arguments.py +4 -4
- meerschaum/_internal/arguments/_parser.py +19 -2
- meerschaum/_internal/docs/index.py +49 -2
- meerschaum/_internal/entry.py +6 -6
- meerschaum/_internal/shell/Shell.py +1 -1
- meerschaum/_internal/static.py +356 -0
- meerschaum/actions/api.py +12 -2
- meerschaum/actions/bootstrap.py +7 -7
- meerschaum/actions/edit.py +142 -18
- meerschaum/actions/register.py +137 -6
- meerschaum/actions/show.py +117 -29
- meerschaum/actions/stop.py +4 -1
- meerschaum/actions/sync.py +1 -1
- meerschaum/actions/tag.py +9 -8
- meerschaum/actions/verify.py +5 -8
- meerschaum/api/__init__.py +11 -3
- meerschaum/api/_events.py +39 -2
- meerschaum/api/_oauth2.py +118 -8
- meerschaum/api/_tokens.py +102 -0
- meerschaum/api/dash/__init__.py +0 -3
- meerschaum/api/dash/callbacks/custom.py +2 -2
- meerschaum/api/dash/callbacks/dashboard.py +103 -19
- meerschaum/api/dash/callbacks/plugins.py +0 -1
- meerschaum/api/dash/callbacks/register.py +1 -1
- meerschaum/api/dash/callbacks/settings/__init__.py +1 -0
- meerschaum/api/dash/callbacks/settings/password_reset.py +2 -2
- meerschaum/api/dash/callbacks/settings/tokens.py +388 -0
- meerschaum/api/dash/components.py +30 -8
- meerschaum/api/dash/keys.py +19 -93
- meerschaum/api/dash/pages/dashboard.py +1 -20
- meerschaum/api/dash/pages/settings/__init__.py +1 -0
- meerschaum/api/dash/pages/settings/password_reset.py +1 -1
- meerschaum/api/dash/pages/settings/tokens.py +55 -0
- meerschaum/api/dash/pipes.py +94 -59
- meerschaum/api/dash/sessions.py +12 -0
- meerschaum/api/dash/tokens.py +606 -0
- meerschaum/api/dash/websockets.py +1 -1
- meerschaum/api/dash/webterm.py +4 -0
- meerschaum/api/models/__init__.py +23 -3
- meerschaum/api/models/_actions.py +22 -0
- meerschaum/api/models/_pipes.py +85 -7
- meerschaum/api/models/_tokens.py +81 -0
- meerschaum/api/resources/templates/termpage.html +12 -0
- meerschaum/api/routes/__init__.py +1 -0
- meerschaum/api/routes/_actions.py +3 -4
- meerschaum/api/routes/_connectors.py +3 -7
- meerschaum/api/routes/_jobs.py +14 -35
- meerschaum/api/routes/_login.py +49 -12
- meerschaum/api/routes/_misc.py +5 -10
- meerschaum/api/routes/_pipes.py +173 -140
- meerschaum/api/routes/_plugins.py +38 -28
- meerschaum/api/routes/_tokens.py +236 -0
- meerschaum/api/routes/_users.py +47 -35
- meerschaum/api/routes/_version.py +3 -3
- meerschaum/config/__init__.py +43 -20
- meerschaum/config/_default.py +43 -6
- meerschaum/config/_edit.py +28 -24
- meerschaum/config/_environment.py +1 -1
- meerschaum/config/_patch.py +6 -6
- meerschaum/config/_paths.py +5 -1
- meerschaum/config/_read_config.py +65 -34
- meerschaum/config/_sync.py +6 -3
- meerschaum/config/_version.py +1 -1
- meerschaum/config/stack/__init__.py +31 -11
- meerschaum/config/static.py +18 -0
- meerschaum/connectors/_Connector.py +10 -4
- meerschaum/connectors/__init__.py +4 -20
- meerschaum/connectors/api/_APIConnector.py +34 -6
- meerschaum/connectors/api/_actions.py +2 -2
- meerschaum/connectors/api/_jobs.py +1 -1
- meerschaum/connectors/api/_login.py +33 -7
- meerschaum/connectors/api/_misc.py +2 -2
- meerschaum/connectors/api/_pipes.py +16 -31
- meerschaum/connectors/api/_plugins.py +2 -2
- meerschaum/connectors/api/_request.py +1 -1
- meerschaum/connectors/api/_tokens.py +146 -0
- meerschaum/connectors/api/_users.py +70 -58
- meerschaum/connectors/instance/_InstanceConnector.py +83 -0
- meerschaum/connectors/instance/__init__.py +10 -0
- meerschaum/connectors/instance/_pipes.py +442 -0
- meerschaum/connectors/instance/_plugins.py +151 -0
- meerschaum/connectors/instance/_tokens.py +296 -0
- meerschaum/connectors/instance/_users.py +181 -0
- meerschaum/connectors/parse.py +4 -1
- meerschaum/connectors/sql/_SQLConnector.py +8 -5
- meerschaum/connectors/sql/_cli.py +12 -11
- meerschaum/connectors/sql/_create_engine.py +9 -168
- meerschaum/connectors/sql/_fetch.py +2 -18
- meerschaum/connectors/sql/_pipes.py +156 -190
- meerschaum/connectors/sql/_plugins.py +29 -0
- meerschaum/connectors/sql/_sql.py +46 -21
- meerschaum/connectors/sql/_users.py +29 -2
- meerschaum/connectors/sql/tables/__init__.py +1 -1
- meerschaum/connectors/valkey/_ValkeyConnector.py +2 -4
- meerschaum/connectors/valkey/_pipes.py +53 -26
- meerschaum/connectors/valkey/_plugins.py +2 -26
- meerschaum/core/Pipe/__init__.py +59 -19
- meerschaum/core/Pipe/_attributes.py +412 -90
- meerschaum/core/Pipe/_bootstrap.py +54 -24
- meerschaum/core/Pipe/_data.py +96 -18
- meerschaum/core/Pipe/_dtypes.py +48 -18
- meerschaum/core/Pipe/_edit.py +14 -4
- meerschaum/core/Pipe/_fetch.py +1 -1
- meerschaum/core/Pipe/_show.py +5 -5
- meerschaum/core/Pipe/_sync.py +118 -193
- meerschaum/core/Pipe/_verify.py +4 -4
- meerschaum/{plugins → core/Plugin}/_Plugin.py +9 -11
- meerschaum/core/Plugin/__init__.py +1 -1
- meerschaum/core/Token/_Token.py +220 -0
- meerschaum/core/Token/__init__.py +12 -0
- meerschaum/core/User/_User.py +34 -8
- meerschaum/core/User/__init__.py +9 -1
- meerschaum/core/__init__.py +1 -0
- meerschaum/jobs/_Job.py +3 -2
- meerschaum/jobs/__init__.py +3 -2
- meerschaum/jobs/systemd.py +1 -1
- meerschaum/models/__init__.py +35 -0
- meerschaum/models/pipes.py +247 -0
- meerschaum/models/tokens.py +38 -0
- meerschaum/models/users.py +26 -0
- meerschaum/plugins/__init__.py +22 -7
- meerschaum/plugins/bootstrap.py +2 -1
- meerschaum/utils/_get_pipes.py +68 -27
- meerschaum/utils/daemon/Daemon.py +2 -1
- meerschaum/utils/daemon/__init__.py +30 -2
- meerschaum/utils/dataframe.py +473 -81
- meerschaum/utils/debug.py +15 -15
- meerschaum/utils/dtypes/__init__.py +473 -34
- meerschaum/utils/dtypes/sql.py +368 -28
- meerschaum/utils/formatting/__init__.py +1 -1
- meerschaum/utils/formatting/_pipes.py +5 -4
- meerschaum/utils/formatting/_shell.py +11 -9
- meerschaum/utils/misc.py +246 -148
- meerschaum/utils/packages/__init__.py +10 -27
- meerschaum/utils/packages/_packages.py +41 -34
- meerschaum/utils/pipes.py +181 -0
- meerschaum/utils/process.py +1 -1
- meerschaum/utils/prompt.py +3 -1
- meerschaum/utils/schedule.py +2 -1
- meerschaum/utils/sql.py +121 -44
- meerschaum/utils/typing.py +1 -4
- meerschaum/utils/venv/_Venv.py +2 -2
- meerschaum/utils/venv/__init__.py +5 -7
- {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc2.dist-info}/METADATA +92 -96
- meerschaum-3.0.0rc2.dist-info/RECORD +283 -0
- {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc2.dist-info}/WHEEL +1 -1
- meerschaum-3.0.0rc2.dist-info/licenses/NOTICE +2 -0
- meerschaum/api/models/_interfaces.py +0 -15
- meerschaum/api/models/_locations.py +0 -15
- meerschaum/api/models/_metrics.py +0 -15
- meerschaum/config/static/__init__.py +0 -186
- meerschaum-2.9.5.dist-info/RECORD +0 -263
- {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc2.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc2.dist-info}/licenses/LICENSE +0 -0
- {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc2.dist-info}/top_level.txt +0 -0
- {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc2.dist-info}/zip-safe +0 -0
meerschaum/api/routes/_pipes.py
CHANGED
@@ -23,9 +23,22 @@ from meerschaum.api import (
|
|
23
23
|
pipes,
|
24
24
|
get_pipe,
|
25
25
|
_get_pipes,
|
26
|
-
manager,
|
27
26
|
debug,
|
28
|
-
|
27
|
+
ScopedAuth,
|
28
|
+
)
|
29
|
+
from meerschaum.models import (
|
30
|
+
ConnectorKeysModel,
|
31
|
+
MetricKeyModel,
|
32
|
+
LocationKeyModel,
|
33
|
+
InstanceKeysModel,
|
34
|
+
PipeModel,
|
35
|
+
PipeWithParametersModel,
|
36
|
+
PipesWithParametersDictModel,
|
37
|
+
)
|
38
|
+
from meerschaum.api.models import (
|
39
|
+
SuccessTupleResponseModel,
|
40
|
+
FetchPipesKeysResponseModel,
|
41
|
+
SyncPipeRequestModel,
|
29
42
|
)
|
30
43
|
from meerschaum.api._chunks import generate_chunks_cursor_token
|
31
44
|
from meerschaum.utils.packages import attempt_import
|
@@ -35,6 +48,7 @@ from meerschaum.utils.misc import (
|
|
35
48
|
is_pipe_registered,
|
36
49
|
is_int,
|
37
50
|
replace_pipes_in_dict,
|
51
|
+
string_to_dict,
|
38
52
|
)
|
39
53
|
from meerschaum.connectors.sql.tables import get_tables
|
40
54
|
|
@@ -49,6 +63,7 @@ MAX_RESPONSE_ROW_LIMIT: int = mrsm.get_config('system', 'api', 'data', 'max_resp
|
|
49
63
|
@app.post(
|
50
64
|
pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/register',
|
51
65
|
tags=['Pipes: Attributes'],
|
66
|
+
response_model=SuccessTupleResponseModel,
|
52
67
|
)
|
53
68
|
def register_pipe(
|
54
69
|
connector_keys: str,
|
@@ -56,10 +71,8 @@ def register_pipe(
|
|
56
71
|
location_key: str,
|
57
72
|
instance_keys: Optional[str] = None,
|
58
73
|
parameters: Optional[Dict[str, Any]] = None,
|
59
|
-
curr_user = (
|
60
|
-
|
61
|
-
),
|
62
|
-
):
|
74
|
+
curr_user = fastapi.Security(ScopedAuth(['pipes:write']), scopes=['pipes:write']),
|
75
|
+
) -> mrsm.SuccessTuple:
|
63
76
|
"""
|
64
77
|
Register a new pipe.
|
65
78
|
"""
|
@@ -69,7 +82,7 @@ def register_pipe(
|
|
69
82
|
"The administrator for this server has not allowed pipe registration.\n\n"
|
70
83
|
"Please contact the system administrator, or if you are running this server, "
|
71
84
|
"open the configuration file with `edit config system` and search for 'permissions'."
|
72
|
-
" Under the keys `api:permissions:registration`, " +
|
85
|
+
" Under the keys `api:permissions:registration`, " +
|
73
86
|
"you can toggle various registration types."
|
74
87
|
)
|
75
88
|
pipe = get_pipe(connector_keys, metric_key, location_key, instance_keys)
|
@@ -79,14 +92,16 @@ def register_pipe(
|
|
79
92
|
)
|
80
93
|
if parameters:
|
81
94
|
pipe.parameters = parameters
|
82
|
-
|
83
|
-
|
84
|
-
|
95
|
+
|
96
|
+
success, msg = get_api_connector(instance_keys).register_pipe(pipe, debug=debug)
|
97
|
+
_ = pipes(instance_keys, refresh=True)
|
98
|
+
return success, msg
|
85
99
|
|
86
100
|
|
87
101
|
@app.patch(
|
88
102
|
pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/edit',
|
89
103
|
tags=['Pipes: Attributes'],
|
104
|
+
response_model=SuccessTupleResponseModel,
|
90
105
|
)
|
91
106
|
def edit_pipe(
|
92
107
|
connector_keys: str,
|
@@ -95,10 +110,8 @@ def edit_pipe(
|
|
95
110
|
parameters: dict,
|
96
111
|
instance_keys: Optional[str] = None,
|
97
112
|
patch: bool = False,
|
98
|
-
curr_user = (
|
99
|
-
|
100
|
-
),
|
101
|
-
):
|
113
|
+
curr_user = fastapi.Security(ScopedAuth(['pipes:write'])),
|
114
|
+
) -> mrsm.SuccessTuple:
|
102
115
|
"""
|
103
116
|
Edit an existing pipe's parameters.
|
104
117
|
"""
|
@@ -111,53 +124,50 @@ def edit_pipe(
|
|
111
124
|
" Under the keys `api:permissions:actions`, "
|
112
125
|
"you can toggle non-admin actions."
|
113
126
|
)
|
127
|
+
|
114
128
|
pipe = get_pipe(connector_keys, metric_key, location_key, instance_keys)
|
115
129
|
if not is_pipe_registered(pipe, pipes(instance_keys, refresh=True)):
|
116
130
|
raise fastapi.HTTPException(
|
117
131
|
status_code=409, detail=f"{pipe} is not registered."
|
118
132
|
)
|
133
|
+
|
119
134
|
pipe.parameters = parameters
|
120
|
-
|
121
|
-
pipes(instance_keys, refresh=True)
|
122
|
-
return
|
135
|
+
success, msg = get_api_connector(instance_keys).edit_pipe(pipe, patch=patch, debug=debug)
|
136
|
+
_ = pipes(instance_keys, refresh=True)
|
137
|
+
return success, msg
|
123
138
|
|
124
139
|
|
125
140
|
@app.delete(
|
126
141
|
pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/delete',
|
127
142
|
tags=['Pipes: Attributes'],
|
143
|
+
response_model=SuccessTupleResponseModel,
|
128
144
|
)
|
129
145
|
def delete_pipe(
|
130
146
|
connector_keys: str,
|
131
147
|
metric_key: str,
|
132
148
|
location_key: str,
|
133
149
|
instance_keys: Optional[str] = None,
|
134
|
-
curr_user = (
|
135
|
-
|
136
|
-
),
|
137
|
-
):
|
150
|
+
curr_user = fastapi.Security(ScopedAuth(['pipes:delete'])),
|
151
|
+
) -> SuccessTuple:
|
138
152
|
"""
|
139
153
|
Delete a Pipe (without dropping its table).
|
140
154
|
"""
|
141
|
-
allow_actions = mrsm.get_config('system', 'api', 'permissions', 'actions', 'non_admin')
|
142
|
-
if not allow_actions:
|
143
|
-
return False, (
|
144
|
-
"The administrator for this server has not allowed actions.\n\n"
|
145
|
-
"Please contact the system administrator, or if you are running this server, "
|
146
|
-
"open the configuration file with `edit config system` and search for 'permissions'."
|
147
|
-
" Under the keys `api:permissions:actions`, "
|
148
|
-
"you can toggle non-admin actions."
|
149
|
-
)
|
150
155
|
pipe = get_pipe(connector_keys, metric_key, location_key, instance_keys)
|
151
156
|
if not is_pipe_registered(pipe, pipes(instance_keys, refresh=True)):
|
152
157
|
raise fastapi.HTTPException(
|
153
158
|
status_code=409, detail=f"{pipe} is not registered."
|
154
159
|
)
|
155
|
-
|
156
|
-
|
157
|
-
|
160
|
+
|
161
|
+
success, msg = get_api_connector(instance_keys).delete_pipe(pipe, debug=debug)
|
162
|
+
_ = pipes(instance_keys, refresh=True)
|
163
|
+
return success, msg
|
158
164
|
|
159
165
|
|
160
|
-
@app.get(
|
166
|
+
@app.get(
|
167
|
+
pipes_endpoint + '/keys',
|
168
|
+
tags=['Pipes: Attributes'],
|
169
|
+
response_model=FetchPipesKeysResponseModel,
|
170
|
+
)
|
161
171
|
async def fetch_pipes_keys(
|
162
172
|
connector_keys: str = "[]",
|
163
173
|
metric_keys: str = "[]",
|
@@ -165,10 +175,8 @@ async def fetch_pipes_keys(
|
|
165
175
|
instance_keys: Optional[str] = None,
|
166
176
|
tags: str = "[]",
|
167
177
|
params: str = "{}",
|
168
|
-
curr_user = (
|
169
|
-
|
170
|
-
),
|
171
|
-
):
|
178
|
+
curr_user = fastapi.Security(ScopedAuth(['pipes:read'])),
|
179
|
+
) -> FetchPipesKeysResponseModel:
|
172
180
|
"""
|
173
181
|
Get a list of tuples of all registered pipes' keys.
|
174
182
|
"""
|
@@ -187,41 +195,52 @@ async def get_pipes(
|
|
187
195
|
connector_keys: str = "",
|
188
196
|
metric_keys: str = "",
|
189
197
|
location_keys: str = "",
|
190
|
-
instance_keys:
|
191
|
-
curr_user=(
|
192
|
-
fastapi.Depends(manager) if not no_auth else None
|
193
|
-
),
|
198
|
+
instance_keys: str = "",
|
199
|
+
curr_user=fastapi.Security(ScopedAuth(['pipes:read'])),
|
194
200
|
debug: bool = False,
|
195
|
-
) ->
|
201
|
+
) -> PipesWithParametersDictModel:
|
196
202
|
"""
|
197
203
|
Get all registered Pipes with metadata, excluding parameters.
|
198
204
|
"""
|
199
|
-
kw = {'debug': debug, 'mrsm_instance': get_api_connector(instance_keys)}
|
205
|
+
kw = {'debug': debug, 'mrsm_instance': get_api_connector(instance_keys or None)}
|
200
206
|
if connector_keys != "":
|
201
207
|
kw['connector_keys'] = connector_keys
|
202
208
|
if metric_keys != "":
|
203
209
|
kw['metric_keys'] = metric_keys
|
204
210
|
if location_keys != "":
|
205
211
|
kw['location_keys'] = location_keys
|
206
|
-
|
212
|
+
|
213
|
+
pipes_dict = replace_pipes_in_dict(_get_pipes(**kw), lambda p: p.attributes)
|
214
|
+
for metrics in pipes_dict.values():
|
215
|
+
for locations in metrics.values():
|
216
|
+
if None in locations:
|
217
|
+
locations['None'] = locations.pop(None)
|
218
|
+
return pipes_dict
|
207
219
|
|
208
220
|
|
209
221
|
@app.get(pipes_endpoint + '/{connector_keys}', tags=['Pipes: Attributes'])
|
210
222
|
async def get_pipes_by_connector(
|
211
223
|
connector_keys: str,
|
212
224
|
instance_keys: Optional[str] = None,
|
213
|
-
curr_user = (
|
214
|
-
fastapi.Depends(manager) if not no_auth else None
|
215
|
-
),
|
225
|
+
curr_user = fastapi.Security(ScopedAuth(['pipes:read'])),
|
216
226
|
) -> Dict[str, Any]:
|
217
227
|
"""
|
218
228
|
Get all registered Pipes by connector_keys with metadata, excluding parameters.
|
219
229
|
"""
|
220
|
-
if connector_keys not in pipes(instance_keys):
|
230
|
+
if connector_keys not in pipes(instance_keys, refresh=True):
|
221
231
|
raise fastapi.HTTPException(
|
222
232
|
status_code=404, detail=f"Connector '{connector_keys}' not found."
|
223
233
|
)
|
224
|
-
|
234
|
+
|
235
|
+
metrics = replace_pipes_in_dict(
|
236
|
+
pipes(instance_keys, refresh=False)[connector_keys],
|
237
|
+
lambda p: p.attributes
|
238
|
+
)
|
239
|
+
for locations in metrics.values():
|
240
|
+
if None in locations:
|
241
|
+
locations['None'] = locations.pop(None)
|
242
|
+
|
243
|
+
return metrics
|
225
244
|
|
226
245
|
|
227
246
|
@app.get(pipes_endpoint + '/{connector_keys}/{metric_key}', tags=['Pipes: Attributes'])
|
@@ -229,28 +248,33 @@ async def get_pipes_by_connector_and_metric(
|
|
229
248
|
connector_keys: str,
|
230
249
|
metric_key: str,
|
231
250
|
instance_keys: Optional[str] = None,
|
232
|
-
curr_user = (
|
233
|
-
fastapi.Depends(manager) if not no_auth else None
|
234
|
-
),
|
251
|
+
curr_user = fastapi.Security(ScopedAuth(['pipes:read'])),
|
235
252
|
):
|
236
253
|
"""
|
237
|
-
Get all registered Pipes by connector_keys and metric_key with metadata, excluding parameters.
|
254
|
+
Get all registered Pipes by `connector_keys` and `metric_key` with metadata, excluding parameters.
|
238
255
|
"""
|
239
|
-
if connector_keys not in pipes(instance_keys):
|
256
|
+
if connector_keys not in pipes(instance_keys, refresh=True):
|
240
257
|
raise fastapi.HTTPException(
|
241
258
|
status_code=404,
|
242
259
|
detail=f"Connector '{connector_keys}' not found.",
|
243
260
|
)
|
244
|
-
|
261
|
+
|
262
|
+
if metric_key not in pipes(instance_keys, refresh=False)[connector_keys]:
|
245
263
|
raise fastapi.HTTPException(
|
246
264
|
status_code=404,
|
247
265
|
detail=f"Metric '{metric_key}' not found.",
|
248
266
|
)
|
249
|
-
|
250
|
-
|
267
|
+
|
268
|
+
locations = replace_pipes_in_dict(
|
269
|
+
pipes(instance_keys, refresh=False)[connector_keys][metric_key],
|
251
270
|
lambda p: p.attributes
|
252
271
|
)
|
253
272
|
|
273
|
+
if None in locations:
|
274
|
+
locations['None'] = locations.pop(None)
|
275
|
+
|
276
|
+
return locations
|
277
|
+
|
254
278
|
|
255
279
|
@app.get(
|
256
280
|
pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}',
|
@@ -261,29 +285,30 @@ async def get_pipe_by_connector_and_metric_and_location(
|
|
261
285
|
metric_key: str,
|
262
286
|
location_key: str,
|
263
287
|
instance_keys: Optional[str] = None,
|
264
|
-
curr_user = (
|
265
|
-
fastapi.Depends(manager) if not no_auth else None
|
266
|
-
),
|
288
|
+
curr_user = fastapi.Security(ScopedAuth(['pipes:read'])),
|
267
289
|
):
|
268
290
|
"""
|
269
291
|
Get a specific Pipe with metadata, excluding parameters.
|
270
292
|
"""
|
271
|
-
if connector_keys not in pipes(instance_keys):
|
293
|
+
if connector_keys not in pipes(instance_keys, refresh=True):
|
272
294
|
raise fastapi.HTTPException(
|
273
295
|
status_code=404,
|
274
296
|
detail=f"Connector '{connector_keys}' not found.",
|
275
297
|
)
|
276
|
-
|
298
|
+
|
299
|
+
if metric_key not in pipes(instance_keys, refresh=False)[connector_keys]:
|
277
300
|
raise fastapi.HTTPException(status_code=404, detail=f"Metric '{metric_key}' not found.")
|
301
|
+
|
278
302
|
if location_key in ('[None]', 'None', 'null'):
|
279
303
|
location_key = None
|
280
|
-
|
304
|
+
|
305
|
+
if location_key not in pipes(instance_keys, refresh=False)[connector_keys][metric_key]:
|
281
306
|
raise fastapi.HTTPException(
|
282
307
|
status_code=404,
|
283
308
|
detail=f"location_key '{location_key}' not found."
|
284
309
|
)
|
285
310
|
|
286
|
-
return pipes(instance_keys)[connector_keys][metric_key][location_key].attributes
|
311
|
+
return pipes(instance_keys, refresh=False)[connector_keys][metric_key][location_key].attributes
|
287
312
|
|
288
313
|
|
289
314
|
@app.get(
|
@@ -299,9 +324,7 @@ def get_sync_time(
|
|
299
324
|
remote: bool = False,
|
300
325
|
round_down: bool = True,
|
301
326
|
instance_keys: Optional[str] = None,
|
302
|
-
curr_user = (
|
303
|
-
fastapi.Depends(manager) if not no_auth else None
|
304
|
-
),
|
327
|
+
curr_user = fastapi.Security(ScopedAuth(['pipes:read'])),
|
305
328
|
) -> Union[str, int, None]:
|
306
329
|
"""
|
307
330
|
Get a Pipe's latest datetime value.
|
@@ -323,31 +346,74 @@ def get_sync_time(
|
|
323
346
|
@app.post(
|
324
347
|
pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/data',
|
325
348
|
tags=['Pipes: Data'],
|
349
|
+
response_model=SuccessTupleResponseModel,
|
350
|
+
openapi_extra={
|
351
|
+
'requestBody': {
|
352
|
+
'content': {
|
353
|
+
'application/json': {
|
354
|
+
'example': [
|
355
|
+
{
|
356
|
+
'timestamp': '2026-01-01',
|
357
|
+
'id': 1,
|
358
|
+
'value': 100.1,
|
359
|
+
},
|
360
|
+
{
|
361
|
+
'timestamp': '2026-01-02',
|
362
|
+
'id': 1,
|
363
|
+
'value': 200.2,
|
364
|
+
},
|
365
|
+
],
|
366
|
+
},
|
367
|
+
'text/plain': {
|
368
|
+
'example': 'a:1,b:2',
|
369
|
+
},
|
370
|
+
},
|
371
|
+
'required': True,
|
372
|
+
},
|
373
|
+
},
|
326
374
|
)
|
327
|
-
def sync_pipe(
|
375
|
+
async def sync_pipe(
|
328
376
|
connector_keys: str,
|
329
377
|
metric_key: str,
|
330
378
|
location_key: str,
|
331
|
-
|
379
|
+
request: fastapi.Request,
|
332
380
|
instance_keys: Optional[str] = None,
|
333
381
|
check_existing: bool = True,
|
334
382
|
blocking: bool = True,
|
335
383
|
force: bool = False,
|
336
384
|
workers: Optional[int] = None,
|
337
385
|
columns: Optional[str] = None,
|
338
|
-
curr_user = (
|
339
|
-
fastapi.Depends(manager) if not no_auth else None
|
340
|
-
),
|
386
|
+
curr_user = fastapi.Security(ScopedAuth(['pipes:write'])),
|
341
387
|
debug: bool = False,
|
342
|
-
) ->
|
388
|
+
) -> mrsm.SuccessTuple:
|
343
389
|
"""
|
344
390
|
Add data to an existing Pipe.
|
345
391
|
See [`meerschaum.Pipe.sync`](https://docs.meerschaum.io/meerschaum.html#Pipe.sync).
|
346
392
|
"""
|
393
|
+
body = await request.body()
|
394
|
+
try:
|
395
|
+
data = json.loads(body)
|
396
|
+
except (json.JSONDecodeError, UnicodeDecodeError):
|
397
|
+
data = body.decode('utf-8', errors='replace')
|
398
|
+
|
399
|
+
if not data:
|
400
|
+
return True, "No data to sync."
|
401
|
+
|
402
|
+
if isinstance(data, str) and data.strip() and not data.lstrip()[0] not in ('{', '['):
|
403
|
+
try:
|
404
|
+
lines = data.splitlines()
|
405
|
+
data = [string_to_dict(line) for line in lines]
|
406
|
+
except Exception:
|
407
|
+
data = None
|
408
|
+
|
347
409
|
if not data:
|
348
|
-
|
349
|
-
|
350
|
-
|
410
|
+
raise fastapi.HTTPException(
|
411
|
+
status=400,
|
412
|
+
detail="Cannot sync given data.",
|
413
|
+
)
|
414
|
+
|
415
|
+
pipe = get_pipe(connector_keys, metric_key, location_key, instance_keys, refresh=True)
|
416
|
+
if pipe.target in ('mrsm_users', 'mrsm_plugins', 'mrsm_pipes', 'mrsm_tokens'):
|
351
417
|
raise fastapi.HTTPException(
|
352
418
|
status_code=409,
|
353
419
|
detail=f"Cannot sync data to protected table '{pipe.target}'.",
|
@@ -364,7 +430,7 @@ def sync_pipe(
|
|
364
430
|
force=force,
|
365
431
|
workers=workers,
|
366
432
|
)
|
367
|
-
return
|
433
|
+
return success, msg
|
368
434
|
|
369
435
|
|
370
436
|
@app.get(
|
@@ -387,9 +453,7 @@ def get_pipe_data(
|
|
387
453
|
date_unit: str = 'us',
|
388
454
|
double_precision: int = 15,
|
389
455
|
geometry_format: str = 'wkb_hex',
|
390
|
-
curr_user = (
|
391
|
-
fastapi.Depends(manager) if not no_auth else None
|
392
|
-
),
|
456
|
+
curr_user = fastapi.Security(ScopedAuth(['pipes:read'])),
|
393
457
|
) -> str:
|
394
458
|
"""
|
395
459
|
Get a pipe's data, applying any filtering.
|
@@ -525,6 +589,7 @@ def get_pipe_chunk_bounds(
|
|
525
589
|
end: Union[str, int, None] = None,
|
526
590
|
bounded: bool = True,
|
527
591
|
chunk_interval_minutes: Union[int, None] = None,
|
592
|
+
curr_user = fastapi.Security(ScopedAuth(['pipes:read'])),
|
528
593
|
) -> List[List[Union[str, int, None]]]:
|
529
594
|
"""
|
530
595
|
Return a list of request boundaries between `begin` and `end` (or the pipe's sync times).
|
@@ -558,37 +623,28 @@ def get_pipe_chunk_bounds(
|
|
558
623
|
@app.delete(
|
559
624
|
pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/drop',
|
560
625
|
tags=['Pipes: Data'],
|
626
|
+
response_model=SuccessTupleResponseModel,
|
561
627
|
)
|
562
628
|
def drop_pipe(
|
563
629
|
connector_keys: str,
|
564
630
|
metric_key: str,
|
565
631
|
location_key: str,
|
566
632
|
instance_keys: Optional[str] = None,
|
567
|
-
curr_user = (
|
568
|
-
|
569
|
-
),
|
570
|
-
):
|
633
|
+
curr_user = fastapi.Security(ScopedAuth(['pipes:drop'])),
|
634
|
+
) -> mrsm.SuccessTuple:
|
571
635
|
"""
|
572
636
|
Drop a pipe's target table.
|
573
637
|
"""
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
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
|
638
|
+
pipe = get_pipe(connector_keys, metric_key, location_key, instance_keys)
|
639
|
+
success, msg = pipe.drop(debug=debug)
|
640
|
+
_ = pipes(instance_keys, refresh=True)
|
641
|
+
return success, msg
|
587
642
|
|
588
643
|
|
589
644
|
@app.delete(
|
590
645
|
pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/clear',
|
591
646
|
tags=['Pipes: Data'],
|
647
|
+
response_model=SuccessTupleResponseModel,
|
592
648
|
)
|
593
649
|
def clear_pipe(
|
594
650
|
connector_keys: str,
|
@@ -598,10 +654,8 @@ def clear_pipe(
|
|
598
654
|
begin: Union[str, int, None] = None,
|
599
655
|
end: Union[str, int, None] = None,
|
600
656
|
params: Optional[str] = None,
|
601
|
-
curr_user = (
|
602
|
-
|
603
|
-
),
|
604
|
-
):
|
657
|
+
curr_user = fastapi.Security(ScopedAuth(['pipes:delete'])),
|
658
|
+
) -> SuccessTupleResponseModel:
|
605
659
|
"""
|
606
660
|
Delete rows from a pipe's target table.
|
607
661
|
"""
|
@@ -613,21 +667,13 @@ def clear_pipe(
|
|
613
667
|
_params = json.loads(params)
|
614
668
|
except Exception:
|
615
669
|
_params = None
|
670
|
+
|
616
671
|
if not isinstance(_params, dict):
|
617
672
|
raise fastapi.HTTPException(
|
618
673
|
status_code=409,
|
619
674
|
detail="Params must be a valid JSON-encoded dictionary.",
|
620
675
|
)
|
621
676
|
|
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
677
|
pipe = get_pipe(connector_keys, metric_key, location_key, instance_keys)
|
632
678
|
begin, end = pipe.parse_date_bounds(begin, end)
|
633
679
|
results = get_api_connector(instance_keys=instance_keys).clear_pipe(
|
@@ -637,7 +683,7 @@ def clear_pipe(
|
|
637
683
|
params=_params,
|
638
684
|
debug=debug,
|
639
685
|
)
|
640
|
-
pipes(instance_keys, refresh=True)
|
686
|
+
_ = pipes(instance_keys, refresh=True)
|
641
687
|
return results
|
642
688
|
|
643
689
|
|
@@ -653,9 +699,7 @@ def get_pipe_csv(
|
|
653
699
|
begin: Union[str, int, None] = None,
|
654
700
|
end: Union[str, int, None] = None,
|
655
701
|
params: Optional[str] = None,
|
656
|
-
curr_user = (
|
657
|
-
fastapi.Depends(manager) if not no_auth else None
|
658
|
-
),
|
702
|
+
curr_user = fastapi.Security(ScopedAuth(['pipes:read'])),
|
659
703
|
) -> str:
|
660
704
|
"""
|
661
705
|
Get a pipe's data as a CSV file. Optionally set query boundaries.
|
@@ -720,9 +764,7 @@ def get_pipe_id(
|
|
720
764
|
metric_key: str,
|
721
765
|
location_key: str,
|
722
766
|
instance_keys: Optional[str] = None,
|
723
|
-
curr_user = (
|
724
|
-
fastapi.Depends(manager) if not no_auth else None
|
725
|
-
),
|
767
|
+
curr_user = fastapi.Security(ScopedAuth(['pipes:read'])),
|
726
768
|
) -> Union[int, str]:
|
727
769
|
"""
|
728
770
|
Get a pipe's ID.
|
@@ -742,9 +784,7 @@ def get_pipe_attributes(
|
|
742
784
|
metric_key: str,
|
743
785
|
location_key: str,
|
744
786
|
instance_keys: Optional[str] = None,
|
745
|
-
curr_user=(
|
746
|
-
fastapi.Depends(manager) if not no_auth else None
|
747
|
-
),
|
787
|
+
curr_user=fastapi.Security(ScopedAuth(['pipes:read'])),
|
748
788
|
) -> Dict[str, Any]:
|
749
789
|
"""Get a pipe's attributes."""
|
750
790
|
return get_pipe(
|
@@ -765,9 +805,7 @@ def get_pipe_exists(
|
|
765
805
|
metric_key: str,
|
766
806
|
location_key: str,
|
767
807
|
instance_keys: Optional[str] = None,
|
768
|
-
curr_user = (
|
769
|
-
fastapi.Depends(manager) if not no_auth else None
|
770
|
-
),
|
808
|
+
curr_user = fastapi.Security(ScopedAuth(['pipes:read'])),
|
771
809
|
) -> bool:
|
772
810
|
"""Determine whether a pipe's target table exists."""
|
773
811
|
return get_pipe(connector_keys, metric_key, location_key, instance_keys).exists(debug=debug)
|
@@ -776,9 +814,7 @@ def get_pipe_exists(
|
|
776
814
|
@app.post(endpoints['metadata'], tags=['Misc'])
|
777
815
|
def create_metadata(
|
778
816
|
instance_keys: Optional[str] = None,
|
779
|
-
curr_user = (
|
780
|
-
fastapi.Depends(manager) if not no_auth else None
|
781
|
-
),
|
817
|
+
curr_user = fastapi.Security(ScopedAuth(['actions:execute'])),
|
782
818
|
) -> bool:
|
783
819
|
"""Create pipe instance metadata tables."""
|
784
820
|
conn = get_api_connector(instance_keys)
|
@@ -804,9 +840,7 @@ def get_pipe_rowcount(
|
|
804
840
|
end: Union[str, int, None] = None,
|
805
841
|
params: Optional[Dict[str, Any]] = None,
|
806
842
|
remote: bool = False,
|
807
|
-
curr_user = (
|
808
|
-
fastapi.Depends(manager) if not no_auth else None
|
809
|
-
),
|
843
|
+
curr_user = fastapi.Security(ScopedAuth(['pipes:read'])),
|
810
844
|
) -> int:
|
811
845
|
"""
|
812
846
|
Return a pipe's rowcount.
|
@@ -852,9 +886,7 @@ def get_pipe_columns_types(
|
|
852
886
|
metric_key: str,
|
853
887
|
location_key: str,
|
854
888
|
instance_keys: Optional[str] = None,
|
855
|
-
curr_user=(
|
856
|
-
fastapi.Depends(manager) if not no_auth else None
|
857
|
-
),
|
889
|
+
curr_user=fastapi.Security(ScopedAuth(['pipes:read'])),
|
858
890
|
) -> Dict[str, str]:
|
859
891
|
"""
|
860
892
|
Return a dictionary of column names and types.
|
@@ -868,7 +900,12 @@ def get_pipe_columns_types(
|
|
868
900
|
}
|
869
901
|
```
|
870
902
|
"""
|
871
|
-
|
903
|
+
pipe = get_pipe(connector_keys, metric_key, location_key, instance_keys)
|
904
|
+
columns_types = (
|
905
|
+
pipe.get_columns_types(debug=debug)
|
906
|
+
or pipe.get_dtypes(refresh=True, debug=debug)
|
907
|
+
)
|
908
|
+
return columns_types
|
872
909
|
|
873
910
|
|
874
911
|
@app.get(
|
@@ -880,9 +917,7 @@ def get_pipe_columns_indices(
|
|
880
917
|
metric_key: str,
|
881
918
|
location_key: str,
|
882
919
|
instance_keys: Optional[str] = None,
|
883
|
-
curr_user=(
|
884
|
-
fastapi.Depends(manager) if not no_auth else None
|
885
|
-
),
|
920
|
+
curr_user=fastapi.Security(ScopedAuth(['pipes:read'])),
|
886
921
|
) -> Dict[str, List[Dict[str, str]]]:
|
887
922
|
"""
|
888
923
|
Return a dictionary of column names and related indices.
|
@@ -934,9 +969,7 @@ def get_pipe_index_names(
|
|
934
969
|
metric_key: str,
|
935
970
|
location_key: str,
|
936
971
|
instance_keys: Optional[str] = None,
|
937
|
-
curr_user=(
|
938
|
-
fastapi.Depends(manager) if not no_auth else None
|
939
|
-
),
|
972
|
+
curr_user=fastapi.Security(ScopedAuth(['pipes:read'])),
|
940
973
|
) -> Dict[str, Union[str, Dict[str, str], List[Dict[str, str]]]]:
|
941
974
|
"""
|
942
975
|
Return a dictionary of index keys and index names.
|