meerschaum 3.0.0rc4__py3-none-any.whl → 3.0.0rc8__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 (117) hide show
  1. meerschaum/_internal/arguments/_parser.py +14 -2
  2. meerschaum/_internal/cli/__init__.py +6 -0
  3. meerschaum/_internal/cli/daemons.py +103 -0
  4. meerschaum/_internal/cli/entry.py +220 -0
  5. meerschaum/_internal/cli/workers.py +435 -0
  6. meerschaum/_internal/docs/index.py +1 -2
  7. meerschaum/_internal/entry.py +44 -8
  8. meerschaum/_internal/shell/Shell.py +115 -24
  9. meerschaum/_internal/shell/__init__.py +4 -1
  10. meerschaum/_internal/static.py +4 -1
  11. meerschaum/_internal/term/TermPageHandler.py +1 -2
  12. meerschaum/_internal/term/__init__.py +40 -6
  13. meerschaum/_internal/term/tools.py +33 -8
  14. meerschaum/actions/__init__.py +6 -4
  15. meerschaum/actions/api.py +39 -11
  16. meerschaum/actions/attach.py +1 -0
  17. meerschaum/actions/delete.py +4 -2
  18. meerschaum/actions/edit.py +27 -8
  19. meerschaum/actions/login.py +8 -8
  20. meerschaum/actions/register.py +13 -7
  21. meerschaum/actions/reload.py +22 -5
  22. meerschaum/actions/restart.py +14 -0
  23. meerschaum/actions/show.py +69 -4
  24. meerschaum/actions/start.py +135 -14
  25. meerschaum/actions/stop.py +36 -3
  26. meerschaum/actions/sync.py +6 -1
  27. meerschaum/api/__init__.py +35 -13
  28. meerschaum/api/_events.py +2 -2
  29. meerschaum/api/_oauth2.py +47 -4
  30. meerschaum/api/dash/callbacks/dashboard.py +29 -0
  31. meerschaum/api/dash/callbacks/jobs.py +3 -2
  32. meerschaum/api/dash/callbacks/login.py +10 -1
  33. meerschaum/api/dash/callbacks/register.py +9 -2
  34. meerschaum/api/dash/pages/login.py +2 -2
  35. meerschaum/api/dash/pipes.py +72 -36
  36. meerschaum/api/dash/webterm.py +14 -6
  37. meerschaum/api/models/_pipes.py +7 -1
  38. meerschaum/api/resources/static/js/terminado.js +3 -0
  39. meerschaum/api/resources/static/js/xterm-addon-unicode11.js +2 -0
  40. meerschaum/api/resources/templates/termpage.html +1 -0
  41. meerschaum/api/routes/_jobs.py +23 -11
  42. meerschaum/api/routes/_login.py +73 -5
  43. meerschaum/api/routes/_pipes.py +6 -4
  44. meerschaum/api/routes/_webterm.py +3 -3
  45. meerschaum/config/__init__.py +60 -13
  46. meerschaum/config/_default.py +89 -61
  47. meerschaum/config/_edit.py +10 -8
  48. meerschaum/config/_formatting.py +2 -0
  49. meerschaum/config/_patch.py +4 -2
  50. meerschaum/config/_paths.py +127 -12
  51. meerschaum/config/_read_config.py +32 -12
  52. meerschaum/config/_version.py +1 -1
  53. meerschaum/config/environment.py +262 -0
  54. meerschaum/config/stack/__init__.py +7 -5
  55. meerschaum/connectors/_Connector.py +1 -2
  56. meerschaum/connectors/__init__.py +37 -2
  57. meerschaum/connectors/api/_APIConnector.py +1 -1
  58. meerschaum/connectors/api/_jobs.py +11 -0
  59. meerschaum/connectors/api/_pipes.py +7 -1
  60. meerschaum/connectors/instance/_plugins.py +9 -1
  61. meerschaum/connectors/instance/_tokens.py +20 -3
  62. meerschaum/connectors/instance/_users.py +8 -1
  63. meerschaum/connectors/parse.py +1 -1
  64. meerschaum/connectors/sql/_create_engine.py +3 -0
  65. meerschaum/connectors/sql/_pipes.py +93 -79
  66. meerschaum/connectors/sql/_users.py +8 -1
  67. meerschaum/connectors/valkey/_ValkeyConnector.py +3 -3
  68. meerschaum/connectors/valkey/_pipes.py +7 -5
  69. meerschaum/core/Pipe/__init__.py +45 -71
  70. meerschaum/core/Pipe/_attributes.py +66 -90
  71. meerschaum/core/Pipe/_cache.py +555 -0
  72. meerschaum/core/Pipe/_clear.py +0 -11
  73. meerschaum/core/Pipe/_data.py +0 -50
  74. meerschaum/core/Pipe/_deduplicate.py +0 -13
  75. meerschaum/core/Pipe/_delete.py +12 -21
  76. meerschaum/core/Pipe/_drop.py +11 -23
  77. meerschaum/core/Pipe/_dtypes.py +1 -1
  78. meerschaum/core/Pipe/_index.py +8 -14
  79. meerschaum/core/Pipe/_sync.py +12 -18
  80. meerschaum/core/Plugin/_Plugin.py +7 -1
  81. meerschaum/core/Token/_Token.py +1 -1
  82. meerschaum/core/User/_User.py +1 -2
  83. meerschaum/jobs/_Executor.py +88 -4
  84. meerschaum/jobs/_Job.py +146 -36
  85. meerschaum/jobs/systemd.py +7 -2
  86. meerschaum/plugins/__init__.py +277 -81
  87. meerschaum/utils/daemon/Daemon.py +197 -42
  88. meerschaum/utils/daemon/FileDescriptorInterceptor.py +0 -1
  89. meerschaum/utils/daemon/RotatingFile.py +63 -36
  90. meerschaum/utils/daemon/StdinFile.py +53 -13
  91. meerschaum/utils/daemon/__init__.py +18 -5
  92. meerschaum/utils/daemon/_names.py +6 -3
  93. meerschaum/utils/debug.py +34 -4
  94. meerschaum/utils/dtypes/__init__.py +5 -1
  95. meerschaum/utils/formatting/__init__.py +4 -1
  96. meerschaum/utils/formatting/_jobs.py +1 -1
  97. meerschaum/utils/formatting/_pipes.py +47 -46
  98. meerschaum/utils/formatting/_shell.py +33 -9
  99. meerschaum/utils/misc.py +22 -38
  100. meerschaum/utils/packages/__init__.py +15 -13
  101. meerschaum/utils/packages/_packages.py +1 -0
  102. meerschaum/utils/pipes.py +33 -5
  103. meerschaum/utils/process.py +1 -1
  104. meerschaum/utils/prompt.py +172 -143
  105. meerschaum/utils/sql.py +12 -2
  106. meerschaum/utils/threading.py +42 -0
  107. meerschaum/utils/venv/__init__.py +2 -0
  108. meerschaum/utils/warnings.py +19 -13
  109. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/METADATA +3 -1
  110. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/RECORD +116 -110
  111. meerschaum/config/_environment.py +0 -145
  112. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/WHEEL +0 -0
  113. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/entry_points.txt +0 -0
  114. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/licenses/LICENSE +0 -0
  115. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/licenses/NOTICE +0 -0
  116. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/top_level.txt +0 -0
  117. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/zip-safe +0 -0
