meerschaum 2.2.6__py3-none-any.whl → 2.2.7__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 (33) hide show
  1. meerschaum/__main__.py +10 -5
  2. meerschaum/_internal/entry.py +13 -13
  3. meerschaum/_internal/shell/Shell.py +26 -22
  4. meerschaum/_internal/shell/updates.py +175 -0
  5. meerschaum/actions/register.py +19 -5
  6. meerschaum/actions/sync.py +3 -3
  7. meerschaum/actions/upgrade.py +28 -36
  8. meerschaum/api/routes/_pipes.py +20 -20
  9. meerschaum/config/_formatting.py +1 -0
  10. meerschaum/config/_paths.py +4 -0
  11. meerschaum/config/_shell.py +78 -66
  12. meerschaum/config/_version.py +1 -1
  13. meerschaum/config/static/__init__.py +1 -0
  14. meerschaum/connectors/api/_misc.py +1 -1
  15. meerschaum/connectors/api/_request.py +13 -9
  16. meerschaum/core/Pipe/_sync.py +3 -0
  17. meerschaum/utils/daemon/Daemon.py +88 -129
  18. meerschaum/utils/daemon/FileDescriptorInterceptor.py +14 -5
  19. meerschaum/utils/daemon/RotatingFile.py +8 -1
  20. meerschaum/utils/daemon/__init__.py +28 -21
  21. meerschaum/utils/formatting/__init__.py +81 -36
  22. meerschaum/utils/formatting/_jobs.py +47 -9
  23. meerschaum/utils/packages/__init__.py +21 -15
  24. meerschaum/utils/prompt.py +5 -0
  25. meerschaum/utils/schedule.py +21 -15
  26. {meerschaum-2.2.6.dist-info → meerschaum-2.2.7.dist-info}/METADATA +1 -1
  27. {meerschaum-2.2.6.dist-info → meerschaum-2.2.7.dist-info}/RECORD +33 -32
  28. {meerschaum-2.2.6.dist-info → meerschaum-2.2.7.dist-info}/LICENSE +0 -0
  29. {meerschaum-2.2.6.dist-info → meerschaum-2.2.7.dist-info}/NOTICE +0 -0
  30. {meerschaum-2.2.6.dist-info → meerschaum-2.2.7.dist-info}/WHEEL +0 -0
  31. {meerschaum-2.2.6.dist-info → meerschaum-2.2.7.dist-info}/entry_points.txt +0 -0
  32. {meerschaum-2.2.6.dist-info → meerschaum-2.2.7.dist-info}/top_level.txt +0 -0
  33. {meerschaum-2.2.6.dist-info → meerschaum-2.2.7.dist-info}/zip-safe +0 -0
meerschaum/__main__.py CHANGED
@@ -19,9 +19,15 @@ See the License for the specific language governing permissions and
19
19
  limitations under the License.
20
20
  """
21
21
 
22
- import sys, os, copy
22
+ import sys
23
+ import os
24
+ import copy
23
25
 
24
- def main(sysargs: list = None) -> None:
26
+ from meerschaum.utils.typing import List, Optional
27
+ from meerschaum.utils.formatting import print_tuple as _print_tuple
28
+
29
+
30
+ def main(sysargs: Optional[List[str]] = None) -> None:
25
31
  """Main CLI entry point."""
26
32
  if sysargs is None:
27
33
  sysargs = copy.deepcopy(sys.argv[1:])
@@ -41,7 +47,7 @@ def main(sysargs: list = None) -> None:
41
47
 
42
48
  if ('-d' in sysargs or '--daemon' in sysargs) and ('stack' not in sysargs):
43
49
  from meerschaum.utils.daemon import daemon_entry
44
- daemon_entry(sysargs)
50
+ _print_tuple(daemon_entry(sysargs), upper_padding=1)
45
51
  return _exit(old_cwd=old_cwd)
46
52
 
47
53
  from meerschaum._internal.entry import entry, get_shell
@@ -57,8 +63,7 @@ def main(sysargs: list = None) -> None:
57
63
  return_tuple = entry(sysargs)
58
64
  rc = 0
59
65
  if isinstance(return_tuple, tuple) and '--nopretty' not in sysargs:
60
- from meerschaum.utils.formatting import print_tuple
61
- print_tuple(return_tuple, upper_padding=1)
66
+ _print_tuple(return_tuple, upper_padding=1)
62
67
  rc = 0 if (return_tuple[0] is True) else 1
63
68
 
64
69
  return _exit(rc, old_cwd=old_cwd)
@@ -8,20 +8,15 @@ The entry point for launching Meerschaum actions.
8
8
  """
