meerschaum 2.9.5__py3-none-any.whl → 3.0.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 (200) hide show
  1. meerschaum/__init__.py +5 -2
  2. meerschaum/_internal/__init__.py +1 -0
  3. meerschaum/_internal/arguments/_parse_arguments.py +4 -4
  4. meerschaum/_internal/arguments/_parser.py +33 -4
  5. meerschaum/_internal/cli/__init__.py +6 -0
  6. meerschaum/_internal/cli/daemons.py +103 -0
  7. meerschaum/_internal/cli/entry.py +220 -0
  8. meerschaum/_internal/cli/workers.py +435 -0
  9. meerschaum/_internal/docs/index.py +48 -2
  10. meerschaum/_internal/entry.py +50 -14
  11. meerschaum/_internal/shell/Shell.py +121 -29
  12. meerschaum/_internal/shell/__init__.py +4 -1
  13. meerschaum/_internal/static.py +359 -0
  14. meerschaum/_internal/term/TermPageHandler.py +1 -2
  15. meerschaum/_internal/term/__init__.py +40 -6
  16. meerschaum/_internal/term/tools.py +33 -8
  17. meerschaum/actions/__init__.py +6 -4
  18. meerschaum/actions/api.py +53 -13
  19. meerschaum/actions/attach.py +1 -0
  20. meerschaum/actions/bootstrap.py +8 -8
  21. meerschaum/actions/delete.py +4 -2
  22. meerschaum/actions/edit.py +171 -25
  23. meerschaum/actions/login.py +8 -8
  24. meerschaum/actions/register.py +143 -6
  25. meerschaum/actions/reload.py +22 -5
  26. meerschaum/actions/restart.py +14 -0
  27. meerschaum/actions/show.py +184 -31
  28. meerschaum/actions/start.py +166 -17
  29. meerschaum/actions/stop.py +38 -2
  30. meerschaum/actions/sync.py +7 -2
  31. meerschaum/actions/tag.py +9 -8
  32. meerschaum/actions/verify.py +5 -8
  33. meerschaum/api/__init__.py +45 -15
  34. meerschaum/api/_events.py +46 -4
  35. meerschaum/api/_oauth2.py +162 -9
  36. meerschaum/api/_tokens.py +102 -0
  37. meerschaum/api/dash/__init__.py +0 -3
  38. meerschaum/api/dash/callbacks/__init__.py +1 -0
  39. meerschaum/api/dash/callbacks/custom.py +4 -3
  40. meerschaum/api/dash/callbacks/dashboard.py +198 -118
  41. meerschaum/api/dash/callbacks/jobs.py +14 -7
  42. meerschaum/api/dash/callbacks/login.py +10 -1
  43. meerschaum/api/dash/callbacks/pipes.py +194 -14
  44. meerschaum/api/dash/callbacks/plugins.py +0 -1
  45. meerschaum/api/dash/callbacks/register.py +10 -3
  46. meerschaum/api/dash/callbacks/settings/password_reset.py +2 -2
  47. meerschaum/api/dash/callbacks/tokens.py +389 -0
  48. meerschaum/api/dash/components.py +36 -15
  49. meerschaum/api/dash/jobs.py +1 -1
  50. meerschaum/api/dash/keys.py +35 -93
  51. meerschaum/api/dash/pages/__init__.py +2 -1
  52. meerschaum/api/dash/pages/dashboard.py +1 -20
  53. meerschaum/api/dash/pages/{job.py → jobs.py} +10 -7
  54. meerschaum/api/dash/pages/login.py +2 -2
  55. meerschaum/api/dash/pages/pipes.py +16 -5
  56. meerschaum/api/dash/pages/settings/password_reset.py +1 -1
  57. meerschaum/api/dash/pages/tokens.py +53 -0
  58. meerschaum/api/dash/pipes.py +382 -95
  59. meerschaum/api/dash/sessions.py +12 -0
  60. meerschaum/api/dash/tokens.py +603 -0
  61. meerschaum/api/dash/websockets.py +1 -1
  62. meerschaum/api/dash/webterm.py +18 -6
  63. meerschaum/api/models/__init__.py +23 -3
  64. meerschaum/api/models/_actions.py +22 -0
  65. meerschaum/api/models/_pipes.py +91 -7
  66. meerschaum/api/models/_tokens.py +81 -0
  67. meerschaum/api/resources/static/js/terminado.js +3 -0
  68. meerschaum/api/resources/static/js/xterm-addon-unicode11.js +2 -0
  69. meerschaum/api/resources/templates/termpage.html +13 -0
  70. meerschaum/api/routes/__init__.py +1 -0
  71. meerschaum/api/routes/_actions.py +3 -4
  72. meerschaum/api/routes/_connectors.py +3 -7
  73. meerschaum/api/routes/_jobs.py +26 -35
  74. meerschaum/api/routes/_login.py +120 -15
  75. meerschaum/api/routes/_misc.py +5 -10
  76. meerschaum/api/routes/_pipes.py +178 -143
  77. meerschaum/api/routes/_plugins.py +38 -28
  78. meerschaum/api/routes/_tokens.py +236 -0
  79. meerschaum/api/routes/_users.py +47 -35
  80. meerschaum/api/routes/_version.py +3 -3
  81. meerschaum/api/routes/_webterm.py +3 -3
  82. meerschaum/config/__init__.py +100 -30
  83. meerschaum/config/_default.py +132 -64
  84. meerschaum/config/_edit.py +38 -32
  85. meerschaum/config/_formatting.py +2 -0
  86. meerschaum/config/_patch.py +10 -8
  87. meerschaum/config/_paths.py +133 -13
  88. meerschaum/config/_read_config.py +87 -36
  89. meerschaum/config/_sync.py +6 -3
  90. meerschaum/config/_version.py +1 -1
  91. meerschaum/config/environment.py +262 -0
  92. meerschaum/config/stack/__init__.py +37 -15
  93. meerschaum/config/static.py +18 -0
  94. meerschaum/connectors/_Connector.py +11 -6
  95. meerschaum/connectors/__init__.py +41 -22
  96. meerschaum/connectors/api/_APIConnector.py +34 -6
  97. meerschaum/connectors/api/_actions.py +2 -2
  98. meerschaum/connectors/api/_jobs.py +12 -1
  99. meerschaum/connectors/api/_login.py +33 -7
  100. meerschaum/connectors/api/_misc.py +2 -2
  101. meerschaum/connectors/api/_pipes.py +23 -32
  102. meerschaum/connectors/api/_plugins.py +2 -2
  103. meerschaum/connectors/api/_request.py +1 -1
  104. meerschaum/connectors/api/_tokens.py +146 -0
  105. meerschaum/connectors/api/_users.py +70 -58
  106. meerschaum/connectors/instance/_InstanceConnector.py +83 -0
  107. meerschaum/connectors/instance/__init__.py +10 -0
  108. meerschaum/connectors/instance/_pipes.py +442 -0
  109. meerschaum/connectors/instance/_plugins.py +159 -0
  110. meerschaum/connectors/instance/_tokens.py +317 -0
  111. meerschaum/connectors/instance/_users.py +188 -0
  112. meerschaum/connectors/parse.py +5 -2
  113. meerschaum/connectors/sql/_SQLConnector.py +22 -5
  114. meerschaum/connectors/sql/_cli.py +12 -11
  115. meerschaum/connectors/sql/_create_engine.py +12 -168
  116. meerschaum/connectors/sql/_fetch.py +2 -18
  117. meerschaum/connectors/sql/_pipes.py +295 -278
  118. meerschaum/connectors/sql/_plugins.py +29 -0
  119. meerschaum/connectors/sql/_sql.py +46 -21
  120. meerschaum/connectors/sql/_users.py +36 -2
  121. meerschaum/connectors/sql/tables/__init__.py +254 -122
  122. meerschaum/connectors/valkey/_ValkeyConnector.py +5 -7
  123. meerschaum/connectors/valkey/_pipes.py +60 -31
  124. meerschaum/connectors/valkey/_plugins.py +2 -26
  125. meerschaum/core/Pipe/__init__.py +115 -85
  126. meerschaum/core/Pipe/_attributes.py +425 -124
  127. meerschaum/core/Pipe/_bootstrap.py +54 -24
  128. meerschaum/core/Pipe/_cache.py +555 -0
  129. meerschaum/core/Pipe/_clear.py +0 -11
  130. meerschaum/core/Pipe/_data.py +96 -68
  131. meerschaum/core/Pipe/_deduplicate.py +0 -13
  132. meerschaum/core/Pipe/_delete.py +12 -21
  133. meerschaum/core/Pipe/_drop.py +11 -23
  134. meerschaum/core/Pipe/_dtypes.py +49 -19
  135. meerschaum/core/Pipe/_edit.py +14 -4
  136. meerschaum/core/Pipe/_fetch.py +1 -1
  137. meerschaum/core/Pipe/_index.py +8 -14
  138. meerschaum/core/Pipe/_show.py +5 -5
  139. meerschaum/core/Pipe/_sync.py +123 -204
  140. meerschaum/core/Pipe/_verify.py +4 -4
  141. meerschaum/{plugins → core/Plugin}/_Plugin.py +16 -12
  142. meerschaum/core/Plugin/__init__.py +1 -1
  143. meerschaum/core/Token/_Token.py +220 -0
  144. meerschaum/core/Token/__init__.py +12 -0
  145. meerschaum/core/User/_User.py +35 -10
  146. meerschaum/core/User/__init__.py +9 -1
  147. meerschaum/core/__init__.py +1 -0
  148. meerschaum/jobs/_Executor.py +88 -4
  149. meerschaum/jobs/_Job.py +149 -38
  150. meerschaum/jobs/__init__.py +3 -2
  151. meerschaum/jobs/systemd.py +8 -3
  152. meerschaum/models/__init__.py +35 -0
  153. meerschaum/models/pipes.py +247 -0
  154. meerschaum/models/tokens.py +38 -0
  155. meerschaum/models/users.py +26 -0
  156. meerschaum/plugins/__init__.py +301 -88
  157. meerschaum/plugins/bootstrap.py +510 -4
  158. meerschaum/utils/_get_pipes.py +97 -30
  159. meerschaum/utils/daemon/Daemon.py +199 -43
  160. meerschaum/utils/daemon/FileDescriptorInterceptor.py +0 -1
  161. meerschaum/utils/daemon/RotatingFile.py +63 -36
  162. meerschaum/utils/daemon/StdinFile.py +53 -13
  163. meerschaum/utils/daemon/__init__.py +47 -6
  164. meerschaum/utils/daemon/_names.py +6 -3
  165. meerschaum/utils/dataframe.py +479 -81
  166. meerschaum/utils/debug.py +49 -19
  167. meerschaum/utils/dtypes/__init__.py +476 -34
  168. meerschaum/utils/dtypes/sql.py +369 -29
  169. meerschaum/utils/formatting/__init__.py +5 -2
  170. meerschaum/utils/formatting/_jobs.py +1 -1
  171. meerschaum/utils/formatting/_pipes.py +52 -50
  172. meerschaum/utils/formatting/_pprint.py +1 -0
  173. meerschaum/utils/formatting/_shell.py +44 -18
  174. meerschaum/utils/misc.py +268 -186
  175. meerschaum/utils/packages/__init__.py +25 -40
  176. meerschaum/utils/packages/_packages.py +42 -34
  177. meerschaum/utils/pipes.py +213 -0
  178. meerschaum/utils/process.py +2 -2
  179. meerschaum/utils/prompt.py +175 -144
  180. meerschaum/utils/schedule.py +2 -1
  181. meerschaum/utils/sql.py +134 -47
  182. meerschaum/utils/threading.py +42 -0
  183. meerschaum/utils/typing.py +1 -4
  184. meerschaum/utils/venv/_Venv.py +2 -2
  185. meerschaum/utils/venv/__init__.py +7 -7
  186. meerschaum/utils/warnings.py +19 -13
  187. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0.dist-info}/METADATA +94 -96
  188. meerschaum-3.0.0.dist-info/RECORD +289 -0
  189. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0.dist-info}/WHEEL +1 -1
  190. meerschaum-3.0.0.dist-info/licenses/NOTICE +2 -0
  191. meerschaum/api/models/_interfaces.py +0 -15
  192. meerschaum/api/models/_locations.py +0 -15
  193. meerschaum/api/models/_metrics.py +0 -15
  194. meerschaum/config/_environment.py +0 -145
  195. meerschaum/config/static/__init__.py +0 -186
  196. meerschaum-2.9.5.dist-info/RECORD +0 -263
  197. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0.dist-info}/entry_points.txt +0 -0
  198. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0.dist-info}/licenses/LICENSE +0 -0
  199. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0.dist-info}/top_level.txt +0 -0
  200. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0.dist-info}/zip-safe +0 -0
