meerschaum 2.3.5.dev0__py3-none-any.whl → 2.4.0.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. meerschaum/_internal/arguments/__init__.py +2 -1
  2. meerschaum/_internal/arguments/_parse_arguments.py +86 -7
  3. meerschaum/_internal/entry.py +29 -13
  4. meerschaum/actions/api.py +16 -16
  5. meerschaum/actions/bootstrap.py +36 -10
  6. meerschaum/actions/start.py +16 -15
  7. meerschaum/api/_events.py +11 -7
  8. meerschaum/api/dash/__init__.py +7 -6
  9. meerschaum/api/dash/callbacks/__init__.py +1 -0
  10. meerschaum/api/dash/callbacks/dashboard.py +7 -5
  11. meerschaum/api/dash/callbacks/pipes.py +42 -0
  12. meerschaum/api/dash/pages/__init__.py +1 -0
  13. meerschaum/api/dash/pages/pipes.py +16 -0
  14. meerschaum/api/dash/pipes.py +79 -47
  15. meerschaum/api/dash/users.py +19 -6
  16. meerschaum/api/routes/_actions.py +0 -98
  17. meerschaum/api/routes/_jobs.py +38 -18
  18. meerschaum/api/routes/_login.py +4 -4
  19. meerschaum/api/routes/_pipes.py +3 -3
  20. meerschaum/config/_default.py +9 -2
  21. meerschaum/config/_version.py +1 -1
  22. meerschaum/config/stack/__init__.py +59 -18
  23. meerschaum/config/static/__init__.py +2 -0
  24. meerschaum/connectors/Connector.py +19 -13
  25. meerschaum/connectors/__init__.py +9 -5
  26. meerschaum/connectors/api/_actions.py +22 -36
  27. meerschaum/connectors/api/_jobs.py +1 -0
  28. meerschaum/connectors/poll.py +30 -24
  29. meerschaum/connectors/sql/_pipes.py +126 -154
  30. meerschaum/connectors/sql/_plugins.py +45 -43
  31. meerschaum/connectors/sql/_users.py +46 -38
  32. meerschaum/connectors/valkey/ValkeyConnector.py +535 -0
  33. meerschaum/connectors/valkey/__init__.py +8 -0
  34. meerschaum/connectors/valkey/_fetch.py +75 -0
  35. meerschaum/connectors/valkey/_pipes.py +839 -0
  36. meerschaum/connectors/valkey/_plugins.py +265 -0
  37. meerschaum/connectors/valkey/_users.py +305 -0
  38. meerschaum/core/Pipe/__init__.py +2 -0
  39. meerschaum/core/Pipe/_attributes.py +1 -2
  40. meerschaum/core/Pipe/_drop.py +4 -4
  41. meerschaum/core/Pipe/_dtypes.py +14 -14
  42. meerschaum/core/Pipe/_edit.py +15 -14
  43. meerschaum/core/Pipe/_sync.py +134 -51
  44. meerschaum/core/User/_User.py +14 -12
  45. meerschaum/jobs/_Job.py +26 -8
  46. meerschaum/jobs/systemd.py +20 -8
  47. meerschaum/plugins/_Plugin.py +17 -13
  48. meerschaum/utils/_get_pipes.py +14 -20
  49. meerschaum/utils/dataframe.py +288 -101
  50. meerschaum/utils/dtypes/__init__.py +31 -6
  51. meerschaum/utils/dtypes/sql.py +4 -4
  52. meerschaum/utils/misc.py +3 -3
  53. meerschaum/utils/packages/_packages.py +1 -0
  54. meerschaum/utils/prompt.py +1 -1
  55. {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dev0.dist-info}/METADATA +3 -1
  56. {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dev0.dist-info}/RECORD +62 -54
  57. {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dev0.dist-info}/WHEEL +1 -1
  58. {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dev0.dist-info}/LICENSE +0 -0
  59. {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dev0.dist-info}/NOTICE +0 -0
  60. {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dev0.dist-info}/entry_points.txt +0 -0
  61. {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dev0.dist-info}/top_level.txt +0 -0
  62. {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dev0.dist-info}/zip-safe +0 -0