@@ -9,10 +9,17 @@ Define file paths
9
9
  from __future__ import annotations
10
10
 
11
11
  from pathlib import Path
12
- import os, platform, sys, json
13
- from meerschaum.utils.typing import Union
12
+ import contextlib
13
+ import os
14
+ import platform
15
+ import sys
16
+ import json
17
+
18
+ from typing import Union, List
19
+
14
20
  from meerschaum._internal.static import STATIC_CONFIG
15
21
 
22
+
16
23
  DOT_CONFIG_DIR_PATH = Path(
17
24
  os.environ.get('XDG_CONFIG_HOME', Path.home() / '.config')
18
25
  if platform.system() != 'Windows'
@@ -71,7 +78,7 @@ if ENVIRONMENT_PLUGINS_DIR in os.environ:
71
78
  if path_str
72
79
  ]
73
80
  )
74
- except Exception as e:
81
+ except Exception:
75
82
  PLUGINS_DIR_PATHS = []
76
83
 
77
84
  if not PLUGINS_DIR_PATHS:
@@ -82,20 +89,23 @@ if ENVIRONMENT_PLUGINS_DIR in os.environ:
82
89
  f"`export {ENVIRONMENT_PLUGINS_DIR}=./plugins:/another/path/to/plugins`\n\n"
83
90
  "or a JSON-encoded path list:\n\n"
84
91
  f"`export {ENVIRONMENT_PLUGINS_DIR}=" + "'[\"./plugins\", \"/another/path/to/plugins\"]'`"
