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