meerschaum 2.3.5.dev0__py3-none-any.whl → 2.4.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.
Files changed (99) hide show
  1. meerschaum/_internal/arguments/__init__.py +2 -1
  2. meerschaum/_internal/arguments/_parse_arguments.py +88 -12
  3. meerschaum/_internal/docs/index.py +3 -2
  4. meerschaum/_internal/entry.py +42 -20
  5. meerschaum/_internal/shell/Shell.py +38 -44
  6. meerschaum/_internal/term/TermPageHandler.py +2 -3
  7. meerschaum/_internal/term/__init__.py +13 -11
  8. meerschaum/actions/api.py +26 -23
  9. meerschaum/actions/bootstrap.py +38 -11
  10. meerschaum/actions/copy.py +3 -3
  11. meerschaum/actions/delete.py +4 -1
  12. meerschaum/actions/register.py +1 -3
  13. meerschaum/actions/stack.py +24 -19
  14. meerschaum/actions/start.py +41 -41
  15. meerschaum/actions/sync.py +53 -52
  16. meerschaum/api/__init__.py +48 -14
  17. meerschaum/api/_events.py +26 -17
  18. meerschaum/api/_oauth2.py +2 -2
  19. meerschaum/api/_websockets.py +5 -4
  20. meerschaum/api/dash/__init__.py +7 -16
  21. meerschaum/api/dash/callbacks/__init__.py +1 -0
  22. meerschaum/api/dash/callbacks/dashboard.py +52 -58
  23. meerschaum/api/dash/callbacks/jobs.py +15 -16
  24. meerschaum/api/dash/callbacks/login.py +16 -10
  25. meerschaum/api/dash/callbacks/pipes.py +41 -0
  26. meerschaum/api/dash/callbacks/plugins.py +1 -1
  27. meerschaum/api/dash/callbacks/register.py +15 -11
  28. meerschaum/api/dash/components.py +54 -59
  29. meerschaum/api/dash/jobs.py +5 -9
  30. meerschaum/api/dash/pages/__init__.py +1 -0
  31. meerschaum/api/dash/pages/pipes.py +19 -0
  32. meerschaum/api/dash/pipes.py +86 -58
  33. meerschaum/api/dash/plugins.py +6 -4
  34. meerschaum/api/dash/sessions.py +176 -0
  35. meerschaum/api/dash/users.py +3 -41
  36. meerschaum/api/dash/webterm.py +12 -17
  37. meerschaum/api/resources/static/js/terminado.js +1 -1
  38. meerschaum/api/routes/_actions.py +4 -118
  39. meerschaum/api/routes/_jobs.py +45 -24
  40. meerschaum/api/routes/_login.py +4 -4
  41. meerschaum/api/routes/_pipes.py +3 -3
  42. meerschaum/api/routes/_webterm.py +5 -6
  43. meerschaum/config/_default.py +15 -3
  44. meerschaum/config/_version.py +1 -1
  45. meerschaum/config/stack/__init__.py +64 -21
  46. meerschaum/config/static/__init__.py +6 -0
  47. meerschaum/connectors/{Connector.py → _Connector.py} +19 -13
  48. meerschaum/connectors/__init__.py +24 -14
  49. meerschaum/connectors/api/{APIConnector.py → _APIConnector.py} +3 -1
  50. meerschaum/connectors/api/__init__.py +2 -1
  51. meerschaum/connectors/api/_actions.py +22 -36
  52. meerschaum/connectors/api/_jobs.py +1 -0
  53. meerschaum/connectors/parse.py +18 -16
  54. meerschaum/connectors/poll.py +30 -24
  55. meerschaum/connectors/sql/__init__.py +3 -1
  56. meerschaum/connectors/sql/_pipes.py +172 -197
  57. meerschaum/connectors/sql/_plugins.py +45 -43
  58. meerschaum/connectors/sql/_users.py +46 -38
  59. meerschaum/connectors/valkey/_ValkeyConnector.py +535 -0
  60. meerschaum/connectors/valkey/__init__.py +10 -0
  61. meerschaum/connectors/valkey/_fetch.py +75 -0
  62. meerschaum/connectors/valkey/_pipes.py +844 -0
  63. meerschaum/connectors/valkey/_plugins.py +265 -0
  64. meerschaum/connectors/valkey/_users.py +305 -0
  65. meerschaum/core/Pipe/__init__.py +3 -0
  66. meerschaum/core/Pipe/_attributes.py +1 -2
  67. meerschaum/core/Pipe/_clear.py +16 -13
  68. meerschaum/core/Pipe/_copy.py +106 -0
  69. meerschaum/core/Pipe/_data.py +165 -101
  70. meerschaum/core/Pipe/_drop.py +4 -4
  71. meerschaum/core/Pipe/_dtypes.py +14 -14
  72. meerschaum/core/Pipe/_edit.py +15 -14
  73. meerschaum/core/Pipe/_sync.py +134 -53
  74. meerschaum/core/Pipe/_verify.py +11 -11
  75. meerschaum/core/User/_User.py +14 -12
  76. meerschaum/jobs/_Job.py +27 -14
  77. meerschaum/jobs/__init__.py +7 -2
  78. meerschaum/jobs/systemd.py +20 -8
  79. meerschaum/plugins/_Plugin.py +17 -13
  80. meerschaum/utils/_get_pipes.py +14 -20
  81. meerschaum/utils/dataframe.py +291 -101
  82. meerschaum/utils/dtypes/__init__.py +31 -6
  83. meerschaum/utils/dtypes/sql.py +4 -4
  84. meerschaum/utils/formatting/_shell.py +5 -6
  85. meerschaum/utils/misc.py +3 -3
  86. meerschaum/utils/packages/__init__.py +14 -9
  87. meerschaum/utils/packages/_packages.py +2 -0
  88. meerschaum/utils/prompt.py +1 -1
  89. meerschaum/utils/schedule.py +1 -0
  90. {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dist-info}/METADATA +7 -1
  91. {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dist-info}/RECORD +98 -89
  92. {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dist-info}/WHEEL +1 -1
  93. meerschaum/api/dash/actions.py +0 -255
  94. /meerschaum/connectors/sql/{SQLConnector.py → _SQLConnector.py} +0 -0
  95. {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dist-info}/LICENSE +0 -0
  96. {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dist-info}/NOTICE +0 -0
  97. {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dist-info}/entry_points.txt +0 -0
  98. {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dist-info}/top_level.txt +0 -0
  99. {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dist-info}/zip-safe +0 -0