@@ -48,13 +48,14 @@ def pipe_from_ctx(ctx, trigger_property: str = 'n_clicks') -> Union[mrsm.Pipe, N
48
48
  return None
49
49
  return mrsm.Pipe(**meta)
50
50
 
51
+
51
52
  def keys_from_state(
52
- state: Dict[str, Any],
53
- with_params: bool = False
54
- ) -> Union[
55
- Tuple[List[str], List[str], List[str]],
56
- Tuple[List[str], List[str], List[str], str],
57
- ]:
53
+ state: Dict[str, Any],
54
+ with_params: bool = False
55
+ ) -> Union[
56
+ Tuple[List[str], List[str], List[str]],
57
+ Tuple[List[str], List[str], List[str], str],
58
+ ]:
58
59
  """
59
60
  Read the current state and return the selected keys lists.
60
61
  """
@@ -85,10 +86,11 @@ def keys_from_state(
85
86
  keys.append(params)
86
87
  return tuple(keys)
87
88
 
89
+
88
90
  def pipes_from_state(
89
- state: Dict[str, Any],
90
- **kw
91
- ):
91
+ state: Dict[str, Any],
92
+ **kw
93
+ ):
92
94
  _ck, _mk, _lk, _params = keys_from_state(state, with_params=True)
93
95
  try:
