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
@@ -31,15 +31,23 @@ 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(
@@ -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,
@@ -77,10 +85,14 @@ env_dict['MEERSCHAUM_API_PATCH'] = json.dumps(
77
85
  'database': volumes['api_root'] + '/sqlite/mrsm_local.db'
78
86
  },
79
87
  },
88
+ 'valkey': {
89
+ 'host': valkey_host,
90
+ 'port': 6379,
91
+ },
80
92
  },
81
93
  },
82
94
  },
83
- indent = 4,
95
+ indent=4,
84
96
  )
85
97
 
86
98
  compose_header = """
@@ -114,19 +126,19 @@ default_docker_compose_config = {
114
126
  ],
115
127
  'interval': '5s',
116
128
  'timeout': '3s',
117
- 'retries': 3
129
+ 'retries': 5
118
130
  },
119
131
  'restart': 'always',
120
- 'image' : 'timescale/timescaledb:' + env_dict['TIMESCALEDB_VERSION'],
121
- 'ports' : [
132
+ 'image': 'timescale/timescaledb:' + env_dict['TIMESCALEDB_VERSION'],
133
+ 'ports': [
122
134
  f'{db_port}:5432',
123
135
  ],
124
- 'hostname' : f'{db_hostname}',
125
- 'volumes' : [
136
+ 'hostname': f'{db_hostname}',
137
+ 'volumes': [
126
138
  'meerschaum_db_data:' + volumes['meerschaum_db_data'],
127
139
  ],
128
140
  'shm_size': '1024m',
129
- 'networks' : [
141
+ 'networks': [
130
142
  'backend',
131
143
  ],
132
144
  },
@@ -157,10 +169,39 @@ default_docker_compose_config = {
157
169
  'db': {
158
170
  'condition': 'service_healthy',
159
171
  },
172
+ 'valkey': {
173
+ 'condition': 'service_healthy',
174
+ },
160
175
  },
161
- 'volumes' : [
176
+ 'volumes': [
162
177
  'api_root:' + volumes['api_root'],
163
- 'api_user_local:' + volumes['api_user_local'],
178
+ ],
179
+ },
180
+ 'valkey': {
181
+ 'image': 'bitnami/valkey',
182
+ 'restart': 'always',
183
+ 'environment': {
184
+ 'VALKEY_PASSWORD': '<DOLLAR>VALKEY_PASSWORD',
185
+ 'VALKEY_RDB_POLICY_DISABLED': 'no',
186
+ 'VALKEY_RDB_POLICY': '900#1 600#5 300#10 120#50 60#1000 30#10000',
187
+ },
188
+ 'hostname': valkey_hostname,
189
+ 'ports': [
190
+ f'{valkey_port}:6379',
191
+ ],
192
+ 'volumes': [
193
+ 'valkey_data:' + volumes['valkey_data'],
194
+ ],
195
+ 'healthcheck': {
196
+ 'test': [
197
+ 'CMD', 'valkey-cli', 'ping',
198
+ ],
199
+ 'interval': '5s',
200
+ 'timeout': '3s',
201
+ 'retries': 5,
202
+ },
203
+ 'networks': [
204
+ 'backend',
164
205
  ],
165
206
  },
166
207
  'grafana': {
@@ -46,6 +46,7 @@ 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': {
@@ -73,6 +74,7 @@ STATIC_CONFIG: Dict[str, Any] = {
73
74
  'systemd_log_path': 'MRSM_SYSTEMD_LOG_PATH',
74
75
  'systemd_stdin_path': 'MRSM_SYSTEMD_STDIN_PATH',
75
76
  'systemd_result_path': 'MRSM_SYSTEMD_RESULT_PATH',
77
+ 'systemd_delete_job': 'MRSM_SYSTEMD_DELETE_JOB',
76
78
  'uri_regex': r'MRSM_([a-zA-Z0-9]*)_(\d*[a-zA-Z][a-zA-Z0-9-_+]*$)',
77
79
  'prefix': 'MRSM_',
78
80
  },
@@ -53,17 +53,23 @@ class Connector(metaclass=abc.ABCMeta):
53
53
  """
54
54
  self._original_dict = copy.deepcopy(self.__dict__)
55
55
  self._set_attributes(type=type, label=label, **kw)
56
- self.verify_attributes(getattr(self, 'REQUIRED_ATTRIBUTES', None))
56
+
57
+ ### NOTE: Override `REQUIRED_ATTRIBUTES` if `uri` is set.
58
+ self.verify_attributes(
59
+ ['uri']
60
+ if 'uri' in self.__dict__
61
+ else getattr(self, 'REQUIRED_ATTRIBUTES', None)
62
+ )
57
63
 
58
64
  def _reset_attributes(self):
59
65
  self.__dict__ = self._original_dict
60
66
 
61
67
  def _set_attributes(
62
- self,
63
- *args,
64
- inherit_default: bool = True,
65
- **kw: Any
66
- ):
68
+ self,
69
+ *args,
70
+ inherit_default: bool = True,
71
+ **kw: Any
72
+ ):
67
73
  from meerschaum.config.static import STATIC_CONFIG
