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
@@ -16,22 +16,69 @@ from fastapi_login.exceptions import InvalidCredentialsException
16
16
  from fastapi.exceptions import RequestValidationError
17
17
  from starlette.responses import JSONResponse
18
18
 
19
- from meerschaum.api import endpoints, get_api_connector, app, debug, manager, no_auth
19
+ import meerschaum as mrsm
20
+ from meerschaum.api import (
21
+ endpoints,
22
+ get_api_connector,
23
+ get_cache_connector,
24
+ app,
25
+ debug,
26
+ manager,
27
+ no_auth,
28
+ )
20
29
  from meerschaum.core import User
21
30
  from meerschaum._internal.static import STATIC_CONFIG
22
31
  from meerschaum.utils.typing import Dict, Any
23
- from meerschaum.utils.misc import is_uuid
32
+ from meerschaum.utils.misc import is_uuid, is_int
24
33
  from meerschaum.core.User import verify_password
25
34
  from meerschaum.utils.warnings import warn
26
35
  from meerschaum.api._oauth2 import CustomOAuth2PasswordRequestForm
27
36
 
28
37
 
38
+ USER_ID_CACHE_EXPIRES_SECONDS: int = mrsm.get_config('system', 'api', 'cache', 'session_expires_minutes') * 60
39
+ _active_user_ids = {}
40
+
41
+
29
42
  @manager.user_loader()
30
- def load_user(username: str) -> User:
43
+ def load_user_or_token_from_manager(username_or_token_id: str) -> User:
31
44
  """
32
45
  Create the `meerschaum.core.User` object from the username.
33
46
  """
34
- return User(username, instance=get_api_connector())
47
+ cache_conn = get_cache_connector()
48
+ api_conn = get_api_connector()
49
+
50
+ is_token = is_uuid(username_or_token_id)
51
+
52
+ if is_token:
53
+ return api_conn.get_token(username_or_token_id)
54
+
55
+ username = username_or_token_id
56
+
57
+ cached_user_id = (
58
+ _active_user_ids.get(username)
59
+ if cache_conn is None
60
+ else cache_conn.get(f'mrsm:users:{username}:id')
61
+ )
62
+ if isinstance(cached_user_id, str):
63
+ if is_int(cached_user_id):
64
+ cached_user_id = int(cached_user_id)
65
+ elif is_uuid(cached_user_id):
66
+ cached_user_id = uuid.UUID(cached_user_id)
67
+
68
+ user = User(username, instance=api_conn, user_id=cached_user_id)
69
+
70
+ if cached_user_id is not None:
71
+ return user
72
+
73
+ user_id = api_conn.get_user_id(user)
74
+ if user_id is not None:
75
+ user._user_id = user_id
76
+ if cache_conn is not None:
77
+ cache_conn.set(f'mrsm:users:{username}:id', str(user_id), ex=USER_ID_CACHE_EXPIRES_SECONDS)
78
+ else:
79
+ _active_user_ids[username] = user_id
80
+ return user
81
+
35
82
 
36
83
 
37
84
  @app.post(endpoints['login'], tags=['Users'])
@@ -63,7 +110,10 @@ def login(
63
110
  else 'client_credentials'
64
111
  )
65
112
 
113
+ allowed_scopes = []
114
+ type_ = None
66
115
  expires_dt: Union[datetime, None] = None
116
+ sub_id = None
67
117
  if grant_type == 'password':
68
118
  user = User(str(username), str(password), instance=get_api_connector())
