meerschaum 2.2.6__py3-none-any.whl → 2.3.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 (80) hide show
  1. meerschaum/__init__.py +6 -1
  2. meerschaum/__main__.py +9 -9
  3. meerschaum/_internal/arguments/__init__.py +1 -1
  4. meerschaum/_internal/arguments/_parse_arguments.py +72 -6
  5. meerschaum/_internal/arguments/_parser.py +45 -15
  6. meerschaum/_internal/docs/index.py +265 -8
  7. meerschaum/_internal/entry.py +167 -37
  8. meerschaum/_internal/shell/Shell.py +290 -99
  9. meerschaum/_internal/shell/updates.py +175 -0
  10. meerschaum/actions/__init__.py +29 -17
  11. meerschaum/actions/api.py +12 -12
  12. meerschaum/actions/attach.py +113 -0
  13. meerschaum/actions/copy.py +68 -41
  14. meerschaum/actions/delete.py +112 -50
  15. meerschaum/actions/edit.py +3 -3
  16. meerschaum/actions/install.py +40 -32
  17. meerschaum/actions/pause.py +44 -27
  18. meerschaum/actions/register.py +19 -5
  19. meerschaum/actions/restart.py +107 -0
  20. meerschaum/actions/show.py +130 -159
  21. meerschaum/actions/start.py +161 -100
  22. meerschaum/actions/stop.py +78 -42
  23. meerschaum/actions/sync.py +3 -3
  24. meerschaum/actions/upgrade.py +28 -36
  25. meerschaum/api/_events.py +25 -1
  26. meerschaum/api/_oauth2.py +2 -0
  27. meerschaum/api/_websockets.py +2 -2
  28. meerschaum/api/dash/callbacks/jobs.py +36 -44
  29. meerschaum/api/dash/jobs.py +89 -78
  30. meerschaum/api/routes/__init__.py +1 -0
  31. meerschaum/api/routes/_actions.py +148 -17
  32. meerschaum/api/routes/_jobs.py +407 -0
  33. meerschaum/api/routes/_pipes.py +25 -25
  34. meerschaum/config/_default.py +1 -0
  35. meerschaum/config/_formatting.py +1 -0
  36. meerschaum/config/_jobs.py +1 -1
  37. meerschaum/config/_paths.py +11 -0
  38. meerschaum/config/_shell.py +84 -67
  39. meerschaum/config/_version.py +1 -1
  40. meerschaum/config/static/__init__.py +18 -0
  41. meerschaum/connectors/Connector.py +13 -7
  42. meerschaum/connectors/__init__.py +28 -15
  43. meerschaum/connectors/api/APIConnector.py +27 -1
  44. meerschaum/connectors/api/_actions.py +71 -6
  45. meerschaum/connectors/api/_jobs.py +368 -0
  46. meerschaum/connectors/api/_misc.py +1 -1
  47. meerschaum/connectors/api/_pipes.py +85 -84
  48. meerschaum/connectors/api/_request.py +13 -9
  49. meerschaum/connectors/parse.py +27 -15
  50. meerschaum/core/Pipe/_bootstrap.py +16 -8
  51. meerschaum/core/Pipe/_sync.py +3 -0
  52. meerschaum/jobs/_Executor.py +69 -0
  53. meerschaum/jobs/_Job.py +899 -0
  54. meerschaum/jobs/__init__.py +396 -0
  55. meerschaum/jobs/systemd.py +694 -0
  56. meerschaum/plugins/__init__.py +97 -12
  57. meerschaum/utils/daemon/Daemon.py +352 -147
  58. meerschaum/utils/daemon/FileDescriptorInterceptor.py +19 -10
  59. meerschaum/utils/daemon/RotatingFile.py +22 -8
  60. meerschaum/utils/daemon/StdinFile.py +121 -0
  61. meerschaum/utils/daemon/__init__.py +42 -27
  62. meerschaum/utils/daemon/_names.py +15 -13
  63. meerschaum/utils/formatting/__init__.py +83 -37
  64. meerschaum/utils/formatting/_jobs.py +146 -55
  65. meerschaum/utils/formatting/_shell.py +6 -0
  66. meerschaum/utils/misc.py +41 -22
  67. meerschaum/utils/packages/__init__.py +21 -15
  68. meerschaum/utils/packages/_packages.py +9 -6
  69. meerschaum/utils/process.py +9 -9
  70. meerschaum/utils/prompt.py +20 -7
  71. meerschaum/utils/schedule.py +21 -15
  72. meerschaum/utils/venv/__init__.py +2 -2
  73. {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/METADATA +22 -25
  74. {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/RECORD +80 -70
  75. {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/WHEEL +1 -1
  76. {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/LICENSE +0 -0
  77. {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/NOTICE +0 -0
  78. {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/entry_points.txt +0 -0
  79. {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/top_level.txt +0 -0
  80. {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/zip-safe +0 -0
@@ -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
+ )
@@ -11,10 +11,20 @@ from meerschaum.utils.typing import Callable, Any, Optional, Union, List, Dict,
11
11
  from meerschaum.utils.packages import get_modules_from_package
12
12
  _custom_actions = []
13
13
 
14
+ __all__ = (
15
+ 'get_action',
16
+ 'get_subactions',
17
+ 'make_action',
18
+ 'pre_sync_hook',
19
+ 'post_sync_hook',
20
+ 'get_main_action_name',
21
+ 'get_completer',
22
+ )
23
+
14
24
  def get_subactions(
15
- action: Union[str, List[str]],
16
- _actions: Optional[Dict[str, Callable[[Any], Any]]] = None,
17
- ) -> Dict[str, Callable[[Any], Any]]:
25
+ action: Union[str, List[str]],
26
+ _actions: Optional[Dict[str, Callable[[Any], Any]]] = None,
27
+ ) -> Dict[str, Callable[[Any], Any]]:
18
28
  """
19
29
  Return a dictionary of an action's sub-action functions.
20
30
 
@@ -52,9 +62,9 @@ def get_subactions(
52
62
 
53
63
 
54
64
  def get_action(
55
- action: Union[str, List[str]],
56
- _actions: Optional[Dict[str, Callable[[Any], Any]]] = None,
57
- ) -> Union[Callable[[Any], Any], None]:
65
+ action: Union[str, List[str]],
66
+ _actions: Optional[Dict[str, Callable[[Any], Any]]] = None,
67
+ ) -> Union[Callable[[Any], Any], None]:
58
68
  """
59
69
  Return a function corresponding to the given action list.
60
70
  This may be a custom action with an underscore, in which case, allow for underscores.
@@ -92,6 +102,8 @@ def get_action(
92
102
  if action[0] in _actions:
93
103
  subactions = get_subactions([action[0]], _actions=_actions)
94
104
  if action[1] not in subactions:
105
+ if (action[1] + 's') in subactions:
106
+ return subactions[action[1] + 's']
95
107
  return _actions[action[0]]
96
108
  return subactions[action[1]]
97
109
 
@@ -99,9 +111,9 @@ def get_action(
99
111
 
100
112
 
101
113
  def get_main_action_name(
102
- action: Union[List[str], str],
103
- _actions: Optional[Dict[str, Callable[[Any], SuccessTuple]]] = None,
104
- ) -> Union[str, None]:
114
+ action: Union[List[str], str],
115
+ _actions: Optional[Dict[str, Callable[[Any], SuccessTuple]]] = None,
116
+ ) -> Union[str, None]:
105
117
  """
106
118
  Given an action list, return the name of the main function.
107
119
  For subactions, this will return the root function.
@@ -138,9 +150,9 @@ def get_main_action_name(
138
150
 
139
151
 
140
152
  def get_completer(
141
- action: Union[List[str], str],
142
- _actions: Optional[Dict[str, Callable[[Any], SuccessTuple]]] = None,
143
- ) -> Union[
153
+ action: Union[List[str], str],
154
+ _actions: Optional[Dict[str, Callable[[Any], SuccessTuple]]] = None,
155
+ ) -> Union[
144
156
  Callable[['meerschaum._internal.shell.Shell', str, str, int, int], List[str]], None
145
157
  ]:
146
158
  """Search for a custom completer function for an action."""
@@ -177,10 +189,10 @@ def get_completer(
177
189
 
178
190
 
179
191
  def choose_subaction(
180
- action: Optional[List[str]] = None,
181
- options: Optional[Dict[str, Any]] = None,
182
- **kw
183
- ) -> SuccessTuple:
192
+ action: Optional[List[str]] = None,
193
+ options: Optional[Dict[str, Any]] = None,
194
+ **kw
195
+ ) -> SuccessTuple:
184
196
  """
185
197
  Given a dictionary of options and the standard Meerschaum actions list,
186
198
  check if choice is valid and execute chosen function, else show available
@@ -245,7 +257,7 @@ def _get_subaction_names(action: str, globs: dict = None) -> List[str]:
245
257
  return subactions
246
258
 
247
259
 
248
- def choices_docstring(action: str, globs : Optional[Dict[str, Any]] = None) -> str:
260
+ def choices_docstring(action: str, globs: Optional[Dict[str, Any]] = None) -> str:
249
261
  """
250
262
  Append the an action's available options to the module docstring.
251
263
  This function is to be placed at the bottom of each action module.
meerschaum/actions/api.py CHANGED
@@ -10,12 +10,12 @@ import os
10
10
  from meerschaum.utils.typing import SuccessTuple, Optional, List, Any
11
11
 
12
12
  def api(
13
- action: Optional[List[str]] = None,
14
- sysargs: Optional[List[str]] = None,
15
- debug: bool = False,
16
- mrsm_instance: Optional[str] = None,
17
- **kw: Any
18
- ) -> SuccessTuple:
13
+ action: Optional[List[str]] = None,
14
+ sysargs: Optional[List[str]] = None,
15
+ debug: bool = False,
16
+ mrsm_instance: Optional[str] = None,
17
+ **kw: Any
18
+ ) -> SuccessTuple:
19
19
  """
20
20
  Send commands to a Meerschaum WebAPI instance.
21
21
 
@@ -37,7 +37,8 @@ def api(
37
37
  """
38
38
  from meerschaum.utils.warnings import warn, info
39
39
  from meerschaum.utils.formatting import print_tuple
40
- from meerschaum.utils.packages import attempt_import
40
+ from meerschaum._internal.arguments._parse_arguments import parse_dict_to_sysargs
41
+
41
42
  if action is None:
42
43
  action = []
43
44
  if sysargs is None:
@@ -52,7 +53,6 @@ def api(
52
53
 
53
54
  from meerschaum.config import get_config
54
55
  from meerschaum.connectors import get_connector
55
- requests = attempt_import('requests')
56
56
  if debug:
57
57
  from meerschaum.utils.formatting import pprint
58
58
  api_configs = get_config('meerschaum', 'connectors', 'api', patch=True)
@@ -68,12 +68,13 @@ def api(
68
68
  del action[0]
69
69
  if len(args_to_send) > 1:
70
70
  del args_to_send[0]
71
+
71
72
  kw['action'] = action
72
73
  kw['debug'] = debug
73
74
  kw['sysargs'] = args_to_send
74
75
  kw['yes'] = True
75
76
 
76
- api_conn = get_connector(type='api', label=api_label)
77
+ api_conn = get_connector(f'api:{api_label}')
77
78
 
78
79
  if mrsm_instance is not None and str(mrsm_instance) == str(api_conn):
79
80
  warn(
@@ -83,9 +84,8 @@ def api(
83
84
  elif mrsm_instance is not None:
84
85
  kw['mrsm_instance'] = str(mrsm_instance)
85
86
 
86
- success, message = api_conn.do_action(**kw)
87
- print_tuple((success, message), common_only=True)
88
- msg = f"Action " + ('succeeded' if success else 'failed') + " with message:\n" + str(message)
87
+ sysargs = parse_dict_to_sysargs(kw)
88
+ success, message = api_conn.do_action(sysargs)
89
89
  return success, message
90
90
 
91
91
  def _api_start(
@@ -0,0 +1,113 @@
1
+ #! /usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # vim:fenc=utf-8
4
+
5
+ """
6
+ Attach to running jobs.
7
+ """
8
+
9
+ import meerschaum as mrsm
10
+ from meerschaum.utils.typing import Optional, List, Any, SuccessTuple
11
+
12
+ def attach(
13
+ action: Optional[List[str]] = None,
14
+ executor_keys: Optional[str] = None,
15
+ **kwargs: Any
16
+ ) -> SuccessTuple:
17
+ """
18
+ Attach to a job, and prompt the user when blocking on input.
19
+ """
20
+ from meerschaum.actions import choose_subaction
21
+ attach_options = {
22
+ 'jobs': _attach_jobs,
23
+ 'logs': _attach_logs,
24
+ }
25
+ return choose_subaction(action, attach_options, executor_keys=executor_keys, **kwargs)
26
+
27
+
28
+ def _complete_attach(
29
+ action: Optional[List[str]] = None,
30
+ **kw: Any
31
+ ) -> List[str]:
32
+ """
33
+ Override the default Meerschaum `complete_` function.
34
+ """
35
+ from meerschaum.actions.delete import _complete_delete_jobs
36
+
37
+ if action is None:
38
+ action = []
39
+
40
+ options = {
41
+ 'job': _complete_delete_jobs,
42
+ 'jobs': _complete_delete_jobs,
43
+ 'log': _complete_delete_jobs,
44
+ 'logs': _complete_delete_jobs,
45
+ }
46
+
47
+ if (
48
+ len(action) > 0 and action[0] in options
49
+ and kw.get('line', '').split(' ')[-1] != action[0]
50
+ ):
51
+ sub = action[0]
52
+ del action[0]
53
+ return options[sub](action=action, **kw)
54
+
55
+ from meerschaum._internal.shell import default_action_completer
56
+ return default_action_completer(action=(['attach'] + action), **kw)
57
+
58
+
59
+ def _attach_jobs(
60
+ action: Optional[List[str]] = None,
61
+ name: Optional[str] = None,
62
+ executor_keys: Optional[str] = None,
63
+ **kwargs: Any
64
+ ) -> SuccessTuple:
65
+ """
66
+ Attach to a job, and prompt the user when blocking on input.
67
+ """
68
+ action = action or []
69
+ if not action and not name:
70
+ return False, "Provide the name of the job to attach to."
71
+
72
+ name = name or action[0]
73
+ job = mrsm.Job(name, executor_keys=executor_keys)
74
+ other_executor_keys = 'systemd' if executor_keys in (None, 'local') else 'local'
75
+ if not job.exists():
76
+ other_job = mrsm.Job(name, executor_keys=other_executor_keys)
77
+ if not other_job.exists():
78
+ return False, f"Job '{job.name}' does not exist."
79
+
80
+ job = other_job
81
+
82
+ success, message = True, "Success"
83
+
84
+ def _capture_result(result: SuccessTuple):
85
+ nonlocal success, message
86
+ success, message = result
87
+
88
+ try:
89
+ job.monitor_logs(
90
+ stop_callback_function=_capture_result,
91
+ accept_input=True,
92
+ stop_on_exit=True,
93
+ strip_timestamps=True,
94
+ )
95
+ except KeyboardInterrupt:
96
+ pass
97
+
98
+ return success, message
99
+
100
+
101
+ def _attach_logs(*args, **kwargs) -> SuccessTuple:
102
+ """
103
+ Attach to jobs' logs.
104
+ """
105
+ from meerschaum.actions.show import _show_logs
106
+ return _show_logs(*args, **kwargs)
107
+
108
+
109
+ ### NOTE: This must be the final statement of the module.
110
+ ### Any subactions added below these lines will not
111
+ ### be added to the `help` docstring.
112
+ from meerschaum.actions import choices_docstring as _choices_docstring
113
+ attach.__doc__ += _choices_docstring('attach')
@@ -10,9 +10,9 @@ from __future__ import annotations
10
10
  from meerschaum.utils.typing import Union, Any, Sequence, SuccessTuple, Optional, Tuple, List
11
11
 
12
12
  def copy(
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
  Duplicate connectors or pipes.
18
18
 
@@ -32,17 +32,16 @@ def copy(
32
32
 
33
33
 
34
34
  def _complete_copy(
35
- action : Optional[List[str]] = None,
36
- **kw : Any
37
- ) -> List[str]:
35
+ action: Optional[List[str]] = None,
36
+ **kw: Any
37
+ ) -> List[str]:
38
38
  """
39
39
  Override the default Meerschaum `complete_` function.
40
-
41
40
  """
42
- from meerschaum.actions.start import _complete_start_jobs
43
41
  from meerschaum.actions.edit import _complete_edit_config
44
42
  if action is None:
45
43
  action = []
44
+
46
45
  options = {
47
46
  'connector': _complete_copy_connectors,
48
47
  'connectors': _complete_copy_connectors,
@@ -61,15 +60,14 @@ def _complete_copy(
61
60
 
62
61
 
63
62
  def _copy_pipes(
64
- yes: bool = False,
65
- noask: bool = False,
66
- force: bool = False,
67
- debug: bool = False,
68
- **kw
69
- ) -> SuccessTuple:
63
+ yes: bool = False,
64
+ noask: bool = False,
65
+ force: bool = False,
66
+ debug: bool = False,
67
+ **kw
68
+ ) -> SuccessTuple:
70
69
  """
71
70
  Copy pipes' attributes and make new pipes.
72
-
73
71
  """
74
72
  from meerschaum import get_pipes, Pipe
75
73
  from meerschaum.utils.prompt import prompt, yes_no
@@ -132,16 +130,15 @@ def _copy_pipes(
132
130
 
133
131
  return successes > 0, msg
134
132
 
133
+
135
134
  def _copy_connectors(
136
- action: Optional[List[str]] = None,
137
- connector_keys: Optional[List[str]] = None,
138
- nopretty: bool = False,
139
- yes: bool = False,
140
- force: bool = False,
141
- noask: bool = False,
142
- debug: bool = False,
143
- **kw
144
- ) -> SuccessTuple:
135
+ action: Optional[List[str]] = None,
136
+ connector_keys: Optional[List[str]] = None,
137
+ nopretty: bool = False,
138
+ force: bool = False,
139
+ debug: bool = False,
140
+ **kwargs: Any
141
+ ) -> SuccessTuple:
145
142
  """
146
143
  Create a new connector from an existing one.
147
144
 
@@ -153,39 +150,69 @@ def _copy_connectors(
153
150
  from meerschaum.config._edit import write_config
154
151
  from meerschaum.utils.warnings import info, warn
155
152
  from meerschaum.utils.formatting import pprint
153
+ from meerschaum.actions import get_action
156
154
  cf = _config()
157
155
  if action is None:
158
156
  action = []
159
157
  if connector_keys is None:
160
158
  connector_keys = []
161
159
 
162
- _keys = list(set(action + connector_keys))
160
+ _keys = (action or []) + connector_keys
163
161
 
164
162
  if not _keys:
165
163
  return False, "No connectors to copy."
166
164
 
167
- for ck in _keys:
168
- try:
169
- conn = parse_connector_keys(ck)
170
- except Exception as e:
171
- warn(f"Unable to parse connector '{ck}'. Skipping...", stack=False)
172
- continue
165
+ if len(_keys) < 1 or len(_keys) > 2:
166
+ return False, "Provide one set of connector keys."
173
167
 
174
- attrs = get_config('meerschaum', 'connectors', conn.type, conn.label)
175
- pprint(attrs, nopretty=nopretty)
168
+ ck = _keys[0]
176
169
 
177
- asking = True
178
- # while asking:
179
- # new_ck = prompt("Please enter a new label for the new connector ():")
170
+ try:
171
+ conn = parse_connector_keys(ck)
172
+ except Exception as e:
173
+ return False, f"Unable to parse connector '{ck}'."
180
174
 
175
+ if len(_keys) == 2:
176
+ new_ck = _keys[1] if ':' in _keys[1] else None
177
+ new_label = _keys[1].split(':')[-1]
178
+ else:
179
+ new_ck = None
180
+ new_label = None
181
+
182
+ try:
183
+ if new_label is None:
184
+ new_label = prompt(f"Enter a label for the new '{conn.type}' connector:")
185
+ except KeyboardInterrupt:
186
+ return False, "Nothing was copied."
187
+
188
+ if new_ck is None:
189
+ new_ck = f"{conn.type}:{new_label}"
190
+
191
+ info(f"Registering connector '{new_ck}' from '{ck}'...")
192
+
193
+ attrs = get_config('meerschaum', 'connectors', conn.type, conn.label)
194
+ pprint(attrs, nopretty=nopretty)
195
+ if not force and not yes_no(
196
+ f"Register connector '{new_ck}' with the above attributes?",
197
+ default='n',
198
+ **kwargs
199
+ ):
200
+ return False, "Nothing was copied."
201
+
202
+ register_connector = get_action(['register', 'connector'])
203
+ register_success, register_msg = register_connector(
204
+ [new_ck],
205
+ params=attrs,
206
+ **kwargs
207
+ )
208
+ return register_success, register_msg
181
209
 
182
- return False, "Not implemented."
183
210
 
184
211
  def _complete_copy_connectors(
185
- action : Optional[List[str]] = None,
186
- line : str = '',
187
- **kw : Any
188
- ) -> List[str]:
212
+ action: Optional[List[str]] = None,
213
+ line: str = '',
214
+ **kw: Any
215
+ ) -> List[str]:
189
216
  from meerschaum.config import get_config
190
217
  from meerschaum.utils.misc import get_connector_labels
191
218
  types = list(get_config('meerschaum', 'connectors').keys())