85
- f"",
92
+ "",
86
93
  )
87
94
  sys.exit(1)
88
95
  else:
89
96
  PLUGINS_DIR_PATHS = [_ROOT_DIR_PATH / 'plugins']
90
97
 
91
98
  ### Remove duplicate plugins paths.
92
- _seen_plugins_paths, _plugins_paths_to_remove = set(), set()
93
- for _plugin_path in PLUGINS_DIR_PATHS:
94
- if _plugin_path in _seen_plugins_paths:
95
- _plugins_paths_to_remove.add(_plugin_path)
96
- _seen_plugins_paths.add(_plugin_path)
97
- for _plugin_path in _plugins_paths_to_remove:
98
- PLUGINS_DIR_PATHS.remove(_plugin_path)
99
+ def _process_plugins_dir_paths(plugins_dir_paths: List[Path]):
100
+ _seen_plugins_paths, _plugins_paths_to_remove = set(), set()
101
+ for _plugin_path in plugins_dir_paths:
102
+ if _plugin_path in _seen_plugins_paths:
103
+ _plugins_paths_to_remove.add(_plugin_path)
104
+ _seen_plugins_paths.add(_plugin_path)
105
+ for _plugin_path in _plugins_paths_to_remove:
106
+ plugins_dir_paths.remove(_plugin_path)
107
+ _process_plugins_dir_paths(PLUGINS_DIR_PATHS)
108
+
99
109
 
100
110
  ENVIRONMENT_VENVS_DIR = STATIC_CONFIG['environment']['venvs']
101
111
  if ENVIRONMENT_VENVS_DIR in os.environ:
@@ -144,6 +154,8 @@ paths = {
144
154
  'API_SECRET_KEY_PATH' : ('{API_CONFIG_RESOURCES_PATH}', '.api_secret_key'),
145
155
  'API_UVICORN_RESOURCES_PATH' : ('{API_CONFIG_RESOURCES_PATH}', 'uvicorn'),
146
156
  'API_UVICORN_CONFIG_PATH' : ('{API_UVICORN_RESOURCES_PATH}', '.thread_config.json'),
157
+
158
+ 'WEBTERM_INTERNAL_RESOURCES_PATH': ('{INTERNAL_RESOURCES_PATH}', 'webterm'),
147
159
 
148
160
  'CACHE_RESOURCES_PATH' : ('{ROOT_DIR_PATH}', '.cache'),
149
161
  'PIPES_CACHE_RESOURCES_PATH' : ('{CACHE_RESOURCES_PATH}', 'pipes'),
@@ -151,12 +163,16 @@ paths = {
151
163
  'VENVS_CACHE_RESOURCES_PATH' : ('{CACHE_RESOURCES_PATH}', 'venvs'),
152
164
  'SQL_CONN_CACHE_RESOURCES_PATH' : ('{CACHE_RESOURCES_PATH}', 'sql'),
153
165
 
166
+ 'CLI_RESOURCES_PATH' : ('{INTERNAL_RESOURCES_PATH}', 'cli'),
167
+ 'CLI_LOGS_RESOURCES_PATH' : ('{CLI_RESOURCES_PATH}', 'logs'),
168
+
154
169
  'PLUGINS_RESOURCES_PATH' : ('{INTERNAL_RESOURCES_PATH}', 'plugins'),
155
170
  'PLUGINS_INTERNAL_LOCK_PATH' : ('{INTERNAL_RESOURCES_PATH}', 'plugins.lock'),
156
171
  'PLUGINS_PACKAGES_INTERNAL_PATH' : ('{INTERNAL_RESOURCES_PATH}', 'packaged_plugins'),
157
172
  'PLUGINS_ARCHIVES_RESOURCES_PATH': ('{PLUGINS_RESOURCES_PATH}', '.archives'),
158
173
  'PLUGINS_TEMP_RESOURCES_PATH' : ('{PLUGINS_RESOURCES_PATH}', '.tmp'),
159
174
  'PLUGINS_INIT_PATH' : ('{PLUGINS_RESOURCES_PATH}', '__init__.py'),
175
+ 'PLUGINS_INJECTED_RESOURCES_PATH': ('{PLUGINS_RESOURCES_PATH}', '.injected'),
160
176
 
161
177
  'DB_RESOURCES_PATH' : ('{ROOT_DIR_PATH}', 'db'),
162
178
  'DB_INIT_RESOURCES_PATH' : ('{DB_RESOURCES_PATH}', 'initdb'),
@@ -197,11 +213,110 @@ paths = {
197
213
 
198
214
  def set_root(root: Union[Path, str]):
199
215
  """Modify the value of `ROOT_DIR_PATH`."""
200
- paths['ROOT_DIR_PATH'] = Path(root).resolve()
216
+ paths['ROOT_DIR_PATH'] = Path(root).resolve().as_posix()
201
217
  for path_name, path_parts in paths.items():
202
218
  if isinstance(path_parts, tuple) and path_parts[0] == '{ROOT_DIR_PATH}':
203
219
  globals()[path_name] = __getattr__(path_name)
204
220
 
221
+ def set_plugins_dir_paths(plugins_dir_paths: Union[List[Path], Path, str]):
222
+ """Modify the value of `PLUGINS_DIR_PATHS`."""
223
+ global PLUGINS_DIR_PATHS
224
+ if isinstance(plugins_dir_paths, str):
225
+ if plugins_dir_paths.strip() and plugins_dir_paths.lstrip()[0] == '[':
226
+ plugins_dir_paths = json.loads(plugins_dir_paths)
227
+ else:
228
+ plugins_dir_paths = plugins_dir_paths.split(':')
229
+ plugins_dir_paths = [Path(_path).resolve() for _path in plugins_dir_paths]
230
+ if isinstance(plugins_dir_paths, Path):
231
+ plugins_dir_paths = [plugins_dir_paths.resolve()]
232
+
233
+ PLUGINS_DIR_PATHS = plugins_dir_paths
234
+ _process_plugins_dir_paths(plugins_dir_paths)
235
+
236
+ def set_venvs_dir_path(venvs_dir_path: Union[str, Path]):
237
+ """Modify the value of `VIRTENV_RESOURCES_PATH`."""
238
+ paths['VIRTENV_RESOURCES_PATH'] = Path(venvs_dir_path).resolve().as_posix()
239
+
240
+ def set_config_dir_path(config_dir_path: Union[str, Path]):
241
+ paths['CONFIG_DIR_PATH'] = Path(config_dir_path).resolve().as_posix()
242
+
243
+
244
+ @contextlib.contextmanager
245
+ def replace_root_dir(root_dir_path: Union[str, Path, None]):
246
+ """
247
+ Temporarily replace the root directory path.
248
+ """
249
+ if root_dir_path is None:
250
+ try:
251
+ yield
252
+ finally:
253
+ return
254
+
255
+ old_root = paths.get('ROOT_DIR_PATH', _ROOT_DIR_PATH)
256
+ set_root(root_dir_path)
257
+
258
+ try:
259
+ yield
260
+ finally:
261
+ set_root(old_root)
262
+
263
+ @contextlib.contextmanager
264
+ def replace_plugins_dir_paths(plugins_dir_paths: Union[List[Path], None]):
265
+ """
266
+ Temporarily replace the plugins directory paths.
267
+ """
268
+ if plugins_dir_paths is None:
269
+ try:
270
+ yield
271
+ finally:
272
+ return
273
+
274
+ old_plugins_dir_paths = PLUGINS_DIR_PATHS
275
+ set_plugins_dir_paths(plugins_dir_paths)
276
+
277
+ try:
278
+ yield
279
+ finally:
280
+ set_plugins_dir_paths(old_plugins_dir_paths)
281
+
282
+ @contextlib.contextmanager
283
+ def replace_venvs_dir_path(venvs_dir_path: Union[Path, None]):
284
+ """
285
+ Temporarily replace the virtual environments directory path.
286
+ """
287
+ if venvs_dir_path is None:
288
+ try:
289
+ yield
290
+ finally:
291
+ return
292
+
293
+ old_venvs_dir_path = paths.get('VIRTENV_RESOURCES_PATH', _VENVS_DIR_PATH)
294
+ set_venvs_dir_path(venvs_dir_path)
295
+
296
+ try:
297
+ yield
298
+ finally:
299
+ set_venvs_dir_path(old_venvs_dir_path)
300
+
301
+ @contextlib.contextmanager
302
+ def replace_config_dir_path(config_dir_path: Union[Path, None]):
303
+ """
304
+ Temporarily replace the config directory path.
305
+ """
306
+ if config_dir_path is None:
307
+ try:
308
+ yield
309
+ finally:
310
+ return
311
+
312
+ old_config_dir_path = paths.get('CONFIG_DIR_PATH', _CONFIG_DIR_PATH)
313
+ set_config_dir_path(config_dir_path)
314
+
315
+ try:
316
+ yield
317
+ finally:
318
+ set_config_dir_path(old_config_dir_path)
319
+
205
320
 
206
321
  def __getattr__(name: str) -> Path:
207
322
  if name not in paths:
@@ -8,7 +8,9 @@ Import the config yaml file
8
8
  from __future__ import annotations
9
9
  import pathlib
10
10
 
11
+ import meerschaum as mrsm
11
12
  from meerschaum.utils.typing import Optional, Dict, Any, List, Tuple, Union
13
+ from meerschaum._internal.static import STATIC_CONFIG
12
14
 
13
15
 
14
16
  def read_config(
@@ -53,7 +55,6 @@ def read_config(
53
55
  import itertools
54
56
  from meerschaum.utils.yaml import yaml, _yaml
55
57
  from meerschaum.config._paths import CONFIG_DIR_PATH
56
- from meerschaum._internal.static import STATIC_CONFIG
57
58
  from meerschaum.config._patch import apply_patch_to_config
58
59
  if directory is None:
59
60
  directory = CONFIG_DIR_PATH
@@ -106,20 +107,25 @@ def read_config(
106
107
  ### If default config contains symlinks, add them to the config to write.
107
108
  try:
108
109
  _default_symlinks = _default_dict[symlinks_key][mk]
109
- except Exception:
110
+ except KeyError:
110
111
  _default_symlinks = {}
112
+
111
113
  config[mk] = _default_dict[mk]
112
114
  if _default_symlinks:
113
115
  if symlinks_key not in config:
114
116
  config[symlinks_key] = {}
117
+ if symlinks_key not in config_to_write:
118
+ config_to_write[symlinks_key] = {}
119
+
115
120
  if mk not in config[symlinks_key]:
116
121
  config[symlinks_key][mk] = {}
122
+ if mk not in config_to_write[symlinks_key]:
123
+ config_to_write[symlinks_key][mk] = {}
124
+
117
125
  config[symlinks_key][mk] = apply_patch_to_config(
118
126
  config[symlinks_key][mk],
119
127
  _default_symlinks
120
128
  )
121
- if symlinks_key not in config_to_write:
122
- config_to_write[symlinks_key] = {}
123
129
  config_to_write[symlinks_key][mk] = config[symlinks_key][mk]
124
130
 
125
131
  ### Write the default key.
@@ -387,10 +393,16 @@ def search_and_substitute_config(
387
393
  s[_keys[-1]] = _pattern
388
394
 
389
395
  from meerschaum.config._patch import apply_patch_to_config
390
- from meerschaum._internal.static import STATIC_CONFIG
391
396
  symlinks_key = STATIC_CONFIG['config']['symlinks_key']
392
- if symlinks_key in parsed_config:
393
- parsed_config[symlinks_key] = apply_patch_to_config(parsed_config[symlinks_key], symlinks)
397
+ if symlinks:
398
+ if symlinks_key not in parsed_config:
399
+ parsed_config[symlinks_key] = symlinks
400
+ else:
401
+ parsed_config[symlinks_key] = apply_patch_to_config(
402
+ parsed_config[symlinks_key],
403
+ symlinks,
404
+ warn=False,
405
+ )
394
406
 
395
407
  return parsed_config
396
408
 
@@ -409,15 +421,24 @@ def revert_symlinks_config(config: Dict[str, Any]) -> Dict[str, Any]:
409
421
  -------
410
422
  A configuration dictionary with `_symlinks` re-applied.
411
423
  """
412
- from meerschaum.config import apply_patch_to_config
424
+ import copy
413
425
  from meerschaum._internal.static import STATIC_CONFIG
426
+
414
427
  symlinks_key = STATIC_CONFIG['config']['symlinks_key']
415
428
  if symlinks_key not in config:
416
429
  return config
417
430
 
418
- symlinks_config = config[symlinks_key]
419
- reverted_config = apply_patch_to_config(config, symlinks_config)
420
- _ = reverted_config.pop(symlinks_key, None)
431
+ reverted_config = copy.deepcopy(config)
432
+ symlinks_config = reverted_config.pop(symlinks_key)
433
+
434
+ def deep_patch(target_dict, patch_dict):
435
+ for key, value in patch_dict.items():
436
+ if isinstance(value, dict) and key in target_dict and isinstance(target_dict[key], dict):
437
+ deep_patch(target_dict[key], value)
438
+ else:
439
+ target_dict[key] = value
440
+
441
+ deep_patch(reverted_config, symlinks_config)
421
442
  return reverted_config
422
443
 
423
444
 
@@ -462,7 +483,6 @@ def get_keyfile_path(
462
483
  )
463
484
  except IndexError:
464
485
  if create_new:
465
- from meerschaum._internal.static import STATIC_CONFIG
466
486
  default_filetype = STATIC_CONFIG['config']['default_filetype']
467
487
  return pathlib.Path(os.path.join(directory, key + '.' + default_filetype))
468
488
  return None
@@ -2,4 +2,4 @@
2
2
  Specify the Meerschaum release version.
3
3
  """
4
4
 
5
- __version__ = "3.0.0rc4"
5
+ __version__ = "3.0.0rc8"
@@ -0,0 +1,262 @@
1
+ #! /usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # vim:fenc=utf-8
4
+
5
+ """
6
+ Patch the runtime configuration from environment variables.
7
+ """
8
+
9
+ import os
10
+ import re
11
+ import json
12
+ import contextlib
13
+ import copy
14
+ import pathlib
15
+
16
+ from meerschaum.utils.typing import List, Union, Dict, Any, Optional
17
+ from meerschaum._internal.static import STATIC_CONFIG
18
+
19
+
20
+ def apply_environment_patches(env: Optional[Dict[str, Any]] = None) -> None:
21
+ """
22
+ Apply patches defined in `MRSM_CONFIG` and `MRSM_PATCH`.
23
+ """
24
+ config_var = STATIC_CONFIG['environment']['config']
25
+ patch_var = STATIC_CONFIG['environment']['patch']
26
+ apply_environment_config(config_var, env=env)
27
+ apply_environment_config(patch_var, env=env)
28
+
29
+
30
+ def apply_environment_config(env_var: str, env: Optional[Dict[str, Any]] = None) -> None:
31
+ """
32
+ Parse a dictionary (simple or JSON) from an environment variable
33
+ and apply it to the current configuration.
34
+ """
35
+ from meerschaum.config import get_config, set_config, _config
36
+ from meerschaum.config._patch import apply_patch_to_config
37
+
38
+ env = env if env is not None else os.environ
39
+
40
+ if env_var not in env:
41
+ return
42
+
43
+ from meerschaum.utils.misc import string_to_dict
44
+ try:
45
+ _patch = string_to_dict(str(os.environ[env_var]).lstrip())
46
+ except Exception:
47
+ _patch = None
48
+
49
+ error_msg = (
50
+ f"Environment variable {env_var} is set but cannot be parsed.\n"
51
+ f"Unset {env_var} or change to JSON or simplified dictionary format "
52
+ "(see --help, under params for formatting)\n"
53
+ f"{env_var} is set to:\n{os.environ[env_var]}\n"
54
+ f"Skipping patching os environment into config..."
55
+ )
56
+
57
+ if not isinstance(_patch, dict):
58
+ print(error_msg)
59
+ return
60
+
61
+ valids = []
62
+
63
+ def load_key(key: str) -> Union[Dict[str, Any], None]:
64
+ try:
65
+ c = get_config(key, warn=False)
66
+ except Exception:
67
+ c = None
68
+ return c
69
+
70
+ ### This was multi-threaded, but I ran into all sorts of locking issues.
71
+ keys = list(_patch.keys())
72
+ for key in keys:
73
+ _ = load_key(key)
74
+
75
+ ### Load and patch config files.
76
+ set_config(
77
+ apply_patch_to_config(
78
+ _config(),
79
+ _patch,
80
+ )
81
+ )
82
+
83
+
84
+ def apply_environment_uris(env: Optional[Dict[str, Any]] = None) -> None:
85
+ """
86
+ Patch temporary connectors defined in environment variables which start with
87
+ `MRSM_SQL_` or `MRSM_API_`.
88
+ """
89
+ for env_var in get_connector_env_vars(env=env):
90
+ apply_connector_uri(env_var, env=env)
91
+
92
+
93
+ def get_connector_env_regex() -> str:
94
+ """
95
+ Return the regex pattern for valid environment variable names for instance connectors.
96
+ """
97
+ return STATIC_CONFIG['environment']['uri_regex']
98
+
99
+
100
+ def get_connector_env_vars(env: Optional[Dict[str, Any]] = None) -> List[str]:
101
+ """
102
+ Get the names of the environment variables which match the Meerschaum connector regex.
103
+
104
+ Examples
105
+ --------
106
+ >>> get_connector_environment_vars()
107
+ ['MRSM_SQL_FOO']
108
+ """
109
+ uri_regex = get_connector_env_regex()
110
+ env_vars = []
111
+
112
+ env = env if env is not None else os.environ
113
+
114
+ for env_var in env:
115
+ matched = re.match(uri_regex, env_var)
116
+ if matched is None:
117
+ continue
118
+ if env_var in STATIC_CONFIG['environment'].values():
119
+ continue
120
+ env_vars.append(env_var)
121
+
122
+ return env_vars
123
+
124
+
125
+ def apply_connector_uri(env_var: str, env: Optional[Dict[str, Any]] = None) -> None:
126
+ """
127
+ Parse and validate a URI obtained from an environment variable.
128
+ """
129
+ from meerschaum.config import get_config, set_config, _config
130
+ from meerschaum.config._patch import apply_patch_to_config
131
+ from meerschaum.config._read_config import search_and_substitute_config
132
+ from meerschaum.utils.warnings import warn
133
+
134
+ env = env if env is not None else os.environ
135
+
136
+ if env_var not in env:
137
+ return
138
+
139
+ uri_regex = get_connector_env_regex()
140
+ matched = re.match(uri_regex, env_var)
141
+ groups = matched.groups()
142
+ typ, label = groups[0].lower(), groups[1].lower()
143
+ if not typ or not label:
144
+ return
145
+
146
+ uri = env[env_var]
147
+
148
+ if uri.lstrip().startswith('{') and uri.rstrip().endswith('}'):
149
+ try:
150
+ conn_attrs = json.loads(uri)
151
+ except Exception:
152
+ warn(f"Unable to parse JSON for environment connector '{typ}:{label}'.")
153
+ conn_attrs = {'uri': uri}
154
+ else:
155
+ conn_attrs = {'uri': uri}
156
+
157
+ set_config(
158
+ apply_patch_to_config(
159
+ {'meerschaum': get_config('meerschaum')},
160
+ {'meerschaum': {'connectors': {typ: {label: conn_attrs}}}},
161
+ )
162
+ )
163
+
164
+
165
+ def get_env_vars(env: Optional[Dict[str, Any]] = None) -> List[str]:
166
+ """
167
+ Return all environment variables which begin with `'MRSM_'`.
168
+ """
169
+ prefix = STATIC_CONFIG['environment']['prefix']
170
+ env = env if env is not None else os.environ
171
+ return sorted([env_var for env_var in env if env_var.startswith(prefix)])
172
+
173
+
174
+ @contextlib.contextmanager
175
+ def replace_env(env: Union[Dict[str, Any], None]):
176
+ """
177
+ Temporarily replace environment variables and current configuration.
178
+
179
+ Parameters
180
+ ----------
181
+ env: Dict[str, Any]
182
+ The new environment dictionary to be patched on `os.environ`.
183
+ """
184
+ if env is None:
185
+ try:
186
+ yield
187
+ finally:
188
+ return
189
+
190
+ from meerschaum.config import _config, set_config
191
+ from meerschaum.config.paths import (
192
+ set_root,
193
+ set_plugins_dir_paths,
194
+ set_venvs_dir_path,
195
+ set_config_dir_path,
196
+ ROOT_DIR_PATH,
197
+ PLUGINS_DIR_PATHS,
198
+ VIRTENV_RESOURCES_PATH,
199
+ CONFIG_DIR_PATH,
200
+ )
201
+
202
+ old_environ = dict(os.environ)
203
+ old_config = copy.deepcopy(_config())
204
+ old_root_dir_path = ROOT_DIR_PATH
205
+ old_plugins_dir_paths = PLUGINS_DIR_PATHS
206
+ old_venvs_dir_path = VIRTENV_RESOURCES_PATH
207
+ old_config_dir_path = CONFIG_DIR_PATH
208
+
209
+ os.environ.update(env)
210
+
211
+ root_dir_env_var = STATIC_CONFIG['environment']['root']
212
+ plugins_dir_env_var = STATIC_CONFIG['environment']['plugins']
213
+ config_dir_env_var = STATIC_CONFIG['environment']['config_dir']
214
+ venvs_dir_env_var = STATIC_CONFIG['environment']['venvs']
215
+
216
+ replaced_root = False
217
+ if root_dir_env_var in env:
218
+ root_dir_path = pathlib.Path(env[root_dir_env_var])
219
+ set_root(root_dir_path)
220
+ replaced_root = True
221
+
222
+ replaced_plugins = False
223
+ if plugins_dir_env_var in env:
224
+ plugins_dir_paths = env[plugins_dir_env_var]
225
+ set_plugins_dir_paths(plugins_dir_paths)
226
+ replaced_plugins = True
227
+
228
+ replaced_venvs = False
229
+ if venvs_dir_env_var in env:
230
+ venv_dir_path = pathlib.Path(env[venvs_dir_env_var])
231
+ set_venvs_dir_path(venv_dir_path)
232
+ replaced_venvs = True
233
+
234
+ replaced_config_dir = False
235
+ if config_dir_env_var in env:
236
+ config_dir_path = pathlib.Path(env[config_dir_env_var])
237
+ set_config_dir_path(config_dir_path)
238
+ replaced_config_dir = True
239
+
240
+ apply_environment_patches(env)
241
+ apply_environment_uris(env)
242
+
243
+ try:
244
+ yield
245
+ finally:
246
+ os.environ.clear()
247
+ os.environ.update(old_environ)
248
+
249
+ if replaced_root:
250
+ set_root(old_root_dir_path)
251
+
252
+ if replaced_plugins:
253
+ set_plugins_dir_paths(old_plugins_dir_paths)
254
+
255
+ if replaced_venvs:
256
+ set_venvs_dir_path(old_venvs_dir_path)
257
+
258
+ if replaced_config_dir:
259
+ set_config_dir_path(old_config_dir_path)
260
+
261
+ _config().clear()
262
+ set_config(old_config)
@@ -56,19 +56,22 @@ env_dict['MEERSCHAUM_API_CONFIG'] = json.dumps(
56
56
  {
57
57
  'meerschaum': 'MRSM{!meerschaum}',
58
58
  'system': 'MRSM{!system}',
59
+ 'api': 'MRSM{!api}',
59
60
  },
60
61
  indent=4,
61
62
  ).replace(
62
63
  '"MRSM{!system}"', 'MRSM{!system}'
63
64
  ).replace(
64
- '"MRSM{!meerschaum}"', 'MRSM{!meerschaum}',
65
+ '"MRSM{!meerschaum}"', 'MRSM{!meerschaum}'
66
+ ).replace(
67
+ '"MRSM{!api}"', 'MRSM{!api}'
65
68
  )
66
69
 
67
70
  volumes = {
68
71
  'api_root': '/meerschaum',
69
- 'meerschaum_db_data': '/var/lib/postgresql/data',
72
+ 'meerschaum_db_data': '/home/postgres/pgdata',
70
73
  'grafana_storage': '/var/lib/grafana',
71
- 'valkey_data': '/bitnami/valkey/data',
74
+ 'valkey_data': '/valkey/data',
72
75
  }
73
76
  networks = {
74
77
  'frontend': None,
@@ -122,7 +125,6 @@ default_docker_compose_config = {
122
125
  'POSTGRES_DB': '<DOLLAR>POSTGRES_DB',
123
126
  'POSTGRES_PASSWORD': '<DOLLAR>POSTGRES_PASSWORD',
124
127
  'ALLOW_IP_RANGE': env_dict['ALLOW_IP_RANGE'],
125
- # 'POSTGRES_INITDB_ARGS': '-c max_connections=1000 -c shared_buffers=1024MB -c max_prepared_transactions=100'
126
128
  },
127
129
  'command': 'postgres -c max_connections=1000 -c shared_buffers=1024MB -c max_prepared_transactions=100',
128
130
  'healthcheck': {
@@ -184,7 +186,7 @@ default_docker_compose_config = {
184
186
  ],
185
187
  },
186
188
  'valkey': {
187
- 'image': 'bitnami/valkey:latest',
189
+ 'image': 'valkey/valkey:latest',
188
190
  'restart': 'always',
189
191
  'environment': {
190
192
  'VALKEY_PASSWORD': '<DOLLAR>VALKEY_PASSWORD',
@@ -152,8 +152,7 @@ class Connector(metaclass=abc.ABCMeta):
152
152
  ------
153
153
  An error if any of the required attributes are missing.
154
154
  """
155
- from meerschaum.utils.warnings import error, warn
156
- from meerschaum.utils.debug import dprint
155
+ from meerschaum.utils.warnings import error
157
156
  from meerschaum.utils.misc import items_str
158
157
  if required_attributes is None:
159
158
  required_attributes = ['type', 'label']