@@ -7,28 +7,13 @@ Execute Meerschaum Actions via the API
7
7
  """
8
8
 
9
9
  from __future__ import annotations
10
- import asyncio
11
- import traceback
12
- import shlex
13
- from functools import partial
14
- from datetime import datetime, timezone
15
-
16
- from fastapi import WebSocket, WebSocketDisconnect
17
- from websockets.exceptions import ConnectionClosedError
18
-
19
- from meerschaum.utils.misc import generate_password
20
- from meerschaum.jobs import Job
21
- from meerschaum.utils.warnings import warn
22
- from meerschaum.utils.typing import SuccessTuple, Union, List, Dict, Any
10
+
11
+ from meerschaum.utils.typing import SuccessTuple, List, Dict, Any
23
12
  from meerschaum.api import (
24
13
  fastapi, app, endpoints, get_api_connector, debug, manager, private, no_auth
25
14
  )
26
15
  from meerschaum.actions import actions
27
- import meerschaum.core
28
16
  from meerschaum.config import get_config
29
- from meerschaum._internal.arguments._parse_arguments import parse_dict_to_sysargs, parse_arguments
30
- from meerschaum.api.routes._jobs import clean_sysargs
31
- from meerschaum.jobs._Job import StopMonitoringLogs
32
17
 
33
18
  actions_endpoint = endpoints['actions']
34
19
 
@@ -67,104 +52,6 @@ def get_actions(
67
52
  return list(actions)
68
53
 
69
54
 
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
55
  @app.post(actions_endpoint + "/{action}", tags=['Actions'])
169
56
  def do_action_legacy(
170
57
  action: str,
@@ -180,7 +67,7 @@ def do_action_legacy(
180
67
  ----------
181
68
  action: str
182
69
  The action to perform.
183
-
70
+
184
71
  keywords: Dict[str, Any]
185
72
  The keywords dictionary to pass to the action.
186
73
 
@@ -189,7 +76,6 @@ def do_action_legacy(
189
76
  A `SuccessTuple`.
190
77
  """