94
96
  _pipes = _get_pipes(
@@ -103,10 +105,10 @@ def pipes_from_state(
103
105
 
104
106
 
105
107
  def build_pipe_card(
106
- pipe: mrsm.Pipe,
107
- authenticated: bool = False,
108
- _build_children_num: int = 10,
109
- ) -> 'dbc.Card':
108
+ pipe: mrsm.Pipe,
109
+ authenticated: bool = False,
110
+ _build_children_num: int = 10,
111
+ ) -> 'dbc.Card':
110
112
  """
111
113
  Return a card for the given pipe.
112
114
 
@@ -128,11 +130,11 @@ def build_pipe_card(
128
130
  dbc.Col(
129
131
  (
130
132
  dbc.DropdownMenu(
131
- label = "Manage",
132
- children = [
133
+ label="Manage",
134
+ children=[
133
135
  dbc.DropdownMenuItem(
134
136
  'Open in Python',
135
- id = {
137
+ id={
136
138
  'type': 'manage-pipe-button',
137
139
  'index': meta_str,
138
140
  'action': 'python',
@@ -140,7 +142,7 @@ def build_pipe_card(
140
142
  ),
141
143
  dbc.DropdownMenuItem(
142
144
  'Delete',
143
- id = {
145
+ id={
144
146
  'type': 'manage-pipe-button',
145
147
  'index': meta_str,
146
148
  'action': 'delete',
@@ -148,7 +150,7 @@ def build_pipe_card(
148
150
  ),
149
151
  dbc.DropdownMenuItem(
150
152
  'Drop',
151
- id = {
153
+ id={
152
154
  'type': 'manage-pipe-button',
153
155
  'index': meta_str,
154
156
  'action': 'drop',
@@ -156,7 +158,7 @@ def build_pipe_card(
156
158
  ),
157
159
  dbc.DropdownMenuItem(
158
160
  'Clear',
159
- id = {
161
+ id={
160
162
  'type': 'manage-pipe-button',
161
163
  'index': meta_str,
162
164
  'action': 'clear',
@@ -164,7 +166,7 @@ def build_pipe_card(
164
166
  ),
165
167
  dbc.DropdownMenuItem(
166
168
  'Verify',
167
- id = {
169
+ id={
168
170
  'type': 'manage-pipe-button',
169
171
  'index': meta_str,
170
172
  'action': 'verify',
@@ -172,56 +174,86 @@ def build_pipe_card(
172
174
  ),
173
175
  dbc.DropdownMenuItem(
174
176
  'Sync',
175
- id = {
177
+ id={
176
178
  'type': 'manage-pipe-button',
177
179
  'index': meta_str,
178
180
  'action': 'sync',
179
181
  },
180
182
  ),
181
183
  ],
182
- direction = "up",
183
- menu_variant = "dark",
184
- size = 'sm',
185
- color = 'secondary',
184
+ direction="up",
185
+ menu_variant="dark",
186
+ size='sm',
187
+ color='secondary',
186
188
  )
187
189
  ) if authenticated else [],
188
- width = 2,
190
+ width=2,
189
191
  ),
190
192
  dbc.Col(width=6),
191
193
  dbc.Col(
192
194
  dbc.Button(
193
195
  'Download CSV',
194
- size = 'sm',
195
- color = 'link',
196
- style = {'float': 'right'},
197
- id = {'type': 'pipe-download-csv-button', 'index': meta_str},
196
+ size='sm',
197
+ color='link',
198
+ style={'float': 'right'},
199
+ id={'type': 'pipe-download-csv-button', 'index': meta_str},
198
200
  ),
199
- width = 4,
201
+ width=4,
200
202
  ),
201
203
  ],
202
- justify = 'start',
204
+ justify='start',
203
205
  )
204
206
  card_body_children = [
205
- html.H5(
206
- html.B(str(pipe)),
207
- className = 'card-title',
208
- style = {'font-family': ['monospace']}
209
- ),
210
207
  html.Div(
211
208
  dbc.Accordion(
212
209
  accordion_items_from_pipe(
213
210
  pipe,
214
- authenticated = authenticated,
215
- _build_children_num = _build_children_num,
211
+ authenticated=authenticated,
212
+ _build_children_num=_build_children_num,
216
213
  ),
217
- flush = True,
218
- start_collapsed = True,
219
- id = {'type': 'pipe-accordion', 'index': meta_str},
214
+ flush=True,
215
+ start_collapsed=True,
216
+ id={'type': 'pipe-accordion', 'index': meta_str},
220
217
  )
221
218
  )
222
219
 
223
220
  ]
221
+
222
+ pipe_url = (
223
+ f"/dash/pipes/{pipe.connector_keys}/{pipe.metric_key}/{pipe.location_key}"
224
+ )
225
+
226
+ card_header_children = dbc.Row(
227
+ [
228
+ dbc.Col(
229
+ html.H5(
230
+ html.B(str(pipe)),
231
+ className='card-title',
232
+ style={'font-family': ['monospace']}
233
+ ),
234
+ width=11,
235
+ ),
236
+ dbc.Col(
237
+ dbc.Button(
238
+ [
239
+ html.I(
240
+ className="bi bi-share",
241
+ ),
242
+ ],
243
+ # href=pipe_url,
244
+ style={'float': 'right'},
245
+ outline=True,
246
+ color='link',
247
+ id={'type': 'share-pipe-button', 'index': meta_str},
248
+ ),
249
+ width=1,
250
+ ),
251
+ ],
252
+ justify='start',
253
+ )
254
+
224
255
  return dbc.Card([
256
+ dbc.CardHeader(children=card_header_children),
225
257
  dbc.CardBody(children=card_body_children),
226
258
  dbc.CardFooter(children=footer_children),
227
259
  ])
@@ -270,11 +302,11 @@ def get_pipes_cards(*keys, session_data: Optional[Dict[str, Any]] = None):
270
302
 
271
303
 
272
304
  def accordion_items_from_pipe(
273
- pipe: mrsm.Pipe,
274
- active_items: Optional[List[str]] = None,
275
- authenticated: bool = False,
276
- _build_children_num: int = 10,
277
- ) -> 'List[dbc.AccordionItem]':
305
+ pipe: mrsm.Pipe,
306
+ active_items: Optional[List[str]] = None,
307
+ authenticated: bool = False,
308
+ _build_children_num: int = 10,
309
+ ) -> 'List[dbc.AccordionItem]':
278
310
  """
279
311
  Build the accordion items for a given pipe.
280
312
  """
@@ -18,6 +18,7 @@ from meerschaum.core import User
18
18
  dcc, html = import_dcc(check_update=CHECK_UPDATE), import_html(check_update=CHECK_UPDATE)
19
19
  dbc = attempt_import('dash_bootstrap_components', lazy=False, check_update=CHECK_UPDATE)
20
20
 
21
+
21
22
  def get_users_cards(state: WebState) -> Tuple[List[dbc.Card], List[SuccessTuple]]:
22
23
  """
23
24
  Return the cards and alerts for users.
@@ -52,21 +53,33 @@ def is_session_authenticated(session_id: str) -> bool:
52
53
  """
53
54
  if no_auth:
54
55
  return True
56
+ if session_id not in active_sessions:
57
+ return False
55
58
  if session_id in unauthenticated_sessions:
56
59
  return False
57
60
  if session_id in authenticated_sessions:
58
61
  return True
62
+
59
63
  permissions = get_config('system', 'api', 'permissions')
60
64
  allow_non_admin = permissions.get('actions', {}).get('non_admin', False)
61
- if allow_non_admin:
62
- return True
63
- conn = get_api_connector()
65
+
66
+ is_auth = True if allow_non_admin else session_is_admin(session_id)
64
67
  username = active_sessions.get(session_id, {}).get('username', None)
65
- user = User(username, instance=conn)
66
- user_type = conn.get_user_type(user, debug=debug)
67
- is_auth = user_type == 'admin'
68
+
68
69
  if is_auth:
69
70
  authenticated_sessions[session_id] = username
70
71
  else:
71
72
  unauthenticated_sessions[session_id] = username
73
+
72
74
  return is_auth
75
+
76
+
77
+ def session_is_admin(session_id: str) -> bool:
78
+ """
79
+ Check whether a session ID corresponds to an admin user.
80
+ """
81
+ conn = get_api_connector()
82
+ username = active_sessions.get(session_id, {}).get('username', None)
83
+ user = User(username, instance=conn)
84
+ user_type = conn.get_user_type(user, debug=debug)
85
+ return user_type == 'admin'
@@ -67,104 +67,6 @@ def get_actions(
67
67
  return list(actions)
68
68
 
69
69
 
70
- async def notify_client(client, content: str):
71
- """
72
- Send a line of text to a client.
73
- """
74
- try:
75
- await client.send_text(content)
76
- except (RuntimeError, WebSocketDisconnect, Exception):
77
- raise StopMonitoringLogs
78
-
79
- _temp_jobs = {}
80
- @app.websocket(actions_endpoint + '/ws')
81
- async def do_action_websocket(websocket: WebSocket):
82
- """
83
- Execute an action and stream the output to the client.
84
- """
85
- await websocket.accept()
86
-
87
- stop_event = asyncio.Event()
88
- job_name = '.' + generate_password(12)
89
- job = None
90
-
91
- async def monitor_logs(job):
92
- success, msg = job.start()
93
- await job.monitor_logs_async(
94
- partial(notify_client, websocket),
95
- stop_event=stop_event,
96
- stop_on_exit=True,
97
- )
98
-
99
- try:
100
- token = await websocket.receive_text()
101
- user = await manager.get_current_user(token) if not no_auth else None
102
- if user is None and not no_auth:
103
- stop_event.set()
104
- if job is not None:
105
- job.delete()
106
- _ = _temp_jobs.pop(job_name, None)
107
- raise fastapi.HTTPException(
108
- status_code=401,
109
- detail="Invalid credentials.",
110
- )
111
-
112
- auth_success, auth_msg = (
113
- is_user_allowed_to_execute(user)
114
- if not no_auth
115
- else (True, "Success")
116
- )
117
- auth_payload = {
118
- 'is_authenticated': auth_success,
119
- 'timestamp': datetime.now(timezone.utc).isoformat(),
120
- }
121
- await websocket.send_json(auth_payload)
122
- if not auth_success:
123
- stop_event.set()
124
- job.stop()
125
- await websocket.close()
126
-
127
- sysargs = clean_sysargs(await websocket.receive_json())
128
- job = Job(
129
- job_name,
130
- sysargs,
131
- executor_keys='local',
132
- _properties={
133
- 'logs': {
134
- 'write_timestamps': False,
135
- },
136
- },
137
- )
138
- _temp_jobs[job_name] = job
139
- monitor_task = asyncio.create_task(monitor_logs(job))
140
-
141
- ### NOTE: Await incoming text to trigger `WebSocketDisconnect`.
142
- while True:
143
- await websocket.receive_text()
144
-
145
- except fastapi.HTTPException:
146
- await websocket.send_text("Invalid credentials.")
147
- await websocket.close()
148
- except (WebSocketDisconnect, asyncio.CancelledError):
149
- stop_event.set()
150
- job.stop()
151
- except Exception:
152
- stop_event.set()
153
- job.stop()
154
- warn(f"Error in logs websocket:\n{traceback.format_exc()}")
155
- finally:
156
- stop_event.set()
157
- job.stop()
158
- monitor_task.cancel()
159
- if job is not None:
160
- job.delete()
161
- _ = _temp_jobs.pop(job_name, None)
162
- try:
163
- await websocket.close()
164
- except RuntimeError:
165
- pass
166
-
167
-
168
70
  @app.post(actions_endpoint + "/{action}", tags=['Actions'])
169
71
  def do_action_legacy(
170
72
  action: str,
@@ -40,7 +40,15 @@ from meerschaum.config.static import STATIC_CONFIG
40
40
 
41
41
  JOBS_STDIN_MESSAGE: str = STATIC_CONFIG['api']['jobs']['stdin_message']
42
42
  JOBS_STOP_MESSAGE: str = STATIC_CONFIG['api']['jobs']['stop_message']
43
- EXECUTOR_KEYS: str = get_executor_keys_from_context()
43
+ EXECUTOR_KEYS: str = 'local'
44
+
45
+
46
+ def _get_job(name: str):
47
+ systemd_job = Job(name, executor_keys='systemd')
48
+ if systemd_job.exists():
49
+ return systemd_job
50
+
51
+ return Job(name, executor_keys=EXECUTOR_KEYS)
44
52
 
45
53
 
46
54
  @app.get(endpoints['jobs'], tags=['Jobs'])
@@ -52,7 +60,7 @@ def get_jobs(
52
60
  """
53
61
  Return metadata about the current jobs.
54
62
  """
55
- jobs = _get_jobs(executor_keys=EXECUTOR_KEYS, combine_local_and_systemd=False)
63
+ jobs = _get_jobs(executor_keys=EXECUTOR_KEYS, combine_local_and_systemd=True)
56
64
  return {
57
65
  name: {
58
66
  'sysargs': job.sysargs,
@@ -83,7 +91,7 @@ def get_job(
83
91
  """
84
92
  Return metadata for a single job.
85
93
  """
86
- job = Job(name, executor_keys=EXECUTOR_KEYS)
94
+ job = _get_job(name)
87
95
  if not job.exists():
88
96
  raise fastapi.HTTPException(
89
97
  status_code=404,
@@ -163,7 +171,7 @@ def delete_job(
163
171
  """
164
172
  Delete a job.
165
173
  """
166
- job = Job(name, executor_keys=EXECUTOR_KEYS)
174
+ job = _get_job(name)
167
175
  return job.delete()
168
176
 
169
177
 
@@ -177,7 +185,7 @@ def get_job_exists(
177
185
  """
178
186
  Return whether a job exists.
179
187
  """
180
- job = Job(name, executor_keys=EXECUTOR_KEYS)
188
+ job = _get_job(name)
181
189
  return job.exists()
182
190
 
183
191
 
@@ -192,7 +200,7 @@ def get_logs(
192
200
  Return a job's log text.
193
201
  To stream log text, connect to the WebSocket endpoint `/logs/{name}/ws`.
194
202
  """
195
- job = Job(name, executor_keys=EXECUTOR_KEYS)
203
+ job = _get_job(name)
196
204
  if not job.exists():
197
205
  raise fastapi.HTTPException(
198
206
  status_code=404,
@@ -212,7 +220,7 @@ def start_job(
212
220
  """
213
221
  Start a job if stopped.
214
222
  """
215
- job = Job(name, executor_keys=EXECUTOR_KEYS)
223
+ job = _get_job(name)
216
224
  if not job.exists():
217
225
  raise fastapi.HTTPException(
218
226
  status_code=404,
@@ -231,7 +239,7 @@ def stop_job(
231
239
  """
232
240
  Stop a job if running.
233
241
  """
234
- job = Job(name, executor_keys=EXECUTOR_KEYS)
242
+ job = _get_job(name)
235
243
  if not job.exists():
236
244
  raise fastapi.HTTPException(
237
245
  status_code=404,
@@ -250,7 +258,7 @@ def pause_job(
250
258
  """
251
259
  Pause a job if running.
252
260
  """
253
- job = Job(name, executor_keys=EXECUTOR_KEYS)
261
+ job = _get_job(name)
254
262
  if not job.exists():
255
263
  raise fastapi.HTTPException(
256
264
  status_code=404,
@@ -269,7 +277,7 @@ def get_stop_time(
269
277
  """
270
278
  Get the timestamp when the job was manually stopped.
271
279
  """
272
- job = Job(name, executor_keys=EXECUTOR_KEYS)
280
+ job = _get_job(name)
273
281
  return job.stop_time
274
282
 
275
283
 
@@ -283,12 +291,13 @@ def get_is_blocking_on_stdin(
283
291
  """
284
292
  Return whether a job is blocking on stdin.
285
293
  """
286
- job = Job(name, executor_keys=EXECUTOR_KEYS)
294
+ job = _get_job(name)
287
295
  return job.is_blocking_on_stdin()
288
296
 
289
297
 
290
298
  _job_clients = defaultdict(lambda: [])
291
299
  _job_stop_events = defaultdict(lambda: asyncio.Event())
300
+ _job_queues = defaultdict(lambda: asyncio.Queue())
292
301
  async def notify_clients(name: str, websocket: WebSocket, content: str):
293
302
  """
294
303
  Write the given content to all connected clients.
@@ -315,12 +324,16 @@ async def get_input_from_clients(name: str, websocket: WebSocket) -> str:
315
324
  async def _read_client(client):
316
325
  try:
317
326
  await client.send_text(JOBS_STDIN_MESSAGE)
318
- data = await client.receive_text()
327
+ data = await _job_queues[name].get()
319
328
  except WebSocketDisconnect:
320
329
  if client in _job_clients[name]:
321
330
  _job_clients[name].remove(client)
331
+ if not _job_clients[name]:
332
+ _job_stop_events[name].set()
322
333
  except Exception:
323
334
  pass
335
+ finally:
336
+ _job_queues[name].task_done()
324
337
  return data
325
338
 
326
339
  read_tasks = [
@@ -357,10 +370,12 @@ async def logs_websocket(name: str, websocket: WebSocket):
357
370
  Stream logs from a job over a websocket.
358
371
  """
359
372
  await websocket.accept()
360
- job = Job(name, executor_keys=EXECUTOR_KEYS)
373
+ job = _get_job(name)
361
374
  _job_clients[name].append(websocket)
362
375
 
376
+ _task = None
363
377
  async def monitor_logs():
378
+ nonlocal _task
364
379
  try:
365
380
  callback_function = partial(
366
381
  notify_clients,
@@ -377,16 +392,17 @@ async def logs_websocket(name: str, websocket: WebSocket):
377
392
  name,
378
393
  websocket,
379
394
  )
380
- await job.monitor_logs_async(
395
+ _task = asyncio.create_task(job.monitor_logs_async(
381
396
  callback_function=callback_function,
382
397
  input_callback_function=input_callback_function,
383
398
  stop_callback_function=stop_callback_function,
384
399
  stop_event=_job_stop_events[name],
385
400
  stop_on_exit=True,
386
401
  accept_input=True,
387
- )
402
+ ))
388
403
  except Exception:
389
404
  warn(traceback.format_exc())
405
+ _task.cancel()
390
406
 
391
407
  try:
392
408
  token = await websocket.receive_text()
@@ -397,13 +413,17 @@ async def logs_websocket(name: str, websocket: WebSocket):
397
413
  detail="Invalid credentials.",
398
414
  )
399
415
  monitor_task = asyncio.create_task(monitor_logs())
400
- await monitor_task
416
+ while True:
417
+ text = await websocket.receive_text()
418
+ await _job_queues[name].put(text)
419
+
401
420
  except fastapi.HTTPException:
402
421
  await websocket.send_text("Invalid credentials.")
403
422
  await websocket.close()
404
423
  except WebSocketDisconnect:
405
- _job_stop_events[name].set()
406
- monitor_task.cancel()
424
+ if not _job_clients[name]:
425
+ _job_stop_events[name].set()
426
+ monitor_task.cancel()
407
427
  except asyncio.CancelledError:
408
428
  pass
409
429
  except Exception:
@@ -23,8 +23,8 @@ from meerschaum.api._oauth2 import CustomOAuth2PasswordRequestForm
23
23
 
24
24
  @manager.user_loader()
25
25
  def load_user(
26
- username: str
27
- ) -> User:
26
+ username: str
27
+ ) -> User:
28
28
  """
29
29
  Create the `meerschaum.core.User` object from the username.
30
30
  """
@@ -33,8 +33,8 @@ def load_user(
33
33
 
34
34
  @app.post(endpoints['login'], tags=['Users'])
35
35
  def login(
36
- data: CustomOAuth2PasswordRequestForm = fastapi.Depends()
37
- ) -> Dict[str, Any]:
36
+ data: CustomOAuth2PasswordRequestForm = fastapi.Depends()
37
+ ) -> Dict[str, Any]:
38
38
  """
39
39
  Login and set the session token.
40
40
  """
@@ -474,9 +474,9 @@ def get_pipe_data(
474
474
  df[col] = df[col].apply(lambda x: f'{x:f}' if isinstance(x, Decimal) else x)
475
475
 
476
476
  json_content = df.to_json(
477
- date_format = 'iso',
478
- orient = 'records',
479
- date_unit = 'us',
477
+ date_format='iso',
478
+ orient='records',
479
+ date_unit='us',
480
480
  )
481
481
 
482
482
  return fastapi.Response(
@@ -16,7 +16,6 @@ default_meerschaum_config = {
16
16
  'api_instance': 'MRSM{meerschaum:instance}',
17
17
  'web_instance': 'MRSM{meerschaum:instance}',
18
18
  'default_repository': 'api:mrsm',
19
- # 'default_executor': 'local',
20
19
  'connectors': {
21
20
  'sql': {
22
21
  'default': {},
@@ -53,6 +52,14 @@ default_meerschaum_config = {
53
52
  'protocol': 'https',
54
53
  },
55
54
  },
55
+ 'valkey': {
56
+ 'main': {
57
+ 'host': 'localhost',
58
+ 'username': 'default',
59
+ 'password': 'mrsm',
60
+ 'port': 6379,
61
+ },
62
+ },
56
63
  },
57
64
  }
58
65
  default_system_config = {
@@ -75,7 +82,7 @@ default_system_config = {
75
82
  },
76
83
  },
77
84
 
78
- 'api' : {
85
+ 'api': {
79
86
  },
80
87
  },
81
88
  ### not to be confused with system_config['connectors']['api'], this is the configuration
@@ -2,4 +2,4 @@
2
2
  Specify the Meerschaum release version.
3
3
  """
4
4
 
5
- __version__ = "2.3.5.dev0"
5
+ __version__ = "2.4.0.dev0"