9
9
 
10
10
  from __future__ import annotations
11
- from meerschaum.utils.typing import SuccessTuple, List, Optional, Dict
11
+ from meerschaum.utils.typing import SuccessTuple, List, Optional, Dict, Callable, Any
12
12
 
13
13
  def entry(sysargs: Optional[List[str]] = None) -> SuccessTuple:
14
- """Parse arguments and launch a Meerschaum action.
15
- The `action` list removes the first element.
16
-
17
- Examples of action:
18
- 'show actions' -> ['actions']
19
- 'show' -> []
14
+ """
15
+ Parse arguments and launch a Meerschaum action.
20
16
 
21
17
  Returns
22
18
  -------
23
- A `SuccessTuple` indicating success. If `schedule` is provided, this will never return.
24
-
19
+ A `SuccessTuple` indicating success.
25
20
  """
26
21
  from meerschaum._internal.arguments import parse_arguments
27
22
  from meerschaum.config.static import STATIC_CONFIG
@@ -51,9 +46,9 @@ def entry(sysargs: Optional[List[str]] = None) -> SuccessTuple:
51
46
 
52
47
 
53
48
  def entry_with_args(
54
- _actions: Optional[Dict[str, Callable[[Any], SuccessTuple]]] = None,
55
- **kw
56
- ) -> SuccessTuple:
49
+ _actions: Optional[Dict[str, Callable[[Any], SuccessTuple]]] = None,
50
+ **kw
51
+ ) -> SuccessTuple:
57
52
  """Execute a Meerschaum action with keyword arguments.
58
53
  Use `_entry()` for parsing sysargs before executing.
59
54
  """
@@ -96,7 +91,12 @@ def entry_with_args(
96
91
 
97
92
  kw['action'] = remove_leading_action(kw['action'], _actions=_actions)
98
93
 
99
- do_action = functools.partial(_do_action_wrapper, action_function, plugin_name, **kw)
94
+ do_action = functools.partial(
95
+ _do_action_wrapper,
96
+ action_function,
97
+ plugin_name,
98
+ **kw
99
+ )
100
100
  if kw.get('schedule', None) and not skip_schedule:
101
101
  from meerschaum.utils.schedule import schedule_function
102
102
  from meerschaum.utils.misc import interval_str
@@ -95,8 +95,8 @@ def _insert_shell_actions(
95
95
  setattr(_shell_class, 'complete_' + a, completer)
96
96
 
97
97
  def _completer_wrapper(
98
- target: Callable[[Any], List[str]]
99
- ) -> Callable[['meerschaum._internal.shell.Shell', str, str, int, int], Any]:
98
+ target: Callable[[Any], List[str]]
99
+ ) -> Callable[['meerschaum._internal.shell.Shell', str, str, int, int], Any]:
100
100
  """
101
101
  Wrapper for `complete_` functions so they can instead use Meerschaum arguments.
102
102
  """
@@ -125,13 +125,13 @@ def _completer_wrapper(
125
125
 
126
126
 
127
127
  def default_action_completer(
128
- text: Optional[str] = None,
129
- line: Optional[str] = None,
130
- begin_index: Optional[int] = None,
131
- end_index: Optional[int] = None,
132
- action: Optional[List[str]] = None,
133
- **kw: Any
134
- ) -> List[str]:
128
+ text: Optional[str] = None,
129
+ line: Optional[str] = None,
130
+ begin_index: Optional[int] = None,
131
+ end_index: Optional[int] = None,
132
+ action: Optional[List[str]] = None,
133
+ **kw: Any
134
+ ) -> List[str]:
135
135
  """
136
136
  Search for subactions by default. This may be overridden by each action.
137
137
  """
@@ -206,13 +206,11 @@ def get_shell_intro(with_color: bool = True) -> str:
206
206
  """
207
207
  from meerschaum.utils.formatting import CHARSET, ANSI, colored
208
208
  intro = get_config('shell', CHARSET, 'intro', patch=patch)
209
- intro += '\n' + ''.join(
210
- [' '
211
- for i in range(
212
- string_width(intro) - len('v' + version)
213
- )
214
- ]
215
- ) + 'v' + version
209
+ intro += (
210
+ '\n'
211
+ + (' ' * (string_width(intro) - len('v' + version)))
212
+ + f'v{version}'
213
+ )
216
214
 