191
78
  if curr_user is not None and curr_user.type != 'admin':
192
- from meerschaum.config import get_config
193
79
  allow_non_admin = get_config(
194
80
  'system', 'api', 'permissions', 'actions', 'non_admin', patch=True
195
81
  )
@@ -201,7 +87,7 @@ def do_action_legacy(
201
87
  + "and search for 'permissions'. "
202
88
  + "\nUnder the keys 'api:permissions:actions', "
203
89
  + "you can allow non-admin users to perform actions."
204
- )
90
+ )
205
91
 
206
92
  if action not in actions:
207
93
  return False, f"Invalid action '{action}'."
@@ -9,7 +9,6 @@ Manage jobs via the Meerschaum API.
9
9
  from __future__ import annotations
10
10
 
11
11
  import os
12
- import select
13
12
  import asyncio
14
13
  import traceback
15
14
  from datetime import datetime
@@ -18,12 +17,10 @@ from functools import partial
18
17
 
19
18
  from fastapi import WebSocket, WebSocketDisconnect
20
19
 
21
- from meerschaum.utils.typing import Dict, Any, SuccessTuple, List, Optional, Union
20
+ from meerschaum.utils.typing import Dict, Any, SuccessTuple, List, Union
22
21
  from meerschaum.jobs import (
23
22
  get_jobs as _get_jobs,
24
23
  Job,
25
- StopMonitoringLogs,
26
- get_executor_keys_from_context,
27
24
  )
28
25
  from meerschaum.utils.warnings import warn
29
26
 
@@ -32,15 +29,23 @@ from meerschaum.api import (
32
29
  app,
33
30
  endpoints,
34
31
  manager,
35
- debug,
36
32
  no_auth,
37
- private,
38
33
  )
39
34
  from meerschaum.config.static import STATIC_CONFIG
40
35
 
41
36
  JOBS_STDIN_MESSAGE: str = STATIC_CONFIG['api']['jobs']['stdin_message']
42
37
  JOBS_STOP_MESSAGE: str = STATIC_CONFIG['api']['jobs']['stop_message']
43
- EXECUTOR_KEYS: str = get_executor_keys_from_context()
38
+ NONINTERACTIVE_ENV: str = STATIC_CONFIG['environment']['noninteractive']
39
+ EXECUTOR_KEYS: str = 'local'
40
+
41
+
42
+ def _get_job(name: str):
43
+ systemd_job = Job(name, executor_keys='systemd')
44
+ if systemd_job.exists():
45
+ return systemd_job
46
+
47
+ job = Job(name, executor_keys=EXECUTOR_KEYS)
48
+ return job
44
49
 
45
50
 
46
51
  @app.get(endpoints['jobs'], tags=['Jobs'])
