meerschaum 2.7.9__py3-none-any.whl → 2.8.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/_internal/arguments/_parser.py +17 -5
- meerschaum/_internal/term/TermPageHandler.py +1 -1
- meerschaum/_internal/term/__init__.py +1 -1
- meerschaum/actions/api.py +36 -10
- meerschaum/actions/copy.py +3 -1
- meerschaum/actions/index.py +1 -1
- meerschaum/actions/show.py +7 -7
- meerschaum/actions/sync.py +5 -1
- meerschaum/actions/verify.py +14 -1
- meerschaum/api/__init__.py +77 -41
- meerschaum/api/_exceptions.py +18 -0
- meerschaum/api/dash/__init__.py +4 -2
- meerschaum/api/dash/callbacks/dashboard.py +30 -1
- meerschaum/api/dash/components.py +2 -2
- meerschaum/api/dash/webterm.py +23 -4
- meerschaum/api/models/_pipes.py +8 -8
- meerschaum/api/resources/static/css/dash.css +2 -2
- meerschaum/api/resources/templates/termpage.html +5 -1
- meerschaum/api/routes/__init__.py +15 -12
- meerschaum/api/routes/_connectors.py +30 -28
- meerschaum/api/routes/_index.py +16 -7
- meerschaum/api/routes/_misc.py +30 -22
- meerschaum/api/routes/_pipes.py +244 -148
- meerschaum/api/routes/_plugins.py +58 -47
- meerschaum/api/routes/_users.py +39 -31
- meerschaum/api/routes/_version.py +8 -10
- meerschaum/api/routes/_webterm.py +2 -2
- meerschaum/config/_default.py +10 -0
- meerschaum/config/_version.py +1 -1
- meerschaum/config/static/__init__.py +5 -2
- meerschaum/connectors/api/_APIConnector.py +4 -3
- meerschaum/connectors/api/_login.py +21 -17
- meerschaum/connectors/api/_pipes.py +1 -0
- meerschaum/connectors/api/_request.py +9 -10
- meerschaum/connectors/sql/_cli.py +11 -3
- meerschaum/connectors/sql/_instance.py +1 -1
- meerschaum/connectors/sql/_pipes.py +77 -57
- meerschaum/connectors/sql/_sql.py +26 -9
- meerschaum/core/Pipe/__init__.py +2 -0
- meerschaum/core/Pipe/_attributes.py +13 -2
- meerschaum/core/Pipe/_data.py +85 -0
- meerschaum/core/Pipe/_deduplicate.py +6 -8
- meerschaum/core/Pipe/_sync.py +63 -30
- meerschaum/core/Pipe/_verify.py +242 -77
- meerschaum/core/User/__init__.py +2 -6
- meerschaum/jobs/_Job.py +1 -1
- meerschaum/jobs/__init__.py +15 -0
- meerschaum/utils/dataframe.py +2 -0
- meerschaum/utils/dtypes/sql.py +26 -0
- meerschaum/utils/formatting/_pipes.py +1 -1
- meerschaum/utils/misc.py +11 -7
- meerschaum/utils/packages/_packages.py +1 -1
- meerschaum/utils/sql.py +6 -2
- {meerschaum-2.7.9.dist-info → meerschaum-2.8.0.dist-info}/METADATA +4 -4
- {meerschaum-2.7.9.dist-info → meerschaum-2.8.0.dist-info}/RECORD +61 -60
- {meerschaum-2.7.9.dist-info → meerschaum-2.8.0.dist-info}/LICENSE +0 -0
- {meerschaum-2.7.9.dist-info → meerschaum-2.8.0.dist-info}/NOTICE +0 -0
- {meerschaum-2.7.9.dist-info → meerschaum-2.8.0.dist-info}/WHEEL +0 -0
- {meerschaum-2.7.9.dist-info → meerschaum-2.8.0.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.7.9.dist-info → meerschaum-2.8.0.dist-info}/top_level.txt +0 -0
- {meerschaum-2.7.9.dist-info → meerschaum-2.8.0.dist-info}/zip-safe +0 -0
meerschaum/api/routes/_pipes.py
CHANGED
@@ -11,8 +11,7 @@ from __future__ import annotations
|
|
11
11
|
|
12
12
|
import io
|
13
13
|
import json
|
14
|
-
from
|
15
|
-
import datetime
|
14
|
+
from datetime import datetime, timedelta
|
16
15
|
|
17
16
|
import meerschaum as mrsm
|
18
17
|
from meerschaum.utils.typing import Any, Optional, Dict, Union, List
|
@@ -27,20 +26,15 @@ from meerschaum.api import (
|
|
27
26
|
manager,
|
28
27
|
debug,
|
29
28
|
no_auth,
|
30
|
-
private,
|
31
29
|
)
|
32
|
-
from meerschaum import
|
33
|
-
from meerschaum.
|
34
|
-
from meerschaum.utils.packages import attempt_import, import_pandas
|
35
|
-
from meerschaum.utils.dataframe import get_numeric_cols, to_json, parse_df_datetimes
|
36
|
-
from meerschaum.utils.dtypes import are_dtypes_equal
|
30
|
+
from meerschaum.utils.packages import attempt_import
|
31
|
+
from meerschaum.utils.dataframe import to_json
|
37
32
|
from meerschaum.utils.misc import (
|
38
33
|
is_pipe_registered,
|
39
|
-
round_time,
|
40
34
|
is_int,
|
41
35
|
replace_pipes_in_dict,
|
42
36
|
)
|
43
|
-
|
37
|
+
from meerschaum.connectors.sql.tables import get_tables
|
44
38
|
|
45
39
|
fastapi_responses = attempt_import('fastapi.responses', lazy=False)
|
46
40
|
StreamingResponse = fastapi_responses.StreamingResponse
|
@@ -48,13 +42,16 @@ dateutil_parser = attempt_import('dateutil.parser', lazy=False)
|
|
48
42
|
pipes_endpoint = endpoints['pipes']
|
49
43
|
pd = attempt_import('pandas', lazy=False)
|
50
44
|
|
45
|
+
MAX_RESPONSE_ROW_LIMIT: int = mrsm.get_config('system', 'api', 'data', 'max_response_row_limit')
|
46
|
+
|
51
47
|
|
52
48
|
@app.post(pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/register', tags=['Pipes'])
|
53
49
|
def register_pipe(
|
54
50
|
connector_keys: str,
|
55
51
|
metric_key: str,
|
56
52
|
location_key: str,
|
57
|
-
|
53
|
+
instance_keys: Optional[str] = None,
|
54
|
+
parameters: Optional[Dict[str, Any]] = None,
|
58
55
|
curr_user = (
|
59
56
|
fastapi.Depends(manager) if not no_auth else None
|
60
57
|
),
|
@@ -62,25 +59,24 @@ def register_pipe(
|
|
62
59
|
"""
|
63
60
|
Register a new pipe.
|
64
61
|
"""
|
65
|
-
|
66
|
-
allow_pipes = get_config('system', 'api', 'permissions', 'registration', 'pipes', patch=True)
|
62
|
+
allow_pipes = mrsm.get_config('system', 'api', 'permissions', 'registration', 'pipes')
|
67
63
|
if not allow_pipes:
|
68
64
|
return False, (
|
69
|
-
"The administrator for this server has not allowed pipe registration.\n\n"
|
70
|
-
"Please contact the system administrator, or if you are running this server, "
|
71
|
-
"open the configuration file with `edit config system` and search for 'permissions'."
|
65
|
+
"The administrator for this server has not allowed pipe registration.\n\n"
|
66
|
+
"Please contact the system administrator, or if you are running this server, "
|
67
|
+
"open the configuration file with `edit config system` and search for 'permissions'."
|
72
68
|
" Under the keys `api:permissions:registration`, " +
|
73
69
|
"you can toggle various registration types."
|
74
70
|
)
|
75
|
-
|
76
|
-
if is_pipe_registered(
|
71
|
+
pipe = get_pipe(connector_keys, metric_key, location_key, instance_keys)
|
72
|
+
if is_pipe_registered(pipe, pipes(instance_keys, refresh=True)):
|
77
73
|
raise fastapi.HTTPException(
|
78
|
-
status_code=409, detail=f"{
|
74
|
+
status_code=409, detail=f"{pipe} already registered."
|
79
75
|
)
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
76
|
+
if parameters:
|
77
|
+
pipe.parameters = parameters
|
78
|
+
results = get_api_connector(instance_keys).register_pipe(pipe, debug=debug)
|
79
|
+
pipes(instance_keys, refresh=True)
|
84
80
|
return results
|
85
81
|
|
86
82
|
|
@@ -89,6 +85,7 @@ def delete_pipe(
|
|
89
85
|
connector_keys: str,
|
90
86
|
metric_key: str,
|
91
87
|
location_key: str,
|
88
|
+
instance_keys: Optional[str] = None,
|
92
89
|
curr_user = (
|
93
90
|
fastapi.Depends(manager) if not no_auth else None
|
94
91
|
),
|
@@ -96,52 +93,50 @@ def delete_pipe(
|
|
96
93
|
"""
|
97
94
|
Delete a Pipe (without dropping its table).
|
98
95
|
"""
|
99
|
-
|
100
|
-
allow_actions = get_config('system', 'api', 'permissions', 'actions', 'non_admin')
|
96
|
+
allow_actions = mrsm.get_config('system', 'api', 'permissions', 'actions', 'non_admin')
|
101
97
|
if not allow_actions:
|
102
98
|
return False, (
|
103
|
-
"The administrator for this server has not allowed actions.\n\n"
|
104
|
-
"Please contact the system administrator, or if you are running this server, "
|
105
|
-
"open the configuration file with `edit config system` and search for 'permissions'."
|
106
|
-
" Under the keys `api:permissions:actions`, "
|
99
|
+
"The administrator for this server has not allowed actions.\n\n"
|
100
|
+
"Please contact the system administrator, or if you are running this server, "
|
101
|
+
"open the configuration file with `edit config system` and search for 'permissions'."
|
102
|
+
" Under the keys `api:permissions:actions`, "
|
107
103
|
"you can toggle non-admin actions."
|
108
104
|
)
|
109
|
-
pipe = get_pipe(connector_keys, metric_key, location_key)
|
110
|
-
if not is_pipe_registered(pipe, pipes(refresh=True)):
|
105
|
+
pipe = get_pipe(connector_keys, metric_key, location_key, instance_keys)
|
106
|
+
if not is_pipe_registered(pipe, pipes(instance_keys, refresh=True)):
|
111
107
|
raise fastapi.HTTPException(
|
112
108
|
status_code=409, detail=f"{pipe} is not registered."
|
113
109
|
)
|
114
|
-
results = get_api_connector().delete_pipe(pipe, debug=debug)
|
115
|
-
pipes(refresh=True)
|
116
|
-
|
110
|
+
results = get_api_connector(instance_keys).delete_pipe(pipe, debug=debug)
|
111
|
+
pipes(instance_keys, refresh=True)
|
117
112
|
return results
|
118
113
|
|
119
114
|
|
120
115
|
@app.delete(pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/drop', tags=['Pipes'])
|
121
116
|
def drop_pipe(
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
)
|
128
|
-
)
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
allow_actions = get_config('system', 'api', 'permissions', 'actions', 'non_admin')
|
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')
|
134
129
|
if not allow_actions:
|
135
130
|
return False, (
|
136
|
-
"The administrator for this server has not allowed actions.\n\n"
|
137
|
-
"Please contact the system administrator, or if you are running this server, "
|
138
|
-
"open the configuration file with `edit config system` and search for 'permissions'."
|
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'."
|
139
134
|
" Under the keys `api:permissions:actions`, " +
|
140
135
|
"you can toggle non-admin actions."
|
141
136
|
)
|
142
|
-
pipe_object = get_pipe(connector_keys, metric_key, location_key)
|
143
|
-
results = get_api_connector().drop_pipe(pipe_object, debug=debug)
|
144
|
-
pipes(refresh=True)
|
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)
|
139
|
+
pipes(instance_keys, refresh=True)
|
145
140
|
return results
|
146
141
|
|
147
142
|
|
@@ -152,33 +147,32 @@ def edit_pipe(
|
|
152
147
|
metric_key: str,
|
153
148
|
location_key: str,
|
154
149
|
parameters: dict,
|
150
|
+
instance_keys: Optional[str] = None,
|
155
151
|
patch: bool = False,
|
156
152
|
curr_user = (
|
157
153
|
fastapi.Depends(manager) if not no_auth else None
|
158
154
|
),
|
159
155
|
):
|
160
156
|
"""
|
161
|
-
Edit an existing pipe.
|
157
|
+
Edit an existing pipe's parameters.
|
162
158
|
"""
|
163
|
-
|
164
|
-
|
165
|
-
if not allow_pipes:
|
159
|
+
allow_actions = mrsm.get_config('system', 'api', 'permissions', 'actions', 'non_admin')
|
160
|
+
if not allow_actions:
|
166
161
|
return False, (
|
167
|
-
"The administrator for this server has not allowed actions.\n\n"
|
168
|
-
"Please contact the system administrator, or if you are running this server, "
|
169
|
-
"open the configuration file with `edit config system` and search for 'permissions'."
|
170
|
-
" Under the keys `api:permissions:actions`, "
|
162
|
+
"The administrator for this server has not allowed actions.\n\n"
|
163
|
+
"Please contact the system administrator, or if you are running this server, "
|
164
|
+
"open the configuration file with `edit config system` and search for 'permissions'."
|
165
|
+
" Under the keys `api:permissions:actions`, "
|
171
166
|
"you can toggle non-admin actions."
|
172
167
|
)
|
173
|
-
|
174
|
-
if not is_pipe_registered(
|
168
|
+
pipe = get_pipe(connector_keys, metric_key, location_key, instance_keys)
|
169
|
+
if not is_pipe_registered(pipe, pipes(instance_keys, refresh=True)):
|
175
170
|
raise fastapi.HTTPException(
|
176
|
-
status_code=409, detail=f"{
|
171
|
+
status_code=409, detail=f"{pipe} is not registered."
|
177
172
|
)
|
178
|
-
|
179
|
-
results = get_api_connector().edit_pipe(
|
180
|
-
pipes(refresh=True)
|
181
|
-
|
173
|
+
pipe.parameters = parameters
|
174
|
+
results = get_api_connector(instance_keys).edit_pipe(pipe, patch=patch, debug=debug)
|
175
|
+
pipes(instance_keys, refresh=True)
|
182
176
|
return results
|
183
177
|
|
184
178
|
|
@@ -187,6 +181,7 @@ async def fetch_pipes_keys(
|
|
187
181
|
connector_keys: str = "[]",
|
188
182
|
metric_keys: str = "[]",
|
189
183
|
location_keys: str = "[]",
|
184
|
+
instance_keys: Optional[str] = None,
|
190
185
|
tags: str = "[]",
|
191
186
|
params: str = "{}",
|
192
187
|
curr_user = (
|
@@ -194,14 +189,14 @@ async def fetch_pipes_keys(
|
|
194
189
|
),
|
195
190
|
):
|
196
191
|
"""
|
197
|
-
Get a list of tuples of all registered
|
192
|
+
Get a list of tuples of all registered pipes' keys.
|
198
193
|
"""
|
199
|
-
keys = get_api_connector().fetch_pipes_keys(
|
200
|
-
connector_keys
|
201
|
-
metric_keys
|
202
|
-
location_keys
|
203
|
-
tags
|
204
|
-
params
|
194
|
+
keys = get_api_connector(instance_keys).fetch_pipes_keys(
|
195
|
+
connector_keys=json.loads(connector_keys),
|
196
|
+
metric_keys=json.loads(metric_keys),
|
197
|
+
location_keys=json.loads(location_keys),
|
198
|
+
tags=json.loads(tags),
|
199
|
+
params=json.loads(params),
|
205
200
|
)
|
206
201
|
return keys
|
207
202
|
|
@@ -211,6 +206,7 @@ async def get_pipes(
|
|
211
206
|
connector_keys: str = "",
|
212
207
|
metric_keys: str = "",
|
213
208
|
location_keys: str = "",
|
209
|
+
instance_keys: Optional[str] = None,
|
214
210
|
curr_user=(
|
215
211
|
fastapi.Depends(manager) if not no_auth else None
|
216
212
|
),
|
@@ -219,18 +215,20 @@ async def get_pipes(
|
|
219
215
|
"""
|
220
216
|
Get all registered Pipes with metadata, excluding parameters.
|
221
217
|
"""
|
222
|
-
kw = {'debug'
|
218
|
+
kw = {'debug': debug, 'mrsm_instance': get_api_connector(instance_keys)}
|
223
219
|
if connector_keys != "":
|
224
220
|
kw['connector_keys'] = connector_keys
|
225
221
|
if metric_keys != "":
|
226
222
|
kw['metric_keys'] = metric_keys
|
227
223
|
if location_keys != "":
|
228
224
|
kw['location_keys'] = location_keys
|
229
|
-
return replace_pipes_in_dict(_get_pipes(**kw),
|
225
|
+
return replace_pipes_in_dict(_get_pipes(**kw), lambda p: p.attributes)
|
226
|
+
|
230
227
|
|
231
228
|
@app.get(pipes_endpoint + '/{connector_keys}', tags=['Pipes'])
|
232
229
|
async def get_pipes_by_connector(
|
233
230
|
connector_keys: str,
|
231
|
+
instance_keys: Optional[str] = None,
|
234
232
|
curr_user = (
|
235
233
|
fastapi.Depends(manager) if not no_auth else None
|
236
234
|
),
|
@@ -238,40 +236,39 @@ async def get_pipes_by_connector(
|
|
238
236
|
"""
|
239
237
|
Get all registered Pipes by connector_keys with metadata, excluding parameters.
|
240
238
|
"""
|
241
|
-
|
242
|
-
if connector_keys not in pipes():
|
239
|
+
if connector_keys not in pipes(instance_keys):
|
243
240
|
raise fastapi.HTTPException(
|
244
|
-
status_code=404, detail=f"
|
241
|
+
status_code=404, detail=f"Connector '{connector_keys}' not found."
|
245
242
|
)
|
246
|
-
return replace_pipes_in_dict(pipes()[connector_keys],
|
243
|
+
return replace_pipes_in_dict(pipes(instance_keys)[connector_keys], lambda p: p.attributes)
|
244
|
+
|
247
245
|
|
248
246
|
@app.get(pipes_endpoint + '/{connector_keys}/{metric_key}', tags=['Pipes'])
|
249
247
|
async def get_pipes_by_connector_and_metric(
|
250
248
|
connector_keys: str,
|
251
249
|
metric_key: str,
|
252
|
-
|
250
|
+
instance_keys: Optional[str] = None,
|
253
251
|
curr_user = (
|
254
252
|
fastapi.Depends(manager) if not no_auth else None
|
255
253
|
),
|
256
254
|
):
|
257
255
|
"""
|
258
256
|
Get all registered Pipes by connector_keys and metric_key with metadata, excluding parameters.
|
259
|
-
|
260
|
-
Parameters
|
261
|
-
----------
|
262
|
-
parent: bool, default False
|
263
|
-
Return the parent Pipe (`location_key` is `None`)
|
264
257
|
"""
|
265
|
-
|
266
|
-
if connector_keys not in pipes():
|
258
|
+
if connector_keys not in pipes(instance_keys):
|
267
259
|
raise fastapi.HTTPException(
|
268
|
-
status_code=404,
|
260
|
+
status_code=404,
|
261
|
+
detail=f"Connector '{connector_keys}' not found.",
|
269
262
|
)
|
270
|
-
if metric_key not in pipes()[connector_keys]:
|
271
|
-
raise fastapi.HTTPException(
|
272
|
-
|
273
|
-
|
274
|
-
|
263
|
+
if metric_key not in pipes(instance_keys)[connector_keys]:
|
264
|
+
raise fastapi.HTTPException(
|
265
|
+
status_code=404,
|
266
|
+
detail=f"Metric '{metric_key}' not found.",
|
267
|
+
)
|
268
|
+
return replace_pipes_in_dict(
|
269
|
+
pipes(instance_keys)[connector_keys][metric_key],
|
270
|
+
lambda p: p.attributes
|
271
|
+
)
|
275
272
|
|
276
273
|
|
277
274
|
@app.get(pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}', tags=['Pipes'])
|
@@ -279,6 +276,7 @@ async def get_pipes_by_connector_and_metric_and_location(
|
|
279
276
|
connector_keys: str,
|
280
277
|
metric_key: str,
|
281
278
|
location_key: str,
|
279
|
+
instance_keys: Optional[str] = None,
|
282
280
|
curr_user = (
|
283
281
|
fastapi.Depends(manager) if not no_auth else None
|
284
282
|
),
|
@@ -286,20 +284,22 @@ async def get_pipes_by_connector_and_metric_and_location(
|
|
286
284
|
"""
|
287
285
|
Get a specific Pipe with metadata, excluding parameters.
|
288
286
|
"""
|
289
|
-
if connector_keys not in pipes():
|
287
|
+
if connector_keys not in pipes(instance_keys):
|
290
288
|
raise fastapi.HTTPException(
|
291
|
-
status_code=404,
|
289
|
+
status_code=404,
|
290
|
+
detail=f"Connector '{connector_keys}' not found.",
|
292
291
|
)
|
293
|
-
if metric_key not in pipes()[connector_keys]:
|
294
|
-
raise fastapi.HTTPException(status_code=404, detail=f"
|
292
|
+
if metric_key not in pipes(instance_keys)[connector_keys]:
|
293
|
+
raise fastapi.HTTPException(status_code=404, detail=f"Metric '{metric_key}' not found.")
|
295
294
|
if location_key in ('[None]', 'None', 'null'):
|
296
295
|
location_key = None
|
297
|
-
if location_key not in pipes()[connector_keys][metric_key]:
|
296
|
+
if location_key not in pipes(instance_keys)[connector_keys][metric_key]:
|
298
297
|
raise fastapi.HTTPException(
|
299
|
-
status_code=404,
|
298
|
+
status_code=404,
|
299
|
+
detail=f"location_key '{location_key}' not found."
|
300
300
|
)
|
301
301
|
|
302
|
-
return str(pipes()[connector_keys][metric_key][location_key])
|
302
|
+
return str(pipes(instance_keys)[connector_keys][metric_key][location_key])
|
303
303
|
|
304
304
|
|
305
305
|
@app.get(pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/sync_time', tags=['Pipes'])
|
@@ -309,6 +309,7 @@ def get_sync_time(
|
|
309
309
|
location_key: str,
|
310
310
|
params: Optional[Dict[str, Any]] = None,
|
311
311
|
newest: bool = True,
|
312
|
+
remote: bool = False,
|
312
313
|
round_down: bool = True,
|
313
314
|
debug: bool = False,
|
314
315
|
curr_user = (
|
@@ -317,7 +318,7 @@ def get_sync_time(
|
|
317
318
|
) -> Union[str, int, None]:
|
318
319
|
"""
|
319
320
|
Get a Pipe's latest datetime value.
|
320
|
-
See `meerschaum.Pipe.get_sync_time
|
321
|
+
See [`meerschaum.Pipe.get_sync_time`](https://docs.meerschaum.io/meerschaum.html#Pipe.get_sync_time).
|
321
322
|
"""
|
322
323
|
if location_key == '[None]':
|
323
324
|
location_key = None
|
@@ -328,7 +329,7 @@ def get_sync_time(
|
|
328
329
|
debug = debug,
|
329
330
|
round_down = round_down,
|
330
331
|
)
|
331
|
-
if isinstance(sync_time, datetime
|
332
|
+
if isinstance(sync_time, datetime):
|
332
333
|
sync_time = sync_time.isoformat()
|
333
334
|
return sync_time
|
334
335
|
|
@@ -351,26 +352,26 @@ def sync_pipe(
|
|
351
352
|
) -> List[Union[bool, str]]:
|
352
353
|
"""
|
353
354
|
Add data to an existing Pipe.
|
354
|
-
See `meerschaum.Pipe.sync
|
355
|
+
See [`meerschaum.Pipe.sync`](https://docs.meerschaum.io/meerschaum.html#Pipe.sync).
|
355
356
|
"""
|
356
357
|
if data is None:
|
357
358
|
data = {}
|
358
|
-
|
359
|
-
if
|
359
|
+
pipe = get_pipe(connector_keys, metric_key, location_key)
|
360
|
+
if pipe.target in ('mrsm_users', 'mrsm_plugins', 'mrsm_pipes'):
|
360
361
|
raise fastapi.HTTPException(
|
361
362
|
status_code=409,
|
362
|
-
detail=f"Cannot sync data to protected table '{
|
363
|
+
detail=f"Cannot sync data to protected table '{pipe.target}'.",
|
363
364
|
)
|
364
365
|
|
365
|
-
if not
|
366
|
-
|
367
|
-
if not
|
366
|
+
if not pipe.columns and columns is not None:
|
367
|
+
pipe.columns = json.loads(columns)
|
368
|
+
if not pipe.columns and not is_pipe_registered(pipe, pipes(refresh=True)):
|
368
369
|
raise fastapi.HTTPException(
|
369
370
|
status_code=409,
|
370
371
|
detail="Pipe must be registered with index columns specified."
|
371
372
|
)
|
372
373
|
|
373
|
-
result = list(
|
374
|
+
result = list(pipe.sync(
|
374
375
|
data,
|
375
376
|
debug=debug,
|
376
377
|
check_existing=check_existing,
|
@@ -386,17 +387,20 @@ def get_pipe_data(
|
|
386
387
|
connector_keys: str,
|
387
388
|
metric_key: str,
|
388
389
|
location_key: str,
|
390
|
+
instance_keys: Optional[str] = None,
|
389
391
|
select_columns: Optional[str] = None,
|
390
392
|
omit_columns: Optional[str] = None,
|
391
393
|
begin: Union[str, int, None] = None,
|
392
394
|
end: Union[str, int, None] = None,
|
393
395
|
params: Optional[str] = None,
|
396
|
+
limit: int = MAX_RESPONSE_ROW_LIMIT,
|
394
397
|
curr_user = (
|
395
398
|
fastapi.Depends(manager) if not no_auth else None
|
396
399
|
),
|
397
400
|
) -> str:
|
398
401
|
"""
|
399
402
|
Get a pipe's data, applying any filtering.
|
403
|
+
See [`Pipe.get_data()`](https://docs.meerschaum.io/meerschaum.html#Pipe.get_data).
|
400
404
|
|
401
405
|
Note that `select_columns`, `omit_columns`, and `params` are JSON-encoded strings.
|
402
406
|
"""
|
@@ -404,6 +408,14 @@ def get_pipe_data(
|
|
404
408
|
begin = int(begin)
|
405
409
|
if is_int(end):
|
406
410
|
end = int(end)
|
411
|
+
if limit > MAX_RESPONSE_ROW_LIMIT:
|
412
|
+
raise fastapi.HTTPException(
|
413
|
+
status_code=413,
|
414
|
+
detail=(
|
415
|
+
f"Requested limit {limit} exceeds the maximum response size of "
|
416
|
+
f"{MAX_RESPONSE_ROW_LIMIT} rows."
|
417
|
+
)
|
418
|
+
)
|
407
419
|
|
408
420
|
_params = {}
|
409
421
|
if params == 'null':
|
@@ -447,8 +459,8 @@ def get_pipe_data(
|
|
447
459
|
detail="Omitted columns must be a JSON-encoded list.",
|
448
460
|
)
|
449
461
|
|
450
|
-
pipe = get_pipe(connector_keys, metric_key, location_key)
|
451
|
-
if not is_pipe_registered(pipe, pipes(refresh=True)):
|
462
|
+
pipe = get_pipe(connector_keys, metric_key, location_key, instance_keys)
|
463
|
+
if not is_pipe_registered(pipe, pipes(instance_keys, refresh=True)):
|
452
464
|
raise fastapi.HTTPException(
|
453
465
|
status_code=409,
|
454
466
|
detail="Pipe must be registered with the datetime column specified."
|
@@ -466,6 +478,7 @@ def get_pipe_data(
|
|
466
478
|
begin=begin,
|
467
479
|
end=end,
|
468
480
|
params=_params,
|
481
|
+
limit=min(limit, MAX_RESPONSE_ROW_LIMIT),
|
469
482
|
debug=debug,
|
470
483
|
)
|
471
484
|
if df is None:
|
@@ -474,12 +487,6 @@ def get_pipe_data(
|
|
474
487
|
detail="Could not fetch data with the given parameters.",
|
475
488
|
)
|
476
489
|
|
477
|
-
### NaN cannot be JSON-serialized.
|
478
|
-
df = df.fillna(pd.NA)
|
479
|
-
numeric_cols = get_numeric_cols(df)
|
480
|
-
for col in numeric_cols:
|
481
|
-
df[col] = df[col].apply(lambda x: f'{x:f}' if isinstance(x, Decimal) else x)
|
482
|
-
|
483
490
|
json_content = to_json(df)
|
484
491
|
return fastapi.Response(
|
485
492
|
json_content,
|
@@ -492,6 +499,7 @@ def get_pipe_csv(
|
|
492
499
|
connector_keys: str,
|
493
500
|
metric_key: str,
|
494
501
|
location_key: str,
|
502
|
+
instance_keys: Optional[str] = None,
|
495
503
|
begin: Union[str, int, None] = None,
|
496
504
|
end: Union[str, int, None] = None,
|
497
505
|
params: Optional[str] = None,
|
@@ -500,16 +508,18 @@ def get_pipe_csv(
|
|
500
508
|
),
|
501
509
|
) -> str:
|
502
510
|
"""
|
503
|
-
Get a
|
511
|
+
Get a pipe's data as a CSV file. Optionally set query boundaries.
|
504
512
|
"""
|
505
513
|
if begin is not None:
|
506
514
|
begin = (
|
507
|
-
int(begin)
|
515
|
+
int(begin)
|
516
|
+
if is_int(begin)
|
508
517
|
else dateutil_parser.parse(begin)
|
509
518
|
)
|
510
519
|
if end is not None:
|
511
520
|
end = (
|
512
|
-
int(end)
|
521
|
+
int(end)
|
522
|
+
if is_int(end)
|
513
523
|
else dateutil_parser.parse(end)
|
514
524
|
)
|
515
525
|
|
@@ -520,7 +530,7 @@ def get_pipe_csv(
|
|
520
530
|
import json
|
521
531
|
try:
|
522
532
|
_params = json.loads(params)
|
523
|
-
except Exception
|
533
|
+
except Exception:
|
524
534
|
_params = None
|
525
535
|
|
526
536
|
if not isinstance(_params, dict):
|
@@ -529,27 +539,33 @@ def get_pipe_csv(
|
|
529
539
|
detail="Params must be a valid JSON-encoded dictionary.",
|
530
540
|
)
|
531
541
|
|
532
|
-
|
533
|
-
if not is_pipe_registered(
|
542
|
+
pipe = get_pipe(connector_keys, metric_key, location_key, instance_keys)
|
543
|
+
if not is_pipe_registered(pipe, pipes(instance_keys, refresh=True)):
|
534
544
|
raise fastapi.HTTPException(
|
535
|
-
status_code
|
536
|
-
detail
|
545
|
+
status_code=409,
|
546
|
+
detail="Pipe must be registered with the datetime column specified."
|
537
547
|
)
|
538
548
|
|
539
|
-
dt_col =
|
549
|
+
dt_col = pipe.columns.get('datetime', None)
|
540
550
|
if dt_col:
|
541
551
|
if begin is None:
|
542
|
-
begin =
|
552
|
+
begin = pipe.get_sync_time(round_down=False, newest=False)
|
543
553
|
if end is None:
|
544
|
-
end =
|
554
|
+
end = pipe.get_sync_time(round_down=False, newest=True)
|
555
|
+
if end is not None:
|
556
|
+
end += (
|
557
|
+
1
|
558
|
+
if is_int(str(end))
|
559
|
+
else timedelta(minutes=1)
|
560
|
+
)
|
545
561
|
|
546
562
|
bounds_text = (
|
547
563
|
('-' + str(begin) + '-' + str(end))
|
548
564
|
if begin is not None and end is not None
|
549
565
|
else ''
|
550
566
|
)
|
551
|
-
filename =
|
552
|
-
df =
|
567
|
+
filename = pipe.target + bounds_text + '.csv'
|
568
|
+
df = pipe.get_data(begin=begin, end=end, params=_params, debug=debug)
|
553
569
|
stream = io.StringIO()
|
554
570
|
df.to_csv(stream, index=False)
|
555
571
|
response = StreamingResponse(iter([stream.getvalue()]), media_type='text/csv')
|
@@ -562,14 +578,15 @@ def get_pipe_id(
|
|
562
578
|
connector_keys: str,
|
563
579
|
metric_key: str,
|
564
580
|
location_key: str,
|
581
|
+
instance_keys: Optional[str] = None,
|
565
582
|
curr_user = (
|
566
583
|
fastapi.Depends(manager) if not no_auth else None
|
567
584
|
),
|
568
585
|
) -> Union[int, str]:
|
569
586
|
"""
|
570
|
-
Get a
|
587
|
+
Get a pipe's ID.
|
571
588
|
"""
|
572
|
-
pipe_id = get_pipe(connector_keys, metric_key, location_key).
|
589
|
+
pipe_id = get_pipe(connector_keys, metric_key, location_key, instance_keys).get_id(debug=debug)
|
573
590
|
if pipe_id is None:
|
574
591
|
raise fastapi.HTTPException(status_code=404, detail="Pipe is not registered.")
|
575
592
|
return pipe_id
|
@@ -577,18 +594,25 @@ def get_pipe_id(
|
|
577
594
|
|
578
595
|
@app.get(
|
579
596
|
pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/attributes',
|
580
|
-
tags
|
597
|
+
tags=['Pipes']
|
581
598
|
)
|
582
599
|
def get_pipe_attributes(
|
583
600
|
connector_keys: str,
|
584
601
|
metric_key: str,
|
585
602
|
location_key: str,
|
603
|
+
instance_keys: Optional[str] = None,
|
586
604
|
curr_user=(
|
587
605
|
fastapi.Depends(manager) if not no_auth else None
|
588
606
|
),
|
589
607
|
) -> Dict[str, Any]:
|
590
|
-
"""Get a
|
591
|
-
return get_pipe(
|
608
|
+
"""Get a pipe's attributes."""
|
609
|
+
return get_pipe(
|
610
|
+
connector_keys,
|
611
|
+
metric_key,
|
612
|
+
location_key,
|
613
|
+
instance_keys,
|
614
|
+
refresh=True,
|
615
|
+
).attributes
|
592
616
|
|
593
617
|
|
594
618
|
@app.get(pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/exists', tags=['Pipes'])
|
@@ -596,24 +620,28 @@ def get_pipe_exists(
|
|
596
620
|
connector_keys: str,
|
597
621
|
metric_key: str,
|
598
622
|
location_key: str,
|
623
|
+
instance_keys: Optional[str] = None,
|
599
624
|
curr_user = (
|
600
625
|
fastapi.Depends(manager) if not no_auth else None
|
601
626
|
),
|
602
627
|
) -> bool:
|
603
|
-
"""Determine whether a
|
604
|
-
return get_pipe(connector_keys, metric_key, location_key).exists()
|
628
|
+
"""Determine whether a pipe's target table exists."""
|
629
|
+
return get_pipe(connector_keys, metric_key, location_key, instance_keys).exists(debug=debug)
|
605
630
|
|
606
631
|
|
607
632
|
@app.post(endpoints['metadata'], tags=['Pipes'])
|
608
633
|
def create_metadata(
|
634
|
+
instance_keys: Optional[str] = None,
|
609
635
|
curr_user = (
|
610
636
|
fastapi.Depends(manager) if not no_auth else None
|
611
637
|
),
|
612
638
|
) -> bool:
|
613
|
-
"""Create
|
614
|
-
|
639
|
+
"""Create pipe instance metadata tables."""
|
640
|
+
conn = get_api_connector(instance_keys)
|
641
|
+
if conn.type not in ('sql', 'api'):
|
642
|
+
return False
|
615
643
|
try:
|
616
|
-
|
644
|
+
_ = get_tables(mrsm_instance=conn, debug=debug)
|
617
645
|
except Exception as e:
|
618
646
|
raise fastapi.HTTPException(status_code=500, detail=str(e))
|
619
647
|
return True
|
@@ -624,24 +652,46 @@ def get_pipe_rowcount(
|
|
624
652
|
connector_keys: str,
|
625
653
|
metric_key: str,
|
626
654
|
location_key: str,
|
655
|
+
instance_keys: Optional[str] = None,
|
627
656
|
begin: Union[str, int, None] = None,
|
628
657
|
end: Union[str, int, None] = None,
|
629
658
|
params: Optional[Dict[str, Any]] = None,
|
659
|
+
remote: bool = False,
|
630
660
|
curr_user = (
|
631
661
|
fastapi.Depends(manager) if not no_auth else None
|
632
662
|
),
|
633
663
|
) -> int:
|
634
664
|
"""
|
635
665
|
Return a pipe's rowcount.
|
666
|
+
See [`Pipe.get_rowcount()`](https://docs.meerschaum.io/meerschaum.html#Pipe.get_rowcount).
|
667
|
+
|
668
|
+
Parameters
|
669
|
+
----------
|
670
|
+
begin: Union[str, int, None], default None
|
671
|
+
If provided, only count rows newer than or equal to `begin`.
|
672
|
+
|
673
|
+
end: Union[str, int, None], defaut None
|
674
|
+
If provided, only count rows older than `end`.
|
675
|
+
|
676
|
+
params: Optional[Dict[str, Any]], default None
|
677
|
+
If provided, only count rows which match the provided `params` dictionary.
|
678
|
+
|
679
|
+
remote: bool, default False
|
680
|
+
If `True`, return the rowcount for the fetch definition instead of the target table.
|
681
|
+
|
682
|
+
Returns
|
683
|
+
-------
|
684
|
+
The rowcount for a pipe's target table or fetch definition (if applicable).
|
636
685
|
"""
|
637
686
|
if is_int(begin):
|
638
687
|
begin = int(begin)
|
639
688
|
if is_int(end):
|
640
689
|
end = int(end)
|
641
|
-
return get_pipe(connector_keys, metric_key, location_key).get_rowcount(
|
690
|
+
return get_pipe(connector_keys, metric_key, location_key, instance_keys).get_rowcount(
|
642
691
|
begin=begin,
|
643
692
|
end=end,
|
644
693
|
params=params,
|
694
|
+
remote=remote,
|
645
695
|
debug=debug,
|
646
696
|
)
|
647
697
|
|
@@ -654,22 +704,24 @@ def get_pipe_columns_types(
|
|
654
704
|
connector_keys: str,
|
655
705
|
metric_key: str,
|
656
706
|
location_key: str,
|
707
|
+
instance_keys: Optional[str] = None,
|
657
708
|
curr_user=(
|
658
709
|
fastapi.Depends(manager) if not no_auth else None
|
659
710
|
),
|
660
711
|
) -> Dict[str, str]:
|
661
712
|
"""
|
662
713
|
Return a dictionary of column names and types.
|
714
|
+
See [`Pipe.dtypes`](https://meerschaum.io/reference/pipes/parameters/#dtypes) for supported types.
|
663
715
|
|
664
|
-
```
|
716
|
+
```json
|
665
717
|
{
|
666
718
|
"dt": "datetime",
|
667
719
|
"id": "int",
|
668
|
-
"val": "float"
|
720
|
+
"val": "float"
|
669
721
|
}
|
670
722
|
```
|
671
723
|
"""
|
672
|
-
return get_pipe(connector_keys, metric_key, location_key).dtypes
|
724
|
+
return get_pipe(connector_keys, metric_key, location_key, instance_keys).dtypes
|
673
725
|
|
674
726
|
|
675
727
|
@app.get(
|
@@ -680,14 +732,50 @@ def get_pipe_columns_indices(
|
|
680
732
|
connector_keys: str,
|
681
733
|
metric_key: str,
|
682
734
|
location_key: str,
|
735
|
+
instance_keys: Optional[str] = None,
|
683
736
|
curr_user=(
|
684
737
|
fastapi.Depends(manager) if not no_auth else None
|
685
738
|
),
|
686
739
|
) -> Dict[str, List[Dict[str, str]]]:
|
687
740
|
"""
|
688
741
|
Return a dictionary of column names and related indices.
|
742
|
+
See [`Pipe.get_columns_indices()`](https://docs.meerschaum.io/meerschaum.html#Pipe.get_columns_indices).
|
743
|
+
|
744
|
+
```json
|
745
|
+
{
|
746
|
+
"datetime": [
|
747
|
+
{
|
748
|
+
"name": "plugin_stress_test_0_datetime_idx",
|
749
|
+
"type": "INDEX"
|
750
|
+
},
|
751
|
+
{
|
752
|
+
"name": "IX_plugin_stress_test_0_id_datetime",
|
753
|
+
"type": "INDEX"
|
754
|
+
},
|
755
|
+
{
|
756
|
+
"name": "UQ_plugin_stress_test_0_id_datetime",
|
757
|
+
"type": "INDEX"
|
758
|
+
}
|
759
|
+
],
|
760
|
+
"id": [
|
761
|
+
{
|
762
|
+
"name": "IX_plugin_stress_test_0_id",
|
763
|
+
"type": "INDEX"
|
764
|
+
},
|
765
|
+
{
|
766
|
+
"name": "UQ_plugin_stress_test_0_id_datetime",
|
767
|
+
"type": "INDEX"
|
768
|
+
}
|
769
|
+
]
|
770
|
+
}
|
771
|
+
```
|
689
772
|
"""
|
690
|
-
return get_pipe(
|
773
|
+
return get_pipe(
|
774
|
+
connector_keys,
|
775
|
+
metric_key,
|
776
|
+
location_key,
|
777
|
+
instance_keys,
|
778
|
+
).get_columns_indices(debug=debug)
|
691
779
|
|
692
780
|
|
693
781
|
@app.get(
|
@@ -698,11 +786,19 @@ def get_pipe_index_names(
|
|
698
786
|
connector_keys: str,
|
699
787
|
metric_key: str,
|
700
788
|
location_key: str,
|
789
|
+
instance_keys: Optional[str] = None,
|
701
790
|
curr_user=(
|
702
791
|
fastapi.Depends(manager) if not no_auth else None
|
703
792
|
),
|
704
793
|
) -> Dict[str, List[Dict[str, str]]]:
|
705
794
|
"""
|
706
795
|
Return a dictionary of index keys and index names.
|
796
|
+
|
797
|
+
See [`Pipe.get_indices()`](https://docs.meerschaum.io/meerschaum.html#Pipe.get_indices).
|
707
798
|
"""
|
708
|
-
return get_pipe(
|
799
|
+
return get_pipe(
|
800
|
+
connector_keys,
|
801
|
+
metric_key,
|
802
|
+
location_key,
|
803
|
+
instance_keys,
|
804
|
+
).get_indices(debug=debug)
|