217
215
  if not with_color or not ANSI:
218
216
  return intro
@@ -225,10 +223,10 @@ def get_shell_intro(with_color: bool = True) -> str:
225
223
 
226
224
  class Shell(cmd.Cmd):
227
225
  def __init__(
228
- self,
229
- actions: Optional[Dict[str, Any]] = None,
230
- sysargs: Optional[List[str]] = None
231
- ):
226
+ self,
227
+ actions: Optional[Dict[str, Any]] = None,
228
+ sysargs: Optional[List[str]] = None
229
+ ):
232
230
  """
233
231
  Customize the CLI from configuration
234
232
  """
@@ -297,19 +295,25 @@ class Shell(cmd.Cmd):
297
295
  except Exception as e:
298
296
  pass
299
297
 
298
+ ### Finally, spawn the version update thread.
299
+ from meerschaum._internal.shell.updates import run_version_check_thread
300
+ self._update_thread = run_version_check_thread(debug=shell_attrs.get('debug', False))
301
+
302
+
300
303
  def load_config(self, instance: Optional[str] = None):
301
304
  """
302
305
  Set attributes from the shell configuration.
303
306
  """
304
307
  from meerschaum.utils.misc import remove_ansi
305
308
  from meerschaum.utils.formatting import CHARSET, ANSI, colored
306
-
309
+ from meerschaum._internal.shell.updates import get_update_message
310
+
307
311
  if shell_attrs.get('intro', None) != '':
308
312
  self.intro = (
309
313
  get_shell_intro(with_color=False)
310
314
  if shell_attrs.get('intro', None) != ''
311
315
  else ""
312
- )
316
+ ) + get_update_message()
313
317
 
314
318
  shell_attrs['intro'] = self.intro
315
319
  shell_attrs['_prompt'] = get_config('shell', CHARSET, 'prompt', patch=patch)