@@ -52,7 +57,7 @@ def get_jobs(
52
57
  """
53
58
  Return metadata about the current jobs.
54
59
  """
55
- jobs = _get_jobs(executor_keys=EXECUTOR_KEYS, combine_local_and_systemd=False)
60
+ jobs = _get_jobs(executor_keys=EXECUTOR_KEYS, combine_local_and_systemd=True)
56
61
  return {
57
62
  name: {
58
63
  'sysargs': job.sysargs,
@@ -83,7 +88,7 @@ def get_job(
83
88
  """
84
89
  Return metadata for a single job.
85
90
  """
86
- job = Job(name, executor_keys=EXECUTOR_KEYS)
91
+ job = _get_job(name)
87
92
  if not job.exists():
88
93
  raise fastapi.HTTPException(
89
94
  status_code=404,
@@ -142,6 +147,10 @@ def create_job(
142
147
  name,
143
148
  clean_sysargs(sysargs),
144
149
  executor_keys=EXECUTOR_KEYS,
150
+ env={
151
+ NONINTERACTIVE_ENV: '1',
152
+ **dict(os.environ)
153
+ },
145
154
  _properties=properties,
146
155
  )
147
156
  if job.exists():
@@ -163,7 +172,7 @@ def delete_job(
163
172
  """
164
173
  Delete a job.
165
174
  """
166
- job = Job(name, executor_keys=EXECUTOR_KEYS)
175
+ job = _get_job(name)
167
176
  return job.delete()
168
177
 
169
178
 
@@ -177,7 +186,7 @@ def get_job_exists(
177
186
  """
178
187
  Return whether a job exists.
179
188
  """
180
- job = Job(name, executor_keys=EXECUTOR_KEYS)
189
+ job = _get_job(name)
181
190
  return job.exists()
182
191
 
183
192
 
@@ -192,7 +201,7 @@ def get_logs(
192
201
  Return a job's log text.
193
202
  To stream log text, connect to the WebSocket endpoint `/logs/{name}/ws`.
194
203
  """
195
- job = Job(name, executor_keys=EXECUTOR_KEYS)
204
+ job = _get_job(name)
196
205
  if not job.exists():
197
206
  raise fastapi.HTTPException(
198
207
  status_code=404,
@@ -212,7 +221,7 @@ def start_job(
212
221
  """
213
222
  Start a job if stopped.
214
223
  """
215
- job = Job(name, executor_keys=EXECUTOR_KEYS)
224
+ job = _get_job(name)
216
225
  if not job.exists():
217
226
  raise fastapi.HTTPException(
218
227
  status_code=404,
@@ -231,7 +240,7 @@ def stop_job(
231
240
  """
232
241
  Stop a job if running.
233
242
  """
234
- job = Job(name, executor_keys=EXECUTOR_KEYS)
243
+ job = _get_job(name)
235
244
  if not job.exists():
236
245
  raise fastapi.HTTPException(
237
246
  status_code=404,
@@ -250,7 +259,7 @@ def pause_job(
250
259
  """
251
260
  Pause a job if running.
252
261
  """
253
- job = Job(name, executor_keys=EXECUTOR_KEYS)
262
+ job = _get_job(name)
254
263
  if not job.exists():
255
264
  raise fastapi.HTTPException(
256
265
  status_code=404,
@@ -269,7 +278,7 @@ def get_stop_time(
269
278
  """
270
279
  Get the timestamp when the job was manually stopped.
271
280
  """
272
- job = Job(name, executor_keys=EXECUTOR_KEYS)
281
+ job = _get_job(name)
273
282
  return job.stop_time
274
283
 
275
284
 
@@ -283,12 +292,13 @@ def get_is_blocking_on_stdin(
283
292
  """
284
293
  Return whether a job is blocking on stdin.
285
294
  """
286
- job = Job(name, executor_keys=EXECUTOR_KEYS)
295
+ job = _get_job(name)
287
296
  return job.is_blocking_on_stdin()
288
297
 
289
298
 
290
299
  _job_clients = defaultdict(lambda: [])
291
300
  _job_stop_events = defaultdict(lambda: asyncio.Event())
301
+ _job_queues = defaultdict(lambda: asyncio.Queue())
292
302
  async def notify_clients(name: str, websocket: WebSocket, content: str):
293
303
  """
294
304
  Write the given content to all connected clients.
@@ -315,12 +325,16 @@ async def get_input_from_clients(name: str, websocket: WebSocket) -> str:
315
325
  async def _read_client(client):
316
326
  try:
317
327
  await client.send_text(JOBS_STDIN_MESSAGE)
318
- data = await client.receive_text()
328
+ data = await _job_queues[name].get()
319
329
  except WebSocketDisconnect:
320
330
  if client in _job_clients[name]:
321
331
  _job_clients[name].remove(client)
332
+ if not _job_clients[name]:
333
+ _job_stop_events[name].set()
322
334
  except Exception:
323
335
  pass
336
+ finally:
337
+ _job_queues[name].task_done()
324
338
  return data
325
339
 
326
340
  read_tasks = [
@@ -357,10 +371,12 @@ async def logs_websocket(name: str, websocket: WebSocket):
357
371
  Stream logs from a job over a websocket.
358
372
  """
359
373
  await websocket.accept()
360
- job = Job(name, executor_keys=EXECUTOR_KEYS)
374
+ job = _get_job(name)
361
375
  _job_clients[name].append(websocket)
362
376
 
377
+ _task = None
363
378
  async def monitor_logs():
379
+ nonlocal _task
364
380
  try:
365
381
  callback_function = partial(
366
382
  notify_clients,
@@ -377,16 +393,17 @@ async def logs_websocket(name: str, websocket: WebSocket):
377
393
  name,
378
394
  websocket,
379
395
  )
380
- await job.monitor_logs_async(
396
+ _task = asyncio.create_task(job.monitor_logs_async(
381
397
  callback_function=callback_function,
382
398
  input_callback_function=input_callback_function,
383
399
  stop_callback_function=stop_callback_function,
384
400
  stop_event=_job_stop_events[name],
385
401
  stop_on_exit=True,
386
402
  accept_input=True,
387
- )
403
+ ))
388
404
  except Exception:
389
405
  warn(traceback.format_exc())
406
+ _task.cancel()
390
407
 
391
408
  try:
392
409
  token = await websocket.receive_text()
@@ -397,13 +414,17 @@ async def logs_websocket(name: str, websocket: WebSocket):
397
414
  detail="Invalid credentials.",
398
415
  )
399
416
  monitor_task = asyncio.create_task(monitor_logs())
400
- await monitor_task
417
+ while True:
418
+ text = await websocket.receive_text()
419
+ await _job_queues[name].put(text)
420
+
401
421
  except fastapi.HTTPException:
402
422
  await websocket.send_text("Invalid credentials.")
403
423
  await websocket.close()
404
424
  except WebSocketDisconnect:
405
- _job_stop_events[name].set()
406
- monitor_task.cancel()
425
+ if not _job_clients[name]:
426
+ _job_stop_events[name].set()
427
+ monitor_task.cancel()
407
428
  except asyncio.CancelledError:
408
429
  pass
409
430
  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(
@@ -8,10 +8,9 @@ Routes to the Webterm proxy.
8
8
 
9
9
  import asyncio
10
10
  from meerschaum.utils.typing import Optional
11
- from meerschaum.api import app, no_auth, manager, endpoints
11
+ from meerschaum.api import app, endpoints
12
12
  from meerschaum.utils.packages import attempt_import
13
- from meerschaum.api.dash import active_sessions
14
- from meerschaum.api.dash.users import is_session_authenticated
13
+ from meerschaum.api.dash.sessions import is_session_authenticated
15
14
  fastapi, fastapi_responses = attempt_import('fastapi', 'fastapi.responses')
16
15
  import starlette
17
16
  httpx = attempt_import('httpx')
@@ -25,9 +24,9 @@ PlainTextResponse = fastapi_responses.PlainTextResponse
25
24
 
26
25
  @app.get(endpoints['webterm'], tags=["Webterm"])
27
26
  async def get_webterm(
28
- request: Request,
29
- s: Optional[str] = None,
30
- ) -> HTMLResponse:
27
+ request: Request,
28
+ s: Optional[str] = None,
29
+ ) -> HTMLResponse:
31
30
  """
32
31
  Get the main HTML template for the Webterm.
33
32
  """
@@ -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
@@ -89,6 +96,10 @@ default_system_config = {
89
96
  'proxy_headers': True,
90
97
  'forwarded_allow_ips': '*',
91
98
  },
99
+ 'cache': {
100
+ 'connector': 'valkey:main',
101
+ 'session_expires_minutes': 43200,
102
+ },
92
103
  'permissions': {
93
104
  'registration': {
94
105
  'users': True,
@@ -98,7 +109,7 @@ default_system_config = {
98
109
  'actions': {
99
110
  'non_admin': True,
100
111
  },
101
- 'chaining' : {
112
+ 'chaining': {
102
113
  'insecure_parent_instance': False,
103
114
  'child_apis': False,
104
115
  },
@@ -113,6 +124,7 @@ default_system_config = {
113
124
  'inplace_sync': True,
114
125
  'uv_pip': True,
115
126
  'systemd_healthcheck': False,
127
+ 'valkey_session_cache': True,
116
128
  },
117
129
  }
118
130
  default_pipes_config = {
@@ -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"
@@ -31,21 +31,29 @@ db_host = 'MRSM{stack:' + str(STACK_COMPOSE_FILENAME) + ':services:db:hostname}'
31
31
  api_port = "MRSM{meerschaum:connectors:api:main:port}"
32
32
  api_host = "api"
33
33
 
34
+ valkey_hostname = "valkey"
35
+ valkey_host = 'MRSM{stack:' + str(STACK_COMPOSE_FILENAME) + ':services:valkey:hostname}'
36
+ valkey_port = "MRSM{meerschaum:connectors:valkey:main:port}"
37
+ valkey_username = 'MRSM{meerschaum:connectors:valkey:main:username}'
38
+ valkey_password = 'MRSM{meerschaum:connectors:valkey:main:password}'
39
+
34
40
  env_dict = {
35
- 'COMPOSE_PROJECT_NAME' : 'mrsm',
36
- 'TIMESCALEDB_VERSION' : 'latest-pg16-oss',
37
- 'POSTGRES_USER' : f'{db_user}',
38
- 'POSTGRES_PASSWORD' : f'{db_pass}',
39
- 'POSTGRES_DB' : f'{db_base}',
40
- 'MEERSCHAUM_API_HOSTNAME' : f'{api_host}',
41
- 'ALLOW_IP_RANGE' : '0.0.0.0/0',
42
- 'MEERSCHAUM_API_CONFIG_RESOURCES' : '/meerschaum',
41
+ 'COMPOSE_PROJECT_NAME': 'mrsm',
42
+ 'TIMESCALEDB_VERSION': 'latest-pg16-oss',
43
+ 'POSTGRES_USER': db_user,
44
+ 'POSTGRES_PASSWORD': db_pass,
45
+ 'POSTGRES_DB': db_base,
46
+ 'VALKEY_USERNAME': valkey_username,
47
+ 'VALKEY_PASSWORD': valkey_password,
48
+ 'MEERSCHAUM_API_HOSTNAME': api_host,
49
+ 'ALLOW_IP_RANGE': '0.0.0.0/0',
50
+ 'MEERSCHAUM_API_CONFIG_RESOURCES': '/meerschaum',
43
51
  }
44
52
  ### apply patch to host config to change hostname to the Docker service name
45
53
  env_dict['MEERSCHAUM_API_CONFIG'] = json.dumps(
46
54
  {
47
- 'meerschaum' : 'MRSM{!meerschaum}',
48
- 'system' : 'MRSM{!system}',
55
+ 'meerschaum': 'MRSM{!meerschaum}',
56
+ 'system': 'MRSM{!system}',
49
57
  },
50
58
  indent = 4,
51
59
  ).replace(
@@ -56,9 +64,9 @@ env_dict['MEERSCHAUM_API_CONFIG'] = json.dumps(
56
64
 
57
65
  volumes = {
58
66
  'api_root': '/meerschaum',
59
- 'api_user_local': '/home/meerschaum/.local',
60
67
  'meerschaum_db_data': '/var/lib/postgresql/data',
61
68
  'grafana_storage': '/var/lib/grafana',
69
+ 'valkey_data': '/bitnami/valkey/data',
62
70
  }
63
71
  networks = {
64
72
  'frontend': None,
@@ -74,13 +82,19 @@ env_dict['MEERSCHAUM_API_PATCH'] = json.dumps(
74
82
  'port': 5432,
75
83
  },
76
84
  'local': {
77
- 'database': volumes['api_root'] + '/sqlite/mrsm_local.db'
85
+ 'database': volumes['api_root'] + '/sqlite/mrsm_local.db',
86
+ },
87
+ },
88
+ 'valkey': {
89
+ 'main': {
90
+ 'host': valkey_host,
91
+ 'port': 6379,
78
92
  },
79
93
  },
80
94
  },
81
95
  },
82
96
  },
83
- indent = 4,
97
+ indent=4,
84
98
  )
85
99
 
86
100
  compose_header = """
@@ -114,19 +128,19 @@ default_docker_compose_config = {
114
128
  ],
115
129
  'interval': '5s',
116
130
  'timeout': '3s',
117
- 'retries': 3
131
+ 'retries': 5
118
132
  },
119
133
  'restart': 'always',
120
- 'image' : 'timescale/timescaledb:' + env_dict['TIMESCALEDB_VERSION'],
121
- 'ports' : [
134
+ 'image': 'timescale/timescaledb:' + env_dict['TIMESCALEDB_VERSION'],
135
+ 'ports': [
122
136
  f'{db_port}:5432',
123
137
  ],
124
- 'hostname' : f'{db_hostname}',
125
- 'volumes' : [
138
+ 'hostname': db_hostname,
139
+ 'volumes': [
126
140
  'meerschaum_db_data:' + volumes['meerschaum_db_data'],
127
141
  ],
128
142
  'shm_size': '1024m',
129
- 'networks' : [
143
+ 'networks': [
130
144
  'backend',
131
145
  ],
132
146
  },
@@ -157,10 +171,39 @@ default_docker_compose_config = {
157
171
  'db': {
158
172
  'condition': 'service_healthy',
159
173
  },
174
+ 'valkey': {
175
+ 'condition': 'service_healthy',
176
+ },
160
177
  },
161
- 'volumes' : [
178
+ 'volumes': [
162
179
  'api_root:' + volumes['api_root'],
163
- 'api_user_local:' + volumes['api_user_local'],
180
+ ],
181
+ },
182
+ 'valkey': {
183
+ 'image': 'bitnami/valkey:latest',
184
+ 'restart': 'always',
185
+ 'environment': {
186
+ 'VALKEY_PASSWORD': '<DOLLAR>VALKEY_PASSWORD',
187
+ 'VALKEY_RDB_POLICY_DISABLED': 'no',
188
+ 'VALKEY_RDB_POLICY': '900#1 600#5 300#10 120#50 60#1000 30#10000',
189
+ },
190
+ 'hostname': valkey_hostname,
191
+ 'ports': [
192
+ f'{valkey_port}:6379',
193
+ ],
194
+ 'volumes': [
195
+ 'valkey_data:' + volumes['valkey_data'],
196
+ ],
197
+ 'healthcheck': {
198
+ 'test': [
199
+ 'CMD', 'valkey-cli', 'ping',
200
+ ],
201
+ 'interval': '5s',
202
+ 'timeout': '3s',
203
+ 'retries': 5,
204
+ },
205
+ 'networks': [
206
+ 'backend',
164
207
  ],
165
208
  },
166
209
  'grafana': {
@@ -46,12 +46,16 @@ STATIC_CONFIG: Dict[str, Any] = {
46
46
  'stdin_message': 'MRSM_STDIN',
47
47
  'stop_message': 'MRSM_STOP',
48
48
  'metadata_cache_seconds': 5,
49
+ 'temp_prefix': '.api-temp-',
49
50
  },
50
51
  },
51
52
  'sql': {
52
53
  'internal_schema': '_mrsm_internal',
53
54
  'instance_schema': 'mrsm',
54
55
  },
56
+ 'valkey': {
57
+ 'colon': '-_',
58
+ },
55
59
  'environment': {
56
60
  'config': 'MRSM_CONFIG',
57
61
  'config_dir': 'MRSM_CONFIG_DIR',
@@ -68,11 +72,13 @@ STATIC_CONFIG: Dict[str, Any] = {
68
72
  'uid': 'MRSM_UID',
69
73
  'gid': 'MRSM_GID',
70
74
  'noask': 'MRSM_NOASK',
75
+ 'noninteractive': 'MRSM_NONINTERACTIVE',
71
76
  'id': 'MRSM_SERVER_ID',
72
77
  'daemon_id': 'MRSM_DAEMON_ID',
73
78
  'systemd_log_path': 'MRSM_SYSTEMD_LOG_PATH',
74
79
  'systemd_stdin_path': 'MRSM_SYSTEMD_STDIN_PATH',
75
80
  'systemd_result_path': 'MRSM_SYSTEMD_RESULT_PATH',
81
+ 'systemd_delete_job': 'MRSM_SYSTEMD_DELETE_JOB',
76
82
  'uri_regex': r'MRSM_([a-zA-Z0-9]*)_(\d*[a-zA-Z][a-zA-Z0-9-_+]*$)',
77
83
  'prefix': 'MRSM_',
78
84
  },