meerschaum 2.7.10__py3-none-any.whl → 2.8.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- meerschaum/_internal/arguments/_parser.py +17 -5
- 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/config/_default.py +10 -0
- meerschaum/config/_version.py +1 -1
- meerschaum/config/static/__init__.py +4 -1
- 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.10.dist-info → meerschaum-2.8.0.dist-info}/METADATA +4 -4
- {meerschaum-2.7.10.dist-info → meerschaum-2.8.0.dist-info}/RECORD +57 -56
- {meerschaum-2.7.10.dist-info → meerschaum-2.8.0.dist-info}/LICENSE +0 -0
- {meerschaum-2.7.10.dist-info → meerschaum-2.8.0.dist-info}/NOTICE +0 -0
- {meerschaum-2.7.10.dist-info → meerschaum-2.8.0.dist-info}/WHEEL +0 -0
- {meerschaum-2.7.10.dist-info → meerschaum-2.8.0.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.7.10.dist-info → meerschaum-2.8.0.dist-info}/top_level.txt +0 -0
- {meerschaum-2.7.10.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)
|