@@ -0,0 +1,175 @@
1
+ #! /usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # vim:fenc=utf-8
4
+
5
+ """
6
+ If configured, check `api:mrsm` for announcement messages.
7
+ """
8
+
9
+ import json
10
+ from datetime import datetime, timezone, timedelta
11
+
12
+ import meerschaum as mrsm
13
+ from meerschaum.utils.typing import Union, SuccessTuple, Optional
14
+ from meerschaum.config import get_config
15
+ from meerschaum.utils.formatting import CHARSET, ANSI, colored
16
+ from meerschaum.utils.misc import string_width, remove_ansi
17
+ from meerschaum.config.paths import (
18
+ UPDATES_LOCK_PATH,
19
+ UPDATES_CACHE_PATH,
20
+ )
21
+ from meerschaum.utils.threading import Thread
22
+
23
+
24
+ def cache_remote_version(debug: bool = False) -> SuccessTuple:
25
+ """
26
+ Fetch and cache the latest version if available.
27
+ """
28
+ allow_update_check = get_config('shell', 'updates', 'check_remote')
29
+ if not allow_update_check:
30
+ return True, "Update checks are disabled."
31
+
32
+ refresh_minutes = get_config('shell', 'updates', 'refresh_minutes')
33
+ update_delta = timedelta(minutes=refresh_minutes)
34
+
35
+ if UPDATES_CACHE_PATH.exists():
36
+ try:
37
+ with open(UPDATES_CACHE_PATH, 'r', encoding='utf8') as f:
38
+ cache_dict = json.load(f)
39
+ except Exception:
40
+ cache_dict = {}
41
+ else:
42
+ cache_dict = {}
43
+
44
+ now = datetime.now(timezone.utc)
45
+ last_check_ts_str = cache_dict.get('last_check_ts')
46
+ last_check_ts = datetime.fromisoformat(last_check_ts_str) if last_check_ts_str else None
47
+
48
+ need_update = (
49
+ last_check_ts_str is None
50
+ or ((now - last_check_ts) >= update_delta)
51
+ )
52
+
53
+ if not need_update:
54
+ return True, "No updates are needed."
55
+
56
+ try:
57
+ conn = mrsm.get_connector('api:mrsm')
58
+ remote_version = conn.get_mrsm_version(debug=debug, timeout=3)
59
+ except Exception:
60
+ remote_version = None
61
+
62
+ if remote_version is None:
63
+ return False, "Could not determine remote version."
64
+
65
+ with open(UPDATES_CACHE_PATH, 'w+', encoding='utf-8') as f:
66
+ json.dump(
67
+ {
68
+ 'last_check_ts': now.isoformat(),
69
+ 'remote_version': remote_version,
70
+ },
71
+ f,
72
+ )
73
+
74
+ return True, "Updated remote version cache."
75
+
76
+
77
+ def run_version_check_thread(debug: bool = False) -> Union[Thread, None]:
78
+ """
79
+ Run the version update check in a separate thread.
80
+ """
81
+ allow_update_check = get_config('shell', 'updates', 'check_remote')
82
+ if not allow_update_check:
83
+ return None
84
+
85
+ thread = Thread(
86
+ target=cache_remote_version,
87
+ daemon=True,
88
+ kwargs={'debug': debug},
89
+ )
90
+ thread.start()
91
+ return thread
92
+
93
+
94
+ _remote_version: Optional[str] = None
95
+ def get_remote_version_from_cache() -> Optional[str]:
96
+ """
97
+ Return the version string from the local cache file.
98
+ """
99
+ global _remote_version
100
+ try:
101
+ with open(UPDATES_CACHE_PATH, 'r', encoding='utf-8') as f:
102
+ cache_dict = json.load(f)
103
+ except Exception:
104
+ return None
105
+
106
+ _remote_version = cache_dict.get('remote_version')
107
+ return _remote_version
108
+
109
+
110
+ _out_of_date: Optional[bool] = None
111
+ def mrsm_out_of_date() -> bool:
112
+ """
113
+ Determine whether to print the upgrade message.
114
+ """
115
+ global _out_of_date
116
+ if _out_of_date is not None:
117
+ return _out_of_date
118
+
119
+ ### NOTE: Remote version is cached asynchronously.
120
+ if not UPDATES_CACHE_PATH.exists():
121
+ return False
122
+
123
+ remote_version_str = get_remote_version_from_cache()
124
+
125
+ packaging_version = mrsm.attempt_import('packaging.version')
126
+ current_version = packaging_version.parse(mrsm.__version__)
127
+ remote_version = packaging_version.parse(remote_version_str)
128
+
129
+ _out_of_date = remote_version > current_version
130
+ return _out_of_date
131
+
132
+
133
+ def get_update_message() -> str:
134
+ """
135
+ Return the formatted message for when the current version is behind the latest release.
136
+ """
137
+ if not mrsm_out_of_date():
138
+ return ''
139
+
140
+ intro = get_config('shell', CHARSET, 'intro')
141
+ update_message = get_config('shell', CHARSET, 'update_message')
142
+ remote_version = get_remote_version_from_cache()
143
+ if not remote_version:
144
+ return ''
145
+
146
+ intro_width = string_width(intro)
147
+ msg_width = string_width(update_message)
148
+ update_left_padding = ' ' * ((intro_width - msg_width) // 2)
149
+
150
+ update_line = (
151
+ colored(
152
+ update_message,
153
+ *get_config('shell', 'ansi', 'update_message', 'color')
154
+ ) if ANSI
155
+ else update_message
156
+ )
157
+ update_instruction = (
158
+ colored("Run ", 'white')
159
+ + colored("upgrade mrsm", 'green')
160
+ + colored(" to install ", 'white')
161
+ + colored(f'v{remote_version}', 'yellow')
162
+ + '.'
163
+ )
164
+ update_instruction_clean = remove_ansi(update_instruction)
165
+ instruction_width = string_width(update_instruction_clean)
166
+ instruction_left_padding = ' ' * ((intro_width - instruction_width) // 2)
167
+
168
+ return (
169
+ '\n\n'
170
+ + update_left_padding
171
+ + update_line
172
+ + '\n'
173
+ + instruction_left_padding
174
+ + update_instruction
175
+ )
@@ -203,14 +203,23 @@ def _register_plugins(
203
203
  successes = {}
204
204
 
205
205
  for name, plugin in plugins_to_register.items():
206
- desc = None
207
206
  plugin.attributes = repo_connector.get_plugin_attributes(plugin, debug=debug)
208
207
  if plugin.attributes is None:
209
208
  plugin.attributes = {}
209
+
210
+ try:
211
+ description_text = plugin.attributes.get('description', '')
212
+ doc_text = plugin.module.__doc__.lstrip().rstrip()
213
+ except Exception:
214
+ description_text = ''
215
+ doc_text = ''
216
+
217
+ desc = description_text or doc_text or ''
218
+
210
219
  question = f"Would you like to add a description to plugin '{name}'?"
211
- if plugin.attributes.get('description', None):
220
+ if desc:
212
221
  info(f"Found existing description for plugin '{plugin}':")
213
- print(plugin.attributes['description'])
222
+ print(desc)
214
223
  question = (
215
224
  "Would you like to overwrite this description?\n"
216
225
  + "To edit the existing text, visit /dash/plugins for this repository."
@@ -220,9 +229,14 @@ def _register_plugins(
220
229
  default='n',
221
230
  yes=yes
222
231
  ):
223
- info('Press (Esc + Enter) to submit the description, (CTRL + C) to cancel.')
232
+ info('Press (Esc + Enter) to submit, (CTRL + C) to cancel.')
224
233
  try:
225
- desc = prompt('', multiline=True, icon=False)
234
+ desc = prompt(
235
+ '',
236
+ multiline=True,
237
+ icon=False,
238
+ default_editable=desc.lstrip().rstrip(),
239
+ )
226
240
  except KeyboardInterrupt:
227
241
  desc = None
228
242
  if desc == '':
@@ -14,9 +14,9 @@ import meerschaum as mrsm
14
14
  from meerschaum.utils.typing import SuccessTuple, Any, List, Optional, Tuple, Union
15
15
 
16
16
  def sync(
17
- action: Optional[List[str]] = None,
18
- **kw: Any
19
- ) -> SuccessTuple:
17
+ action: Optional[List[str]] = None,
18
+ **kw: Any
19
+ ) -> SuccessTuple:
20
20
  """
21
21
  Fetch and sync data for pipes.
22
22
 
@@ -10,9 +10,9 @@ from __future__ import annotations
10
10
  from meerschaum.utils.typing import SuccessTuple, Any, List, Optional, Union
11
11
 
12
12
  def upgrade(
13
- action: Optional[List[str]] = None,
14
- **kw: Any
15
- ) -> SuccessTuple:
13
+ action: Optional[List[str]] = None,
14
+ **kw: Any
15
+ ) -> SuccessTuple:
16
16
  """
17
17
  Upgrade Meerschaum, plugins, or packages.
18
18
 
@@ -34,13 +34,13 @@ def upgrade(
34
34
 
35
35
 
36
36
  def _upgrade_meerschaum(
37
- action: Optional[List[str]] = None,
38
- yes: bool = False,
39
- force: bool = False,
40
- noask: bool = False,
41
- debug: bool = False,
42
- **kw: Any
43
- ) -> SuccessTuple:
37
+ action: Optional[List[str]] = None,
38
+ yes: bool = False,
39
+ force: bool = False,
40
+ noask: bool = False,
41
+ debug: bool = False,
42
+ **kw: Any
43
+ ) -> SuccessTuple:
44
44
  """
45
45
  Upgrade the current Meerschaum instance.
46
46
  Optionally specify dependency groups.
@@ -109,26 +109,21 @@ class NoVenv:
109
109
  pass
110
110
 
111
111
  def _upgrade_packages(
112
- action: Optional[List[str]] = None,
113
- venv: Union[str, None, NoVenv] = NoVenv,
114
- yes: bool = False,
115
- force: bool = False,
116
- noask: bool = False,
117
- debug: bool = False,
118
- **kw: Any
119
- ) -> SuccessTuple:
112
+ action: Optional[List[str]] = None,
113
+ venv: Union[str, None, NoVenv] = NoVenv,
114
+ yes: bool = False,
115
+ force: bool = False,
116
+ noask: bool = False,
117
+ debug: bool = False,
118
+ **kw: Any
119
+ ) -> SuccessTuple:
120
120
  """
121
121
  Upgrade and install dependencies.
122
122
  If provided, upgrade only a dependency group, otherwise default to `full`.
123
123
 
124
124
  Examples:
125
- ```
126
125
  upgrade packages
127
- ```
128
-
129
- ```
130
- upgrade packages docs
131
- ```
126
+ upgrade packages full
132
127
  """
133
128
  from meerschaum.utils.packages import packages, pip_install, get_prerelease_dependencies
134
129
  from meerschaum.utils.warnings import info, warn
@@ -178,14 +173,15 @@ def _upgrade_packages(
178
173
  )
179
174
  return success, msg
180
175
 
176
+
181
177
  def _upgrade_plugins(
182
- action: Optional[List[str]] = None,
183
- yes: bool = False,
184
- force: bool = False,
185
- noask: bool = False,
186
- debug: bool = False,
187
- **kw: Any
188
- ) -> SuccessTuple:
178
+ action: Optional[List[str]] = None,
179
+ yes: bool = False,
180
+ force: bool = False,
181
+ noask: bool = False,
182
+ debug: bool = False,
183
+ **kw: Any
184
+ ) -> SuccessTuple:
189
185
  """
190
186
  Upgrade all installed plugins to the latest versions.
191
187
  If no plugins are specified, attempt to upgrade all,
@@ -193,12 +189,8 @@ def _upgrade_plugins(
193
189
 
194
190
  Examples:
195
191
 
196
- ```
197
192
  upgrade plugins
198
- ```
199
- ```
200
- upgrade plugins testing
201
- ```
193
+ upgrade plugins noaa
202
194
  """
203
195
  from meerschaum.actions import actions
204
196
  from meerschaum.plugins import get_plugins_names
@@ -44,14 +44,14 @@ pd = attempt_import('pandas')
44
44
 
45
45
  @app.post(pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/register', tags=['Pipes'])
46
46
  def register_pipe(
47
- connector_keys: str,
48
- metric_key: str,
49
- location_key: str,
50
- parameters: dict,
51
- curr_user = (
52
- fastapi.Depends(manager) if not no_auth else None
53
- ),
54
- ):
47
+ connector_keys: str,
48
+ metric_key: str,
49
+ location_key: str,
50
+ parameters: dict,
51
+ curr_user = (
52
+ fastapi.Depends(manager) if not no_auth else None
53
+ ),
54
+ ):
55
55
  """
56
56
  Register a new pipe.
57
57
  """
@@ -376,18 +376,18 @@ def sync_pipe(
376
376
 
377
377
  @app.get(pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/data', tags=['Pipes'])
378
378
  def get_pipe_data(
379
- connector_keys: str,
380
- metric_key: str,
381
- location_key: str,
382
- select_columns: Optional[str] = None,
383
- omit_columns: Optional[str] = None,
384
- begin: Union[str, int, None] = None,
385
- end: Union[str, int, None] = None,
386
- params: Optional[str] = None,
387
- curr_user = (
388
- fastapi.Depends(manager) if not no_auth else None
389
- ),
390
- ) -> str:
379
+ connector_keys: str,
380
+ metric_key: str,
381
+ location_key: str,
382
+ select_columns: Optional[str] = None,
383
+ omit_columns: Optional[str] = None,
384
+ begin: Union[str, int, None] = None,
385
+ end: Union[str, int, None] = None,
386
+ params: Optional[str] = None,
387
+ curr_user = (
388
+ fastapi.Depends(manager) if not no_auth else None
389
+ ),
390
+ ) -> str:
391
391
  """
392
392
  Get a pipe's data, applying any filtering.
393
393
 
@@ -36,6 +36,7 @@ default_formatting_config = {
36
36
  'paused' : '🟡',
37
37
  'stopped' : '🔴',
38
38
  'tag' : '🔖',
39
+ 'announcement' : '📢',
39
40
  },
40
41
  'pipes' : {
41
42
  'unicode' : {
@@ -123,6 +123,10 @@ paths = {
123
123
  'PERMANENT_PATCH_DIR_PATH' : ('{ROOT_DIR_PATH}', 'permanent_patch_config'),
124
124
  'INTERNAL_RESOURCES_PATH' : ('{ROOT_DIR_PATH}', '.internal'),
125
125
 
126
+ 'UPDATES_RESOURCES_PATH' : ('{INTERNAL_RESOURCES_PATH}', 'updates'),
127
+ 'UPDATES_CACHE_PATH' : ('{UPDATES_RESOURCES_PATH}', 'cache.json'),
128
+ 'UPDATES_LOCK_PATH' : ('{UPDATES_RESOURCES_PATH}', '.updates.lock'),
129
+
126
130
  'STACK_RESOURCES_PATH' : ('{ROOT_DIR_PATH}', 'stack'),
127
131
  'STACK_COMPOSE_FILENAME' : 'docker-compose.yaml',
128
132
  'STACK_COMPOSE_PATH' : ('{STACK_RESOURCES_PATH}', '{STACK_COMPOSE_FILENAME}'),