meerschaum.actions
Default actions available to the mrsm CLI.
1#! /usr/bin/env python 2# -*- coding: utf-8 -*- 3# vim:fenc=utf-8 4 5""" 6Default actions available to the mrsm CLI. 7""" 8 9from __future__ import annotations 10from meerschaum.utils.typing import Callable, Any, Optional, Union, List, Dict, SuccessTuple 11from meerschaum.utils.packages import get_modules_from_package 12_custom_actions = [] 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 24def get_subactions( 25 action: Union[str, List[str]], 26 _actions: Optional[Dict[str, Callable[[Any], Any]]] = None, 27) -> Dict[str, Callable[[Any], Any]]: 28 """ 29 Return a dictionary of an action's sub-action functions. 30 31 Examples 32 -------- 33 >>> get_subactions('install').keys() 34 dict_keys(['packages', 'plugins', 'required']) 35 >>> 36 """ 37 import importlib, inspect 38 subactions = {} 39 if isinstance(action, str): 40 action = [action] 41 action_function = get_action(action[0], _actions=_actions) 42 if action_function is None: 43 return subactions 44 try: 45 action_module = importlib.import_module(action_function.__module__) 46 except ImportError: 47 action_module = None 48 if action_module is None: 49 return subactions 50 for name, f in inspect.getmembers(action_module): 51 if not inspect.isfunction(f): 52 continue 53 54 ### Detect subactions which may contain an underscore prefix. 55 if ( 56 name.lstrip('_').startswith(action_function.__name__.lstrip('_') + '_') 57 ): 58 _name = name.replace(action_function.__name__, '') 59 _name = _name.lstrip('_') 60 subactions[_name] = f 61 return subactions 62 63 64def get_action( 65 action: Union[str, List[str]], 66 _actions: Optional[Dict[str, Callable[[Any], Any]]] = None, 67) -> Union[Callable[[Any], Any], None]: 68 """ 69 Return a function corresponding to the given action list. 70 This may be a custom action with an underscore, in which case, allow for underscores. 71 This may also be a subactions, which is handled by `get_subactions()` 72 """ 73 if _actions is None: 74 _actions = actions 75 if isinstance(action, str): 76 action = [action] 77 78 if not any(action): 79 return None 80 81 ### Simple case, e.g. ['show'] 82 if len(action) == 1: 83 if action[0] in _actions: 84 return _actions[action[0]] 85 86 ### e.g. ['foo'] (and no custom action available) 87 return None 88 89 ### Last case: it could be a custom action with an underscore in the name. 90 action_name_with_underscores = '_'.join(action) 91 candidates = [] 92 for action_key, action_function in _actions.items(): 93 if not '_' in action_key: 94 continue 95 if action_name_with_underscores.startswith(action_key): 96 leftovers = action_name_with_underscores.replace(action_key, '') 97 candidates.append((len(leftovers), action_function)) 98 if len(candidates) > 0: 99 return sorted(candidates)[0][1] 100 101 ### Might be dealing with a subaction. 102 if action[0] in _actions: 103 subactions = get_subactions([action[0]], _actions=_actions) 104 if action[1] not in subactions: 105 if (action[1] + 's') in subactions: 106 return subactions[action[1] + 's'] 107 return _actions[action[0]] 108 return subactions[action[1]] 109 110 return None 111 112 113def get_main_action_name( 114 action: Union[List[str], str], 115 _actions: Optional[Dict[str, Callable[[Any], SuccessTuple]]] = None, 116) -> Union[str, None]: 117 """ 118 Given an action list, return the name of the main function. 119 For subactions, this will return the root function. 120 If no action function can be found, return `None`. 121 122 Parameters 123 ---------- 124 action: Union[List[str], str] 125 The standard action list. 126 127 Examples 128 -------- 129 >>> get_main_action_name(['show', 'pipes']) 130 'show' 131 >>> 132 """ 133 if _actions is None: 134 _actions = actions 135 action_function = get_action(action, _actions=_actions) 136 if action_function is None: 137 return None 138 clean_name = action_function.__name__.lstrip('_') 139 if clean_name in _actions: 140 return clean_name 141 words = clean_name.split('_') 142 first_word = words[0] 143 if first_word in _actions: 144 return first_word 145 ### We might be dealing with shell `do_` functions. 146 second_word = words[1] if len(words) > 1 else None 147 if second_word and second_word in _actions: 148 return second_word 149 return None 150 151 152def get_completer( 153 action: Union[List[str], str], 154 _actions: Optional[Dict[str, Callable[[Any], SuccessTuple]]] = None, 155) -> Union[ 156 Callable[['meerschaum._internal.shell.Shell', str, str, int, int], List[str]], None 157 ]: 158 """Search for a custom completer function for an action.""" 159 import importlib, inspect 160 if isinstance(action, str): 161 action = [action] 162 action_function = get_action(action) 163 if action_function is None: 164 return None 165 try: 166 action_module = importlib.import_module(action_function.__module__) 167 except ImportError: 168 action_module = None 169 if action_module is None: 170 return None 171 candidates = [] 172 for name, f in inspect.getmembers(action_module): 173 if not inspect.isfunction(f): 174 continue 175 name_lstrip = name.lstrip('_') 176 if not name_lstrip.startswith('complete_'): 177 continue 178 if name_lstrip == 'complete_' + action_function.__name__: 179 candidates.append((name_lstrip, f)) 180 continue 181 if name_lstrip == 'complete_' + action_function.__name__.split('_')[0]: 182 candidates.append((name_lstrip, f)) 183 if len(candidates) == 1: 184 return candidates[0][1] 185 if len(candidates) > 1: 186 return sorted(candidates)[-1][1] 187 188 return None 189 190 191def choose_subaction( 192 action: Optional[List[str]] = None, 193 options: Optional[Dict[str, Any]] = None, 194 **kw 195) -> SuccessTuple: 196 """ 197 Given a dictionary of options and the standard Meerschaum actions list, 198 check if choice is valid and execute chosen function, else show available 199 options and return False 200 201 Parameters 202 ---------- 203 action: Optional[List[str]], default None 204 A list of subactions (e.g. `show pipes` -> ['pipes']). 205 206 options: Optional[Dict[str, Any]], default None 207 Available options to execute. 208 option (key) -> function (value) 209 Functions must accept **kw keyword arguments 210 and return a tuple of success bool and message. 211 212 Returns 213 ------- 214 The return value of the chosen subaction (assumed to be a `SuccessTuple`). 215 216 """ 217 from meerschaum.utils.warnings import warn, info 218 import inspect 219 if action is None: 220 action = [] 221 if options is None: 222 options = {} 223 parent_action = inspect.stack()[1][3] 224 if len(action) == 0: 225 action = [''] 226 choice = action[0] 227 228 def valid_choice(_choice : str, _options : dict): 229 if _choice in _options: 230 return _choice 231 if (_choice + 's') in options: 232 return _choice + 's' 233 return None 234 235 parsed_choice = valid_choice(choice, options) 236 if parsed_choice is None: 237 warn(f"Cannot {parent_action} '{choice}'. Choose one:", stack=False) 238 for option in sorted(options): 239 print(f" - {parent_action} {option}") 240 return (False, f"Invalid choice '{choice}'.") 241 ### remove parent sub-action 242 kw['action'] = list(action) 243 del kw['action'][0] 244 return options[parsed_choice](**kw) 245 246 247def _get_subaction_names(action: str, globs: dict = None) -> List[str]: 248 """Use `meerschaum.actions.get_subactions()` instead.""" 249 if globs is None: 250 import importlib 251 module = importlib.import_module(f'meerschaum.actions.{action}') 252 globs = vars(module) 253 subactions = [] 254 for item in globs: 255 if f'_{action}' in item and 'complete' not in item.lstrip('_'): 256 subactions.append(globs[item]) 257 return subactions 258 259 260def choices_docstring(action: str, globs: Optional[Dict[str, Any]] = None) -> str: 261 """ 262 Append the an action's available options to the module docstring. 263 This function is to be placed at the bottom of each action module. 264 265 Parameters 266 ---------- 267 action: str 268 The name of the action module (e.g. 'install'). 269 270 globs: Optional[Dict[str, Any]], default None 271 An optional dictionary of global variables. 272 273 Returns 274 ------- 275 The generated docstring for the module. 276 277 Examples 278 -------- 279 >>> from meerschaum.utils.misc import choices_docstring as _choices_docstring 280 >>> install.__doc__ += _choices_docstring('install') 281 282 """ 283 options_str = f"\n Options:\n `{action} " 284 subactions = _get_subaction_names(action, globs=globs) 285 options_str += "[" 286 sa_names = [] 287 for sa in subactions: 288 try: 289 sa_names.append(sa.__name__[len(f"_{action}") + 1:]) 290 except Exception as e: 291 print(e) 292 return "" 293 for sa_name in sorted(sa_names): 294 options_str += f"{sa_name}, " 295 options_str = options_str[:-2] + "]`" 296 return options_str 297 298### build __all__ from other .py files in this package 299import sys 300modules = get_modules_from_package( 301 sys.modules[__name__], 302 names = False, 303) 304__all__ = ['actions', 'get_subactions', 'get_action', 'get_main_action_name', 'get_completer'] 305 306### Build the actions dictionary by importing all 307### functions that do not begin with '_' from all submodules. 308from inspect import getmembers, isfunction 309actions = {} 310 311for module in modules: 312 ### A couple important things happening here: 313 ### 1. Find all functions in all modules in `actions` package 314 ### (skip functions that begin with '_') 315 ### 2. Add them as members to the Shell class 316 ### - Original definition : meerschaum._internal.shell.Shell 317 ### - New definition : meerschaum._internal.Shell 318 ### 3. Populate the actions dictionary with function names and functions 319 ### 320 ### UPDATE: 321 ### Shell modifications have been deferred to get_shell in order to improve lazy loading. 322 323 actions.update( 324 dict( 325 [ 326 ### __name__ and new function pointer 327 (ob[0], ob[1]) 328 for ob in getmembers(module) 329 if isfunction(ob[1]) 330 ### check that the function belongs to the module 331 and ob[0] == module.__name__.replace('_', '').split('.')[-1] 332 ### skip functions that start with '_' 333 and ob[0][0] != '_' 334 ] 335 ) 336 ) 337 338original_actions = actions.copy() 339from meerschaum._internal.entry import entry, get_shell 340import meerschaum.plugins 341make_action = meerschaum.plugins.make_action 342pre_sync_hook = meerschaum.plugins.pre_sync_hook 343post_sync_hook = meerschaum.plugins.post_sync_hook 344 345### Instruct pdoc to skip the `meerschaum.actions.plugins` subdirectory. 346__pdoc__ = { 347 'plugins': False, 348 'arguments': True, 349 'shell': False, 350 'actions': ( 351 "Access functions of the standard Meerschaum actions.\n\n" 352 + "Visit the [actions reference page](https://meerschaum.io/reference/actions/) " 353 + "for documentation about each action.\n\n" 354 + """\n\nExamples 355-------- 356>>> actions['show'](['pipes']) 357 358""" 359 + "Keys\n----\n" 360 + ', '.join(sorted([f'`{a}`' for a in actions])) 361 ) 362} 363for a in actions: 364 __pdoc__[a] = False 365meerschaum.plugins.load_plugins()
actions =
{'delete': <function delete>, 'login': <function login>, 'copy': <function copy>, 'deduplicate': <function deduplicate>, 'reload': <function reload>, 'sql': <function sql>, 'edit': <function edit>, 'stack': <function stack>, 'start': <function start>, 'verify': <function verify>, 'drop': <function drop>, 'pause': <function pause>, 'attach': <function attach>, 'uninstall': <function uninstall>, 'os': <function os>, 'register': <function register>, 'clear': <function clear>, 'install': <function install>, 'stop': <function stop>, 'python': <function python>, 'restart': <function restart>, 'show': <function show>, 'api': <function api>, 'sync': <function sync>, 'bootstrap': <function bootstrap>, 'tag': <function tag>, 'setup': <function setup>, 'sh': <function sh>, 'upgrade': <function upgrade>, 'lup': <function lup>, 'compose': <function compose>}
def
get_subactions( action: Union[str, List[str]], _actions: Optional[Dict[str, Callable[[Any], Any]]] = None) -> Dict[str, Callable[[Any], Any]]:
25def get_subactions( 26 action: Union[str, List[str]], 27 _actions: Optional[Dict[str, Callable[[Any], Any]]] = None, 28) -> Dict[str, Callable[[Any], Any]]: 29 """ 30 Return a dictionary of an action's sub-action functions. 31 32 Examples 33 -------- 34 >>> get_subactions('install').keys() 35 dict_keys(['packages', 'plugins', 'required']) 36 >>> 37 """ 38 import importlib, inspect 39 subactions = {} 40 if isinstance(action, str): 41 action = [action] 42 action_function = get_action(action[0], _actions=_actions) 43 if action_function is None: 44 return subactions 45 try: 46 action_module = importlib.import_module(action_function.__module__) 47 except ImportError: 48 action_module = None 49 if action_module is None: 50 return subactions 51 for name, f in inspect.getmembers(action_module): 52 if not inspect.isfunction(f): 53 continue 54 55 ### Detect subactions which may contain an underscore prefix. 56 if ( 57 name.lstrip('_').startswith(action_function.__name__.lstrip('_') + '_') 58 ): 59 _name = name.replace(action_function.__name__, '') 60 _name = _name.lstrip('_') 61 subactions[_name] = f 62 return subactions
Return a dictionary of an action's sub-action functions.
Examples
>>> get_subactions('install').keys()
dict_keys(['packages', 'plugins', 'required'])
>>>
def
get_action( action: Union[str, List[str]], _actions: Optional[Dict[str, Callable[[Any], Any]]] = None) -> Optional[Callable[[Any], Any]]:
65def get_action( 66 action: Union[str, List[str]], 67 _actions: Optional[Dict[str, Callable[[Any], Any]]] = None, 68) -> Union[Callable[[Any], Any], None]: 69 """ 70 Return a function corresponding to the given action list. 71 This may be a custom action with an underscore, in which case, allow for underscores. 72 This may also be a subactions, which is handled by `get_subactions()` 73 """ 74 if _actions is None: 75 _actions = actions 76 if isinstance(action, str): 77 action = [action] 78 79 if not any(action): 80 return None 81 82 ### Simple case, e.g. ['show'] 83 if len(action) == 1: 84 if action[0] in _actions: 85 return _actions[action[0]] 86 87 ### e.g. ['foo'] (and no custom action available) 88 return None 89 90 ### Last case: it could be a custom action with an underscore in the name. 91 action_name_with_underscores = '_'.join(action) 92 candidates = [] 93 for action_key, action_function in _actions.items(): 94 if not '_' in action_key: 95 continue 96 if action_name_with_underscores.startswith(action_key): 97 leftovers = action_name_with_underscores.replace(action_key, '') 98 candidates.append((len(leftovers), action_function)) 99 if len(candidates) > 0: 100 return sorted(candidates)[0][1] 101 102 ### Might be dealing with a subaction. 103 if action[0] in _actions: 104 subactions = get_subactions([action[0]], _actions=_actions) 105 if action[1] not in subactions: 106 if (action[1] + 's') in subactions: 107 return subactions[action[1] + 's'] 108 return _actions[action[0]] 109 return subactions[action[1]] 110 111 return None
Return a function corresponding to the given action list.
This may be a custom action with an underscore, in which case, allow for underscores.
This may also be a subactions, which is handled by get_subactions()
def
get_main_action_name( action: Union[List[str], str], _actions: Optional[Dict[str, Callable[[Any], Tuple[bool, str]]]] = None) -> Optional[str]:
114def get_main_action_name( 115 action: Union[List[str], str], 116 _actions: Optional[Dict[str, Callable[[Any], SuccessTuple]]] = None, 117) -> Union[str, None]: 118 """ 119 Given an action list, return the name of the main function. 120 For subactions, this will return the root function. 121 If no action function can be found, return `None`. 122 123 Parameters 124 ---------- 125 action: Union[List[str], str] 126 The standard action list. 127 128 Examples 129 -------- 130 >>> get_main_action_name(['show', 'pipes']) 131 'show' 132 >>> 133 """ 134 if _actions is None: 135 _actions = actions 136 action_function = get_action(action, _actions=_actions) 137 if action_function is None: 138 return None 139 clean_name = action_function.__name__.lstrip('_') 140 if clean_name in _actions: 141 return clean_name 142 words = clean_name.split('_') 143 first_word = words[0] 144 if first_word in _actions: 145 return first_word 146 ### We might be dealing with shell `do_` functions. 147 second_word = words[1] if len(words) > 1 else None 148 if second_word and second_word in _actions: 149 return second_word 150 return None
Given an action list, return the name of the main function.
For subactions, this will return the root function.
If no action function can be found, return None
.
Parameters
- action (Union[List[str], str]): The standard action list.
Examples
>>> get_main_action_name(['show', 'pipes'])
'show'
>>>
def
get_completer( action: Union[List[str], str], _actions: Optional[Dict[str, Callable[[Any], Tuple[bool, str]]]] = None) -> Optional[Callable[[meerschaum._internal.shell.Shell.Shell, str, str, int, int], List[str]]]:
153def get_completer( 154 action: Union[List[str], str], 155 _actions: Optional[Dict[str, Callable[[Any], SuccessTuple]]] = None, 156) -> Union[ 157 Callable[['meerschaum._internal.shell.Shell', str, str, int, int], List[str]], None 158 ]: 159 """Search for a custom completer function for an action.""" 160 import importlib, inspect 161 if isinstance(action, str): 162 action = [action] 163 action_function = get_action(action) 164 if action_function is None: 165 return None 166 try: 167 action_module = importlib.import_module(action_function.__module__) 168 except ImportError: 169 action_module = None 170 if action_module is None: 171 return None 172 candidates = [] 173 for name, f in inspect.getmembers(action_module): 174 if not inspect.isfunction(f): 175 continue 176 name_lstrip = name.lstrip('_') 177 if not name_lstrip.startswith('complete_'): 178 continue 179 if name_lstrip == 'complete_' + action_function.__name__: 180 candidates.append((name_lstrip, f)) 181 continue 182 if name_lstrip == 'complete_' + action_function.__name__.split('_')[0]: 183 candidates.append((name_lstrip, f)) 184 if len(candidates) == 1: 185 return candidates[0][1] 186 if len(candidates) > 1: 187 return sorted(candidates)[-1][1] 188 189 return None
Search for a custom completer function for an action.