68
74
  from meerschaum.utils.warnings import error
69
75
 
@@ -114,12 +120,11 @@ class Connector(metaclass=abc.ABCMeta):
114
120
  ### finally, update __dict__ with _attributes.
115
121
  self.__dict__.update(self._attributes)
116
122
 
117
-
118
123
  def verify_attributes(
119
- self,
120
- required_attributes: Optional[List[str]] = None,
121
- debug: bool = False
122
- ) -> None:
124
+ self,
125
+ required_attributes: Optional[List[str]] = None,
126
+ debug: bool = False,
127
+ ) -> None:
123
128
  """
124
129
  Ensure that the required attributes have been met.
125
130
 
@@ -147,6 +152,7 @@ class Connector(metaclass=abc.ABCMeta):
147
152
  from meerschaum.utils.misc import items_str
148
153
  if required_attributes is None:
149
154
  required_attributes = ['label']
155
+
150
156
  missing_attributes = set()
151
157
  for a in required_attributes:
152
158
  if a not in self.__dict__:
@@ -158,8 +164,8 @@ class Connector(metaclass=abc.ABCMeta):
158
164
  + f"for connector '{self.type}:{self.label}'."
159
165
  ),
160
166
  InvalidAttributesError,
161
- silent = True,
162
- stack = False
167
+ silent=True,
168
+ stack=False
163
169
  )
164
170
 
165
171
 
@@ -39,6 +39,7 @@ connectors: Dict[str, Dict[str, Connector]] = {
39
39
  'api' : {},
40
40
  'sql' : {},
41
41
  'plugin' : {},
42
+ 'valkey' : {},
42
43
  }
43
44
  instance_types: List[str] = ['sql', 'api']
44
45
  _locks: Dict[str, RLock] = {
@@ -164,13 +165,15 @@ def get_connector(
164
165
 
165
166
  if 'sql' not in types:
166
167
  from meerschaum.connectors.plugin import PluginConnector
168
+ from meerschaum.connectors.valkey import ValkeyConnector
167
169
  with _locks['types']:
168
170
  types.update({
169
- 'api' : APIConnector,
170
- 'sql' : SQLConnector,
171
+ 'api': APIConnector,
172
+ 'sql': SQLConnector,
171
173
  'plugin': PluginConnector,
174
+ 'valkey': ValkeyConnector,
172
175
  })
173
-
176
+
174
177
  ### determine if we need to call the constructor
175
178
  if not refresh:
176
179
  ### see if any user-supplied arguments differ from the existing instance
@@ -273,7 +276,7 @@ def is_connected(keys: str, **kw) -> bool:
273
276
  with warnings.catch_warnings():
274
277
  warnings.filterwarnings('ignore')
275
278
  return conn.test_connection(**kw)
276
- except Exception as e:
279
+ except Exception:
277
280
  return False
278
281
 
279
282
 
@@ -340,7 +343,7 @@ def load_plugin_connectors():
340
343
  to_import.append(plugin.name)
341
344
  if not to_import:
342
345
  return
343
- import_plugins(*to_import)
346
+ import_plugins(*to_import)
344
347
 
345
348
 
346
349
  def get_connector_plugin(
@@ -378,3 +381,4 @@ def _load_builtin_custom_connectors():
378
381
  Import custom connectors decorated with `@make_connector` or `@make_executor`.
379
382
  """
380
383
  import meerschaum.jobs.systemd
384
+ import meerschaum.connectors.valkey
@@ -17,6 +17,7 @@ from meerschaum.utils.typing import SuccessTuple, List, Callable, Optional
17
17
  from meerschaum.config.static import STATIC_CONFIG
18
18
 
19
19
  ACTIONS_ENDPOINT: str = STATIC_CONFIG['api']['endpoints']['actions']
20
+ TEMP_PREFIX: str = STATIC_CONFIG['api']['jobs']['temp_prefix']
20
21
 
21
22
 
22
23
  def get_actions(self):
@@ -37,43 +38,28 @@ async def do_action_async(
37
38
  callback_function: Callable[[str], None] = partial(print, end=''),
38
39
  ) -> SuccessTuple:
39
40
  """
40
- Monitor a job's log files and await a callback with the changes.
41
+ Execute an action as a temporary remote job.
41
42
  """