69
119
  correct_password = no_auth or verify_password(
@@ -73,6 +123,10 @@ def login(
73
123
  if not correct_password:
74
124
  raise InvalidCredentialsException
75
125
 
126
+ allowed_scopes = user.get_scopes(debug=debug)
127
+ type_ = get_api_connector().get_user_type(user, debug=debug)
128
+ sub_id = username
129
+
76
130
  elif grant_type == 'client_credentials':
77
131
  if not is_uuid(str(client_id)):
78
132
  raise InvalidCredentialsException
@@ -83,15 +137,29 @@ def login(
83
137
  )
84
138
  if not correct_password:
85
139
  raise InvalidCredentialsException
140
+
141
+ allowed_scopes = get_api_connector().get_token_scopes(token_id, debug=debug)
142
+ sub_id = client_id
143
+
86
144
  else:
87
145
  raise InvalidCredentialsException
88
146
 
147
+ requested_scopes = data.scope.split()
148
+ if '*' in allowed_scopes:
149
+ final_scopes = requested_scopes or ['*']
150
+ else:
151
+ final_scopes = [
152
+ s for s in requested_scopes if s in allowed_scopes
153
+ ] if requested_scopes else allowed_scopes
154
+
89
155
  expires_minutes = STATIC_CONFIG['api']['oauth']['token_expires_minutes']
90
156
  expires_delta = timedelta(minutes=expires_minutes)
91
157
  expires_dt = datetime.now(timezone.utc).replace(tzinfo=None) + expires_delta
92
158
  access_token = manager.create_access_token(
93
159
  data={
94
- 'sub': (username if grant_type == 'password' else client_id),
160
+ 'sub': sub_id,
161
+ 'scopes': final_scopes,
162
+ 'type': type_,
95
163
  },
96
164
  expires=expires_delta
97
165
  )
@@ -25,6 +25,7 @@ from meerschaum.api import (
25
25
  _get_pipes,
26
26
  debug,
27
27
  ScopedAuth,
28
+ manager,
28
29
  )
29
30
  from meerschaum.models import (
30
31
  ConnectorKeysModel,
@@ -148,7 +149,7 @@ def delete_pipe(
148
149
  location_key: str,
149
150
  instance_keys: Optional[str] = None,
150
151
  curr_user = fastapi.Security(ScopedAuth(['pipes:delete'])),
151
- ) -> SuccessTuple:
152
+ ) -> mrsm.SuccessTuple:
152
153
  """
153
154
  Delete a Pipe (without dropping its table).
154
155
  """
@@ -187,7 +188,10 @@ async def fetch_pipes_keys(
187
188
  tags=json.loads(tags),
188
189
  params=json.loads(params),
189
190
  )
190
- return keys
191
+ return [
192
+ (keys_tuple[0], keys_tuple[1], keys_tuple[2])
193
+ for keys_tuple in keys
194
+ ]
191
195
 
192
196
 
193
197
  @app.get(pipes_endpoint, tags=['Pipes: Attributes'])
@@ -197,7 +201,6 @@ async def get_pipes(
197
201
  location_keys: str = "",
198
202
  instance_keys: str = "",
199
203
  curr_user=fastapi.Security(ScopedAuth(['pipes:read'])),
200
- debug: bool = False,
201
204
  ) -> PipesWithParametersDictModel:
202
205
  """
203
206
  Get all registered Pipes with metadata, excluding parameters.
@@ -384,7 +387,6 @@ async def sync_pipe(
384
387
  workers: Optional[int] = None,
385
388
  columns: Optional[str] = None,
386
389
  curr_user = fastapi.Security(ScopedAuth(['pipes:write'])),
387
- debug: bool = False,
388
390
  ) -> mrsm.SuccessTuple:
389
391
  """
390
392
  Add data to an existing Pipe.
@@ -8,7 +8,7 @@ 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, endpoints
11
+ from meerschaum.api import app, endpoints, webterm_port
12
12
  from meerschaum.utils.packages import attempt_import
13
13
  from meerschaum.api.dash.sessions import is_session_authenticated, get_username_from_session
14
14
  fastapi, fastapi_responses = attempt_import('fastapi', 'fastapi.responses')
@@ -71,7 +71,7 @@ async def get_webterm(
71
71
 
72
72
  username = get_username_from_session(session_id)
73
73
  async with httpx.AsyncClient() as client:
74
- webterm_url = f"http://localhost:8765/webterm/{username or session_id}"
74
+ webterm_url = f"http://localhost:{webterm_port}/webterm/{username or session_id}"
75
75
  response = await client.get(webterm_url)
76
76
  text = response.text
77
77
  if request.url.scheme == 'https':
@@ -100,7 +100,7 @@ async def webterm_websocket(websocket: WebSocket, session_id: str):
100
100
 
101
101
  username = get_username_from_session(session_id)
102
102
 
103
- ws_url = f"ws://localhost:8765/websocket/{username or session_id}"
103
+ ws_url = f"ws://localhost:{webterm_port}/websocket/{username or session_id}"
104
104
  async with websockets.connect(ws_url) as ws:
105
105
  async def forward_messages():
106
106
  try:
@@ -13,8 +13,9 @@ import os
13
13
  import shutil
14
14
  import sys
15
15
  import copy
16
+ import contextlib
16
17
 
17
- from meerschaum.utils.typing import Any, Dict, Optional
18
+ from meerschaum.utils.typing import Any, Dict, Optional, Union
18
19
  from meerschaum.utils.threading import RLock
19
20
 
20
21
  from meerschaum.config._version import __version__
@@ -43,6 +44,7 @@ __all__ = (
43
44
  'write_config',
44
45
  'edit_config',
45
46
  'set_config',
47
+ 'replace_config',
46
48
  'search_and_substitute_config',
47
49
  'revert_symlinks_config',
48
50
  'get_possible_keys',
@@ -57,17 +59,23 @@ _locks = {'config': RLock()}
57
59
 
58
60
  ### apply config preprocessing (e.g. main to meta)
59
61
  config = {}
62
+ _backup_config = None
63
+ _allow_write_missing: bool = True
64
+
65
+
60
66
  def _config(
61
67
  *keys: str,
62
68
  reload: bool = False,
63
69
  substitute: bool = True,
64
70
  sync_files: bool = True,
71
+ allow_replaced: bool = True,
65
72
  write_missing: bool = True,
66
73
  ) -> Dict[str, Any]:
67
74
  """
68
75
  Read and process the configuration file.
69
76
  """
70
- global config
77
+ global config, _backup_config
78
+
71
79
  if config is None or reload:
72
80
  with _locks['config']:
73
81
  config = {}
@@ -77,12 +85,16 @@ def _config(
77
85
  key_config = read_config(
78
86
  keys = [keys[0]],
79
87
  substitute = substitute,
80
- write_missing = write_missing,
88
+ write_missing = write_missing and _allow_write_missing,
81
89
  )
82
90
  if keys[0] in key_config:
83
91
  config[keys[0]] = key_config[keys[0]]
84
- if sync_files:
92
+ if sync_files and _allow_write_missing:
85
93
  _sync_files(keys=[keys[0] if keys else None])
94
+
95
+ if not allow_replaced:
96
+ return _backup_config if _backup_config is not None else config
97
+
86
98
  return config
87
99
 
88
100
 
@@ -158,7 +170,11 @@ def get_config(
158
170
  dprint(f"Indexing keys: {keys}", color=False)
159
171
 
160
172
  if len(keys) == 0:
161
- _rc = _config(substitute=substitute, sync_files=sync_files, write_missing=write_missing)
173
+ _rc = _config(
174
+ substitute=substitute,
175
+ sync_files=sync_files,
176
+ write_missing=(write_missing and _allow_write_missing),
177
+ )
162
178
  if as_tuple:
163
179
  return True, _rc
164
180
  return _rc
@@ -177,15 +193,15 @@ def get_config(
177
193
  except Exception:
178
194
  import traceback
179
195
  traceback.print_exc()
196
+ _subbed = {keys[0]: config[keys[0]]}
197
+
180
198
  config[keys[0]] = _subbed[keys[0]]
181
199
  if symlinks_key in _subbed:
182
200
  if symlinks_key not in config:
183
201
  config[symlinks_key] = {}
184
- if keys[0] not in config[symlinks_key]:
185
- config[symlinks_key][keys[0]] = {}
186
- config[symlinks_key][keys[0]] = apply_patch_to_config(
187
- _subbed,
188
- {symlinks_key: config[symlinks_key][keys[0]]}
202
+ config[symlinks_key] = apply_patch_to_config(
203
+ _subbed.get(symlinks_key, {}),
204
+ config.get(symlinks_key, {}),
189
205
  )
190
206
 
191
207
  from meerschaum.config._sync import sync_files as _sync_files
@@ -312,13 +328,44 @@ def write_plugin_config(
312
328
  return write_config(cf, **kw)
313
329
 
314
330
 
331
+ @contextlib.contextmanager
332
+ def replace_config(config_: Union[Dict[str, Any], None]):
333
+ """
334
+ Temporarily override the Meerschaum config dictionary.
335
+
336
+ Parameters
337
+ ----------
338
+ config_: Dict[str, Any]
339
+ The new config dictionary to temporarily replace the canonical `config`.
340
+ """
341
+ if config_ is None:
342
+ try:
343
+ yield
344
+ finally:
345
+ return
346
+
347
+ global _backup_config, _allow_write_missing
348
+
349
+ _backup_config = _config()
350
+ _allow_write_missing = False
351
+ set_config(config_)
352
+
353
+ try:
354
+ yield
355
+ finally:
356
+ set_config(_backup_config)
357
+ _allow_write_missing = True
358
+
315
359
  ### This need to be below get_config to avoid a circular import.
316
360
  from meerschaum.config._read_config import read_config
317
361
 
318
362
  ### If environment variable MRSM_CONFIG or MRSM_PATCH is set, patch config before anything else.
319
- from meerschaum.config._environment import apply_environment_patches, apply_environment_uris
320
- apply_environment_uris()
321
- apply_environment_patches()
363
+ from meerschaum.config.environment import (
364
+ apply_environment_patches as _apply_environment_patches,
365
+ apply_environment_uris as _apply_environment_uris,
366
+ )
367
+ _apply_environment_uris()
368
+ _apply_environment_patches()
322
369
 
323
370
 
324
371
  from meerschaum.config._paths import PATCH_DIR_PATH, PERMANENT_PATCH_DIR_PATH
@@ -36,8 +36,7 @@ CONNECTOR_ATTRIBUTES: Dict[str, Dict[str, Any]] = {
36
36
  default_meerschaum_config = {
37
37
  'instance': 'sql:main',
38
38
  'api_instance': 'MRSM{meerschaum:instance}',
39
- 'web_instance': 'MRSM{meerschaum:instance}',
40
- 'default_repository': 'api:mrsm',
39
+ 'repository': 'api:mrsm',
41
40
  'connectors': {
42
41
  'sql': {
43
42
  'default': {},
@@ -51,7 +50,7 @@ default_meerschaum_config = {
51
50
  },
52
51
  'local': {
53
52
  'flavor': 'sqlite',
54
- 'database': SQLITE_DB_PATH.as_posix(),
53
+ 'database': "{SQLITE_DB_PATH}",
55
54
  },
56
55
  'memory': {
57
56
  'flavor': 'sqlite',
@@ -117,75 +116,107 @@ default_system_config = {
117
116
  'connect_args': {},
118
117
  },
119
118
  },
120
-
121
119
  'api': {
122
120
  },
123
121
  },
124
- ### not to be confused with system_config['connectors']['api'], this is the configuration
125
- ### for the API server itself.
126
- 'api': {
127
- 'uvicorn': {
128
- 'app': 'meerschaum.api:app',
129
- 'port': 8000,
130
- 'host': '0.0.0.0',
131
- 'workers': max(int(multiprocessing.cpu_count() / 2), 1),
132
- 'proxy_headers': True,
133
- 'forwarded_allow_ips': '*',
134
- },
135
- 'cache': {
136
- 'connector': 'valkey:main',
137
- 'session_expires_minutes': 43200,
122
+ 'api': 'MRSM{api}',
123
+ 'webterm': 'MRSM{api:webterm}',
124
+ 'cli': {
125
+ 'max_daemons': (multiprocessing.cpu_count() * 3),
126
+ 'refresh_seconds': 0.1,
127
+ 'allowed_prefixes': ['*'],
128
+ 'disallowed_prefixes': [
129
+ 'edit',
130
+ 'start daemon',
131
+ 'start job',
132
+ 'stop daemon',
133
+ 'show daemon',
134
+ 'restart daemon',
135
+ 'reload',
136
+ 'start worker',
137
+ 'start job',
138
+ 'python',
139
+ 'login',
140
+ 'executor',
141
+ 'os ',
142
+ 'sh ',
143
+ 'start api',
144
+ 'start webterm',
145
+ 'stack',
146
+ 'instance',
147
+ 'debug',
148
+ 'bootstrap',
149
+ 'daemon',
150
+ ],
151
+ },
152
+ 'experimental': {
153
+ 'fetch': False,
154
+ 'cache': True,
155
+ 'space': False,
156
+ 'join_fetch': False,
157
+ 'inplace_sync': True,
158
+ 'uv_pip': True,
159
+ 'systemd_healthcheck': False,
160
+ 'valkey_session_cache': True,
161
+ 'cli_daemon': True,
162
+ },
163
+ }
164
+
165
+ default_api_config = {
166
+ 'uvicorn': {
167
+ 'app': 'meerschaum.api:app',
168
+ 'port': 8000,
169
+ 'host': '0.0.0.0',
170
+ 'workers': max(int(multiprocessing.cpu_count() / 2), 1),
171
+ 'proxy_headers': True,
172
+ 'forwarded_allow_ips': '*',
173
+ },
174
+ 'cache': {
175
+ 'connector': 'valkey:main',
176
+ 'session_expires_minutes': 43200,
177
+ },
178
+ 'data': {
179
+ 'max_response_row_limit': 100_000,
180
+ 'chunks': {
181
+ 'ttl_seconds': 1800,
138
182
  },
139
- 'data': {
140
- 'max_response_row_limit': 100_000,
141
- 'chunks': {
142
- 'ttl_seconds': 1800,
143
- },
183
+ },
184
+ 'endpoints': {
185
+ 'docs_in_production': True,
186
+ },
187
+ 'tokens': {
188
+ 'valid_refresh_minutes': 60,
189
+ 'default_expiration_days': 366,
190
+ },
191
+ 'permissions': {
192
+ 'registration': {
193
+ 'users': True,
194
+ 'pipes': True,
195
+ 'plugins': True,
144
196
  },
145
- 'endpoints': {
146
- 'docs_in_production': True,
197
+ 'actions': {
198
+ 'non_admin': True,
147
199
  },
148
- 'tokens': {
149
- 'valid_refresh_minutes': 60,
150
- 'default_expiration_days': 366,
200
+ 'chaining': {
201
+ 'insecure_parent_instance': False,
202
+ 'child_apis': False,
151
203
  },
152
- 'permissions': {
153
- 'registration': {
154
- 'users': True,
155
- 'pipes': True,
156
- 'plugins': True,
157
- },
158
- 'actions': {
159
- 'non_admin': True,
160
- },
161
- 'chaining': {
162
- 'insecure_parent_instance': False,
163
- 'child_apis': False,
164
- },
165
- 'instances': {
166
- 'allow_multiple_instances': True,
167
- 'allowed_instance_keys': ['*']
168
- },
204
+ 'instances': {
205
+ 'allow_multiple_instances': True,
206
+ 'allowed_instance_keys': ['*']
169
207
  },
170
- 'protocol': default_meerschaum_config['connectors']['api']['default']['protocol'],
171
208
  },
209
+ 'protocol': default_meerschaum_config['connectors']['api']['default']['protocol'],
172
210
  'webterm': {
173
211
  'tmux': {
174
212
  'enabled': True,
175
213
  'session_suffix': '_mrsm',
176
214
  },
177
- },
178
- 'experimental': {
179
- 'fetch': False,
180
- 'cache': True,
181
- 'space': False,
182
- 'join_fetch': False,
183
- 'inplace_sync': True,
184
- 'uv_pip': True,
185
- 'systemd_healthcheck': False,
186
- 'valkey_session_cache': True,
215
+ 'host': '127.0.0.1',
216
+ 'port': 8765,
187
217
  },
188
218
  }
219
+
189
220
  default_pipes_config = {
190
221
  'parameters': {
191
222
  'columns': {
@@ -221,15 +252,12 @@ default_pipes_config = {
221
252
  },
222
253
  }
223
254
  default_plugins_config = {}
224
- default_experimental_config = {
225
- 'venv': True,
226
- }
227
-
228
255
 
229
256
  ### build default config dictionary
230
257
  default_config = {}
231
258
  default_config['meerschaum'] = default_meerschaum_config
232
259
  default_config['system'] = default_system_config
260
+ default_config['api'] = default_api_config
233
261
  from meerschaum.config._formatting import default_formatting_config
234
262
  default_config['formatting'] = default_formatting_config
235
263
  from meerschaum.config._shell import default_shell_config
@@ -241,7 +269,7 @@ default_config['jobs'] = default_jobs_config
241
269
  ### add configs from other packages
242
270
  try:
243
271
  import meerschaum.config.stack
244
- except ImportError as e:
272
+ except ImportError:
245
273
  pass
246
274
  finally:
247
275
  from meerschaum.config.stack import default_stack_config
@@ -103,19 +103,23 @@ def write_config(
103
103
  if directory is None:
104
104
  from meerschaum.config._paths import CONFIG_DIR_PATH
105
105
  directory = CONFIG_DIR_PATH
106
+
107
+ from meerschaum.config import _allow_write_missing
106
108
  from meerschaum._internal.static import STATIC_CONFIG
107
109
  from meerschaum.config._default import default_header_comment
108
- from meerschaum.config._patch import apply_patch_to_config
109
- from meerschaum.config._read_config import get_keyfile_path
110
- from meerschaum.utils.debug import dprint
110
+ from meerschaum.config._read_config import get_keyfile_path, revert_symlinks_config
111
111
  from meerschaum.utils.yaml import yaml
112
112
  from meerschaum.utils.misc import filter_keywords
113
- import json, os
113
+ import json
114
+ import os
114
115
  if config_dict is None:
115
116
  from meerschaum.config import _config
116
- cf = _config()
117
+ cf = _config(allow_replaced=False)
117
118
  config_dict = cf
118
119
 
120
+ if not _allow_write_missing:
121
+ return False
122
+
119
123
  default_filetype = STATIC_CONFIG['config']['default_filetype']
120
124
  filetype_dumpers = {
121
125
  'yml' : yaml.dump,
@@ -123,9 +127,7 @@ def write_config(
123
127
  'json' : json.dump,
124
128
  }
125
129
 
126
- symlinks_key = STATIC_CONFIG['config']['symlinks_key']
127
- symlinks = config_dict.pop(symlinks_key) if symlinks_key in config_dict else {}
128
- config_dict = apply_patch_to_config(config_dict, symlinks)
130
+ config_dict = revert_symlinks_config(config_dict)
129
131
 
130
132
  def determine_filetype(k, v):
131
133
  if k == 'meerschaum':
@@ -28,6 +28,8 @@ default_formatting_config = {
28
28
  'connector' : '🔌',
29
29
  'metric' : '📊',
30
30
  'location' : '📍',
31
+ 'locked' : '🔒',
32
+ 'unlocked' : '🔓',
31
33
  'key' : '🔑',
32
34
  'idea' : '💡',
33
35
  'connected' : '🟢',
@@ -9,11 +9,12 @@ Functions for patching the configuration dictionary
9
9
  import sys
10
10
  import copy
11
11
  from meerschaum.utils.typing import Dict, Any
12
- from meerschaum.utils.warnings import warn
12
+ from meerschaum.utils.warnings import warn as _warn
13
13
 
14
14
  def apply_patch_to_config(
15
15
  config: Dict[str, Any],
16
16
  patch: Dict[str, Any],
17
+ warn: bool = False,
17
18
  ) -> Dict[str, Any]:
18
19
  """Patch the config dict with a new dict (cascade patching)."""
19
20
  _base = copy.deepcopy(config) if isinstance(config, dict) else {}
@@ -24,7 +25,8 @@ def apply_patch_to_config(
24
25
  if base is None:
25
26
  return {}
26
27
  if not isinstance(base, dict):
27
- warn(f"Overwriting the value {base} with a dictionary:\n{patch}")
28
+ if warn:
29
+ _warn(f"Overwriting the value {base} with a dictionary:\n{patch}")
28
30
  base = {}
29
31
  for key, value in patch.items():
30
32
  if isinstance(value, dict):