@@ -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)
@@ -14,6 +14,8 @@ import json
14
14
  from meerschaum.config._paths import (
15
15
  GRAFANA_DATASOURCE_PATH,
16
16
  GRAFANA_DASHBOARD_PATH,
17
+ DB_INIT_RESOURCES_PATH,
18
+ DB_CREATE_EXTENSIONS_PATH,
17
19
  ROOT_DIR_PATH,
18
20
  )
19
21
  from meerschaum.config._paths import STACK_COMPOSE_FILENAME, STACK_ENV_FILENAME
@@ -39,7 +41,7 @@ valkey_password = 'MRSM{meerschaum:connectors:valkey:main:password}'
39
41
 
40
42
  env_dict = {
41
43
  'COMPOSE_PROJECT_NAME': 'mrsm',
42
- 'TIMESCALEDB_VERSION': 'latest-pg16',
44
+ 'TIMESCALEDB_VERSION': 'pg17',
43
45
  'POSTGRES_USER': db_user,
44
46
  'POSTGRES_PASSWORD': db_pass,
45
47
  'POSTGRES_DB': db_base,
@@ -54,19 +56,22 @@ env_dict['MEERSCHAUM_API_CONFIG'] = json.dumps(
54
56
  {
55
57
  'meerschaum': 'MRSM{!meerschaum}',
56
58
  'system': 'MRSM{!system}',
59
+ 'api': 'MRSM{!api}',
57
60
  },
58
61
  indent=4,
59
62
  ).replace(
60
63
  '"MRSM{!system}"', 'MRSM{!system}'
61
64
  ).replace(
62
- '"MRSM{!meerschaum}"', 'MRSM{!meerschaum}',
65
+ '"MRSM{!meerschaum}"', 'MRSM{!meerschaum}'
66
+ ).replace(
67
+ '"MRSM{!api}"', 'MRSM{!api}'
63
68
  )
64
69
 
65
70
  volumes = {
66
71
  'api_root': '/meerschaum',
67
- 'meerschaum_db_data': '/var/lib/postgresql/data',
72
+ 'meerschaum_db_data': '/home/postgres/pgdata',
68
73
  'grafana_storage': '/var/lib/grafana',
69
- 'valkey_data': '/bitnami/valkey/data',
74
+ 'valkey_data': '/valkey/data',
70
75
  }
71
76
  networks = {
72
77
  'frontend': None,
@@ -121,7 +126,7 @@ default_docker_compose_config = {
121
126
  'POSTGRES_PASSWORD': '<DOLLAR>POSTGRES_PASSWORD',
122
127
  'ALLOW_IP_RANGE': env_dict['ALLOW_IP_RANGE'],
123
128
  },
124
- 'command': 'postgres -c max_connections=1000 -c shared_buffers=1024MB',
129
+ 'command': 'postgres -c max_connections=1000 -c shared_buffers=1024MB -c max_prepared_transactions=100',
125
130
  'healthcheck': {
126
131
  'test': [
127
132
  'CMD-SHELL', 'pg_isready -d <DOLLAR>POSTGRES_DB -U <DOLLAR>POSTGRES_USER',
@@ -131,13 +136,14 @@ default_docker_compose_config = {
131
136
  'retries': 5
132
137
  },
133
138
  'restart': 'always',
134
- 'image': 'timescale/timescaledb:' + env_dict['TIMESCALEDB_VERSION'],
139
+ 'image': 'timescale/timescaledb-ha:' + env_dict['TIMESCALEDB_VERSION'],
135
140
  'ports': [
136
141
  f'{db_port}:5432',
137
142
  ],
138
143
  'hostname': db_hostname,
139
144
  'volumes': [
140
145
  'meerschaum_db_data:' + volumes['meerschaum_db_data'],
146
+ f'{DB_INIT_RESOURCES_PATH.as_posix()}:/docker-entrypoint-initdb.d:z,ro',
141
147
  ],
142
148
  'shm_size': '1024m',
143
149
  'networks': [
@@ -180,7 +186,7 @@ default_docker_compose_config = {
180
186
  ],
181
187
  },
182
188
  'valkey': {
183
- 'image': 'bitnami/valkey:latest',
189
+ 'image': 'valkey/valkey:latest',
184
190
  'restart': 'always',
185
191
  'environment': {
186
192
  'VALKEY_PASSWORD': '<DOLLAR>VALKEY_PASSWORD',
@@ -224,8 +230,8 @@ default_docker_compose_config = {
224
230
  'volumes': [
225
231
  'grafana_storage' + ':' + volumes['grafana_storage'],
226
232
  ### NOTE: Mount with the 'z' option for SELinux.
227
- f'{GRAFANA_DATASOURCE_PATH.parent}:/etc/grafana/provisioning/datasources:z,ro',
228
- f'{GRAFANA_DASHBOARD_PATH.parent}:/etc/grafana/provisioning/dashboards:z,ro',
233
+ f'{GRAFANA_DATASOURCE_PATH.parent.as_posix()}:/etc/grafana/provisioning/datasources:z,ro',
234
+ f'{GRAFANA_DASHBOARD_PATH.parent.as_posix()}:/etc/grafana/provisioning/dashboards:z,ro',
229
235
  ],
230
236
  'environment': {
231
237
  'GF_SECURITY_ALLOW_EMBEDDING': 'true',
@@ -273,6 +279,21 @@ def _sync_stack_files():
273
279
  substitute = True,
274
280
  )
275
281
 
282
+ _write_initdb()
283
+
284
+ def _write_initdb():
285
+ create_postgis_text = (
286
+ "CREATE EXTENSION IF NOT EXISTS timescaledb;\n"
287
+ "CREATE EXTENSION IF NOT EXISTS postgis;\n"
288
+ "CREATE EXTENSION IF NOT EXISTS timescaledb_toolkit;\n"
289
+ "CREATE EXTENSION IF NOT EXISTS pg_stat_statements;\n"
290
+ )
291
+ if DB_CREATE_EXTENSIONS_PATH.exists():
292
+ return
293
+
294
+ with open(DB_CREATE_EXTENSIONS_PATH, 'w+', encoding='utf-8') as f:
295
+ f.write(create_postgis_text)
296
+
276
297
  NECESSARY_FILES = [STACK_COMPOSE_PATH, GRAFANA_DATASOURCE_PATH, GRAFANA_DASHBOARD_PATH]
277
298
  def get_necessary_files():
278
299
  from meerschaum.config import get_config
@@ -286,19 +307,20 @@ def get_necessary_files():
286
307
 
287
308
 
288
309
  def write_stack(
289
- debug: bool = False
290
- ):
310
+ debug: bool = False
311
+ ):
291
312
  """Write Docker Compose configuration files."""
292
313
  from meerschaum.config._edit import general_write_yaml_config
293
314
  from meerschaum.config._sync import sync_files
294
315
  general_write_yaml_config(get_necessary_files(), debug=debug)
295
316
  return sync_files(['stack'])
296
317
 
318
+
297
319
  def edit_stack(
298
- action: Optional[List[str]] = None,
299
- debug: bool = False,
300
- **kw
301
- ):
320
+ action: Optional[List[str]] = None,
321
+ debug: bool = False,
322
+ **kw
323
+ ):
302
324
  """Open docker-compose.yaml or .env for editing."""
303
325
  from meerschaum.config._edit import general_edit_config
304
326
  if action is None:
@@ -0,0 +1,18 @@
1
+ #! /usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ # vim:fenc=utf-8
4
+
5
+ """
6
+ Alias import for the internal static configuration dictionary.
7
+ """
8
+
9
+ from meerschaum._internal.static import SERVER_ID, STATIC_CONFIG
10
+
11
+ __all__ = ('STATIC_CONFIG',)
12
+
13
+
14
+ def _static_config():
15
+ """
16
+ Alias function for the global `STATIC_CONFIG` dictionary.
17
+ """
18
+ return STATIC_CONFIG
@@ -7,10 +7,12 @@ Define the parent `Connector` class.
7
7
  """
8
8
 
9
9
  from __future__ import annotations
10
+
10
11
  import abc
11
12
  import copy
12
13
  from meerschaum.utils.typing import Iterable, Optional, Any, Union, List, Dict
13
14
 
15
+
14
16
  class InvalidAttributesError(Exception):
15
17
  """
16
18
  Raised when the incorrect attributes are set in the Connector.
@@ -20,6 +22,9 @@ class Connector(metaclass=abc.ABCMeta):
20
22
  """
21
23
  The base connector class to hold connection attributes.
22
24
  """
25
+
26
+ IS_INSTANCE: bool = False
27
+
23
28
  def __init__(
24
29
  self,
25
30
  type: Optional[str] = None,
@@ -70,7 +75,7 @@ class Connector(metaclass=abc.ABCMeta):
70
75
  inherit_default: bool = True,
71
76
  **kw: Any
72
77
  ):
73
- from meerschaum.config.static import STATIC_CONFIG
78
+ from meerschaum._internal.static import STATIC_CONFIG
74
79
  from meerschaum.utils.warnings import error
75
80
 
76
81
  self._attributes = {}
@@ -147,11 +152,10 @@ class Connector(metaclass=abc.ABCMeta):
147
152
  ------
148
153
  An error if any of the required attributes are missing.
149
154
  """
150
- from meerschaum.utils.warnings import error, warn
151
- from meerschaum.utils.debug import dprint
155
+ from meerschaum.utils.warnings import error
152
156
  from meerschaum.utils.misc import items_str
153
157
  if required_attributes is None:
154
- required_attributes = ['label']
158
+ required_attributes = ['type', 'label']
155
159
 
156
160
  missing_attributes = set()
157
161
  for a in required_attributes:
@@ -213,6 +217,8 @@ class Connector(metaclass=abc.ABCMeta):
213
217
  else r'executor$'
214
218
  )
215
219
  _type = re.sub(suffix_regex, '', self.__class__.__name__.lower())
220
+ if not _type or _type.lower() == 'instance':
221
+ raise ValueError("No type could be determined for this connector.")
216
222
  self.__dict__['type'] = _type
217
223
  return _type
218
224
 
@@ -224,8 +230,7 @@ class Connector(metaclass=abc.ABCMeta):
224
230
  """
225
231
  _label = self.__dict__.get('label', None)
226
232
  if _label is None:
227
- from meerschaum.config.static import STATIC_CONFIG
233
+ from meerschaum._internal.static import STATIC_CONFIG
228
234
  _label = STATIC_CONFIG['connectors']['default_label']
229
235
  self.__dict__['label'] = _label
230
236
  return _label
231
-
@@ -14,18 +14,19 @@ For ease of use, you can also import from the root `meerschaum` module:
14
14
  from __future__ import annotations
15
15
 
16
16
  import meerschaum as mrsm
17
- from meerschaum.utils.typing import Any, Union, List, Dict
17
+ from meerschaum.utils.typing import Any, Union, List, Dict, Optional
18
18
  from meerschaum.utils.threading import RLock
19
19
  from meerschaum.utils.warnings import warn
20
20
 
21
21
  from meerschaum.connectors._Connector import Connector, InvalidAttributesError
22
+ from meerschaum.connectors.instance._InstanceConnector import InstanceConnector
22
23
  from meerschaum.connectors.sql._SQLConnector import SQLConnector
23
24
  from meerschaum.connectors.api._APIConnector import APIConnector
24
- from meerschaum.connectors.sql._create_engine import flavor_configs as sql_flavor_configs
25
25
 
26
26
  __all__ = (
27
27
  "make_connector",
28
28
  "Connector",
29
+ "InstanceConnector",
29
30
  "SQLConnector",
30
31
  "APIConnector",
31
32
  "get_connector",
@@ -50,30 +51,16 @@ _locks: Dict[str, RLock] = {
50
51
  'connectors' : RLock(),
51
52
  'types' : RLock(),
52
53
  'custom_types' : RLock(),
54
+ 'plugins_types' : RLock(),
53
55
  '_loaded_plugin_connectors': RLock(),
54
56
  'instance_types' : RLock(),
55
57
  }
56
- attributes: Dict[str, Dict[str, Any]] = {
57
- 'api': {
58
- 'required': [
59
- 'host',
60
- 'username',
61
- 'password',
62
- ],
63
- 'optional': [
64
- 'port',
65
- ],
66
- 'default': {
67
- 'protocol': 'http',
68
- },
69
- },
70
- 'sql': {
71
- 'flavors': sql_flavor_configs,
72
- },
73
- }
58
+
74
59
  ### Fill this with objects only when connectors are first referenced.
75
60
  types: Dict[str, Any] = {}
76
61
  custom_types: set = set()
62
+ plugins_types: Dict[str, List[str]] = {}
63
+ _known_custom_types: set = set()
77
64
  _loaded_plugin_connectors: bool = False
78
65
 
79
66
 
@@ -88,7 +75,6 @@ def get_connector(
88
75
  Return existing connector or create new connection and store for reuse.
89
76
 
90
77
  You can create new connectors if enough parameters are provided for the given type and flavor.
91
-
92
78
 
93
79
  Parameters
94
80
  ----------
@@ -130,7 +116,7 @@ def get_connector(
130
116
  """
131
117
  from meerschaum.connectors.parse import parse_instance_keys
132
118
  from meerschaum.config import get_config
133
- from meerschaum.config.static import STATIC_CONFIG
119
+ from meerschaum._internal.static import STATIC_CONFIG
134
120
  from meerschaum.utils.warnings import warn
135
121
  global _loaded_plugin_connectors
136
122
  if isinstance(type, str) and not label and ':' in type:
@@ -313,16 +299,23 @@ def make_connector(cls, _is_executor: bool = False):
313
299
  >>>
314
300
  """
315
301
  import re
302
+ from meerschaum.plugins import _get_parent_plugin
316
303
  suffix_regex = (
317
304
  r'connector$'
318
305
  if not _is_executor
319
306
  else r'executor$'
320
307
  )
308
+ plugin_name = _get_parent_plugin(2)
321
309
  typ = re.sub(suffix_regex, '', cls.__name__.lower())
322
310
  with _locks['types']:
323
311
  types[typ] = cls
324
312
  with _locks['custom_types']:
325
313
  custom_types.add(typ)
314
+ if plugin_name:
315
+ with _locks['plugins_types']:
316
+ if plugin_name not in plugins_types:
317
+ plugins_types[plugin_name] = []
318
+ plugins_types[plugin_name].append(typ)
326
319
  with _locks['connectors']:
327
320
  if typ not in connectors:
328
321
  connectors[typ] = {}
@@ -353,6 +346,30 @@ def load_plugin_connectors():
353
346
  import_plugins(*to_import)
354
347
 
355
348
 
349
+ def unload_plugin_connectors(
350
+ plugin_names: Optional[List[str]] = None,
351
+ debug: bool = False,
352
+ ) -> None:
353
+ """
354
+ Unload custom connectors added by plugins.
355
+ """
356
+ from meerschaum.plugins import get_plugins_names
357
+ global custom_types, _known_custom_types, types, plugins_types, connectors
358
+
359
+ plugin_names = plugin_names or get_plugins_names()
360
+
361
+ for plugin_name in plugin_names:
362
+ plugin_types = plugins_types.get(plugin_name, [])
363
+ for typ in plugin_types:
364
+ _ = types.pop(typ, None)
365
+ _ = connectors.pop(typ, None)
366
+ if typ in instance_types:
367
+ instance_types.remove(typ)
368
+
369
+ custom_types.clear()
370
+ custom_types.update(_known_custom_types)
371
+
372
+
356
373
  def get_connector_plugin(
357
374
  connector: Connector,
358
375
  ) -> Union[str, None, mrsm.Plugin]:
@@ -389,3 +406,5 @@ def _load_builtin_custom_connectors():
389
406
  """
390
407
  import meerschaum.jobs.systemd
391
408
  import meerschaum.connectors.valkey
409
+ _known_custom_types.add('valkey')
410
+ _known_custom_types.add('systemd')
@@ -10,7 +10,7 @@ from __future__ import annotations
10
10
 
11
11
  from datetime import datetime, timedelta, timezone
12
12
  from meerschaum.utils.typing import Optional, List, Union
13
- from meerschaum.connectors import Connector
13
+ from meerschaum.connectors import InstanceConnector
14
14
  from meerschaum.utils.warnings import warn, error
15
15
  from meerschaum.utils.packages import attempt_import
16
16
 
@@ -18,15 +18,13 @@ required_attributes = {
18
18
  'host',
19
19
  }
20
20
 
21
- class APIConnector(Connector):
21
+ class APIConnector(InstanceConnector):
22
22
  """
23
23
  Connect to a Meerschaum API instance.
24
24
  """
25
25
 
26
- IS_INSTANCE: bool = True
27
26
  IS_THREAD_SAFE: bool = False
28
-
29
- OPTIONAL_ATTRIBUTES: List[str] = ['port']
27
+ OPTIONAL_ATTRIBUTES: List[str] = ['port', 'client_secret', 'client_id', 'api_key']
30
28
 
31
29
  from ._request import (
32
30
  make_request,
@@ -82,6 +80,16 @@ class APIConnector(Connector):
82
80
  get_user_type,
83
81
  get_user_attributes,
84
82
  )
83
+ from ._tokens import (
84
+ register_token,
85
+ get_token_model,
86
+ get_tokens,
87
+ edit_token,
88
+ invalidate_token,
89
+ get_token_scopes,
90
+ token_exists,
91
+ delete_token,
92
+ )
85
93
  from ._uri import from_uri
86
94
  from ._jobs import (
87
95
  get_jobs,
@@ -154,9 +162,15 @@ class APIConnector(Connector):
154
162
  """
155
163
  Return the fully qualified URI.
156
164
  """
165
+ import urllib.parse
157
166
  username = self.__dict__.get('username', None)
158
167
  password = self.__dict__.get('password', None)
168
+ client_id = self.__dict__.get('client_id', None)
169
+ client_secret = self.__dict__.get('client_secret', None)
170
+ api_key = self.__dict__.get('api_key', None)
159
171
  creds = (username + ':' + password + '@') if username and password else ''
172
+ params = {}
173
+ params_str = ('?' + urllib.parse.urlencode(params)) if params else ''
160
174
  return (
161
175
  self.protocol
162
176
  + '://'
@@ -167,9 +181,9 @@ class APIConnector(Connector):
167
181
  if self.__dict__.get('port', None)
168
182
  else ''
169
183
  )
184
+ + params_str
170
185
  )
171
186
 
172
-
173
187
  @property
174
188
  def session(self):
175
189
  if self._session is None:
@@ -206,3 +220,17 @@ class APIConnector(Connector):
206
220
  Return the instance keys to be sent alongside pipe requests.
207
221
  """
208
222
  return self._instance_keys
223
+
224
+ @property
225
+ def login_scheme(self) -> str:
226
+ """
227
+ Return the login scheme to use based on the configured credentials.
228
+ """
229
+ if 'username' in self.__dict__:
230
+ return 'password'
231
+ if 'client_id' in self.__dict__:
232
+ return 'client_credentials'
233
+ elif 'api_key' in self.__dict__:
234
+ return 'api_key'
235
+
236
+ return 'password'