42
- websockets, websockets_exceptions = mrsm.attempt_import('websockets', 'websockets.exceptions')
43
- protocol = 'ws' if self.URI.startswith('http://') else 'wss'
44
- port = self.port if 'port' in self.__dict__ else ''
45
- uri = f"{protocol}://{self.host}:{port}{ACTIONS_ENDPOINT}/ws"
46
- if sysargs and sysargs[0] == 'api' and len(sysargs) > 2:
47
- sysargs = sysargs[2:]
48
-
49
- sysargs_str = json.dumps(sysargs)
50
-
51
- async with websockets.connect(uri) as websocket:
52
- try:
53
- await websocket.send(self.token or 'no-login')
54
- response = await websocket.recv()
55
- init_data = json.loads(response)
56
- if not init_data.get('is_authenticated'):
57
- return False, "Cannot authenticate with actions endpoint."
58
-
59
- await websocket.send(sysargs_str)
60
- except websockets_exceptions.ConnectionClosedOK:
61
- return False, "Connection was closed."
62
-
63
- while True:
64
- try:
65
- line = await websocket.recv()
66
- if asyncio.iscoroutinefunction(callback_function):
67
- await callback_function(line)
68
- else:
69
- callback_function(line)
70
- except KeyboardInterrupt:
71
- await websocket.close()
72
- break
73
- except websockets_exceptions.ConnectionClosedOK:
74
- break
75
-
76
- return True, "Success"
43
+ from meerschaum._internal.arguments import remove_api_executor_keys
44
+ from meerschaum.utils.misc import generate_password
45
+ sysargs = remove_api_executor_keys(sysargs)
46
+
47
+ job_name = TEMP_PREFIX + generate_password(12)
48
+ job = mrsm.Job(job_name, sysargs, executor_keys=str(self))
49
+
50
+ start_success, start_msg = job.start()
51
+ if not start_success:
52
+ return start_success, start_msg
53
+
54
+ await job.monitor_logs_async(
55
+ callback_function=callback_function,
56
+ stop_on_exit=True,
57
+ strip_timestamps=True,
58
+ )
59
+
60
+ success, msg = job.result
61
+ job.delete()
62
+ return success, msg
77
63
 
78
64
 
79
65
  def do_action_legacy(
@@ -277,6 +277,7 @@ async def monitor_logs_async(
277
277
  """
278
278
  Monitor a job's log files and await a callback with the changes.
279
279
  """
280
+ import traceback
280
281
  from meerschaum.jobs import StopMonitoringLogs
281
282
  from meerschaum.utils.formatting._jobs import strip_timestamp_from_line
282
283
 
@@ -9,16 +9,16 @@ Poll database and API connections.
9
9
  from meerschaum.utils.typing import InstanceConnector, Union, Optional, Dict, Any
10
10
 
11
11
  def retry_connect(
12
- connector: Union[InstanceConnector, None] = None,
13
- max_retries: int = 50,
14
- retry_wait: int = 3,
15
- workers: int = 1,
16
- warn: bool = True,
17
- print_on_connect: bool = False,
18
- enforce_chaining: bool = True,
19
- enforce_login: bool = True,
20
- debug: bool = False,
21
- ) -> bool:
12
+ connector: Union[InstanceConnector, None] = None,
13
+ max_retries: int = 50,
14
+ retry_wait: int = 3,
15
+ workers: int = 1,
16
+ warn: bool = True,
17
+ print_on_connect: bool = False,
18
+ enforce_chaining: bool = True,
19
+ enforce_login: bool = True,
20
+ debug: bool = False,
21
+ ) -> bool:
22
22
  """
23
23
  Keep trying to connect to the database.
24
24
 
@@ -85,16 +85,16 @@ def retry_connect(
85
85
 
86
86
 
87
87
  def _wrap_retry_connect(
88
- connector_meta: Dict[str, Any],
89
- max_retries: int = 50,
90
- retry_wait: int = 3,
91
- workers: int = 1,
92
- print_on_connect: bool = False,
93
- warn: bool = True,
94
- enforce_chaining: bool = True,
95
- enforce_login: bool = True,
96
- debug: bool = False,
97
- ) -> bool:
88
+ connector_meta: Dict[str, Any],
89
+ max_retries: int = 50,
90
+ retry_wait: int = 3,
91
+ workers: int = 1,
92
+ print_on_connect: bool = False,
93
+ warn: bool = True,
94
+ enforce_chaining: bool = True,
95
+ enforce_login: bool = True,
96
+ debug: bool = False,
97
+ ) -> bool:
98
98
  """
99
99
  Keep trying to connect to the database.
100
100
 
@@ -144,8 +144,6 @@ def _wrap_retry_connect(
144
144
  import time
145
145
 
146
146
  connector = get_connector(**connector_meta)
147
- if connector.type not in instance_types:
148
- return None
149
147
 
150
148
  if not hasattr(connector, 'test_connection'):
151
149
  return True
@@ -157,7 +155,15 @@ def _wrap_retry_connect(
157
155
  dprint(f"Trying to connect to '{connector}'...")
158
156
  dprint(f"Attempt ({retries + 1} / {max_retries})")
159
157
 
160
- if connector.type == 'sql':
158
+ if connector.type not in ('sql', 'api'):
159
+ try:
160
+ connected = connector.test_connection()
161
+ except Exception as e:
162
+ if warn:
163
+ print(e)
164
+ connected = False
165
+
166
+ elif connector.type == 'sql':
161
167
 
162
168
  def _connect(_connector):
163
169
  ### Test queries like `SELECT 1`.
@@ -185,7 +191,7 @@ def _wrap_retry_connect(
185
191
  _warn(
186
192
  f"Meerschaum instance '{connector}' does not allow chaining " +
187
193
  "and cannot be used as the parent for this instance.",
188
- stack = False
194
+ stack=False,
189
195
  )
190
196
  return False
191
197