openbb-cli 1.0.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 (42) hide show
  1. openbb_cli/__init__.py +1 -0
  2. openbb_cli/argparse_translator/__init__.py +0 -0
  3. openbb_cli/argparse_translator/argparse_class_processor.py +148 -0
  4. openbb_cli/argparse_translator/argparse_translator.py +627 -0
  5. openbb_cli/argparse_translator/obbject_registry.py +94 -0
  6. openbb_cli/assets/routines/routine_example.openbb +21 -0
  7. openbb_cli/assets/styles/default/Consolas.ttf +0 -0
  8. openbb_cli/assets/styles/default/dark.mpfstyle.json +47 -0
  9. openbb_cli/assets/styles/default/dark.mplrc.json +7 -0
  10. openbb_cli/assets/styles/default/dark.mplstyle +96 -0
  11. openbb_cli/assets/styles/default/dark.pltstyle.json +132 -0
  12. openbb_cli/assets/styles/default/dark.richstyle.json +9 -0
  13. openbb_cli/assets/styles/default/light.mpfstyle.json +47 -0
  14. openbb_cli/assets/styles/default/light.mplrc.json +7 -0
  15. openbb_cli/assets/styles/default/light.mplstyle +95 -0
  16. openbb_cli/assets/styles/default/light.pltstyle.json +871 -0
  17. openbb_cli/assets/styles/default/light.richstyle.json +9 -0
  18. openbb_cli/assets/styles/default/tables.pltstyle.json +102 -0
  19. openbb_cli/assets/styles/user/openbb.richstyle.json +9 -0
  20. openbb_cli/cli.py +20 -0
  21. openbb_cli/config/__init__.py +1 -0
  22. openbb_cli/config/completer.py +427 -0
  23. openbb_cli/config/console.py +93 -0
  24. openbb_cli/config/constants.py +80 -0
  25. openbb_cli/config/menu_text.py +165 -0
  26. openbb_cli/config/setup.py +11 -0
  27. openbb_cli/config/style.py +108 -0
  28. openbb_cli/controllers/base_controller.py +941 -0
  29. openbb_cli/controllers/base_platform_controller.py +349 -0
  30. openbb_cli/controllers/choices.py +324 -0
  31. openbb_cli/controllers/cli_controller.py +946 -0
  32. openbb_cli/controllers/hub_service.py +107 -0
  33. openbb_cli/controllers/platform_controller_factory.py +58 -0
  34. openbb_cli/controllers/script_parser.py +488 -0
  35. openbb_cli/controllers/settings_controller.py +373 -0
  36. openbb_cli/controllers/utils.py +967 -0
  37. openbb_cli/models/settings.py +73 -0
  38. openbb_cli/session.py +92 -0
  39. openbb_cli-1.0.0.dist-info/METADATA +91 -0
  40. openbb_cli-1.0.0.dist-info/RECORD +42 -0
  41. openbb_cli-1.0.0.dist-info/WHEEL +4 -0
  42. openbb_cli-1.0.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,349 @@
1
+ """Platform Equity Controller."""
2
+
3
+ import os
4
+ from functools import partial, update_wrapper
5
+ from types import MethodType
6
+ from typing import Dict, List, Optional
7
+
8
+ import pandas as pd
9
+ from openbb import obb
10
+ from openbb_charting.core.openbb_figure import OpenBBFigure
11
+ from openbb_cli.argparse_translator.argparse_class_processor import (
12
+ ArgparseClassProcessor,
13
+ )
14
+ from openbb_cli.config.menu_text import MenuText
15
+ from openbb_cli.controllers.base_controller import BaseController
16
+ from openbb_cli.controllers.utils import export_data, print_rich_table
17
+ from openbb_cli.session import Session
18
+ from openbb_core.app.model.obbject import OBBject
19
+
20
+ session = Session()
21
+
22
+
23
+ class DummyTranslation:
24
+ """Dummy Translation for testing."""
25
+
26
+ def __init__(self):
27
+ """Construct a Dummy Translation Class."""
28
+ self.paths = {}
29
+ self.translators = {}
30
+
31
+
32
+ class PlatformController(BaseController):
33
+ """Platform Controller Base class."""
34
+
35
+ CHOICES_GENERATION = True
36
+
37
+ def __init__(
38
+ self,
39
+ name: str,
40
+ parent_path: List[str],
41
+ platform_target: Optional[type] = None,
42
+ queue: Optional[List[str]] = None,
43
+ translators: Optional[Dict] = None,
44
+ ):
45
+ """Construct a Platform based Controller."""
46
+ self.PATH = f"/{'/'.join(parent_path)}/{name}/" if parent_path else f"/{name}/"
47
+ super().__init__(queue)
48
+ self._name = name
49
+
50
+ if not (platform_target or translators):
51
+ raise ValueError("Either platform_target or translators must be provided.")
52
+
53
+ self._translated_target = (
54
+ ArgparseClassProcessor(
55
+ target_class=platform_target, reference=obb.reference["paths"] # type: ignore
56
+ )
57
+ if platform_target
58
+ else DummyTranslation()
59
+ )
60
+ self.translators = (
61
+ translators
62
+ if translators is not None
63
+ else getattr(self._translated_target, "translators", {})
64
+ )
65
+ self.paths = getattr(self._translated_target, "paths", {})
66
+
67
+ if self.translators:
68
+ self._link_obbject_to_data_processing_commands()
69
+ self._generate_commands()
70
+ self._generate_sub_controllers()
71
+ self.update_completer(self.choices_default)
72
+
73
+ def _link_obbject_to_data_processing_commands(self):
74
+ """Link data processing commands to OBBject registry."""
75
+ for _, trl in self.translators.items():
76
+ for action in trl._parser._actions: # pylint: disable=protected-access
77
+ if action.dest == "data":
78
+ action.choices = [
79
+ "OBB" + str(i)
80
+ for i in range(len(session.obbject_registry.obbjects))
81
+ ]
82
+ action.type = str
83
+ action.nargs = None
84
+
85
+ def _intersect_data_processing_commands(self, ns_parser):
86
+ """Intersect data processing commands and change the obbject id into an actual obbject."""
87
+ if hasattr(ns_parser, "data"):
88
+ ns_parser.data = int(ns_parser.data.replace("OBB", ""))
89
+ if ns_parser.data in range(len(session.obbject_registry.obbjects)):
90
+ obbject = session.obbject_registry.get(ns_parser.data)
91
+ setattr(ns_parser, "data", obbject.results)
92
+
93
+ return ns_parser
94
+
95
+ def _generate_sub_controllers(self):
96
+ """Handle paths."""
97
+ for path, value in self.paths.items():
98
+ if value == "path":
99
+ continue
100
+
101
+ sub_menu_translators = {}
102
+ choices_commands = []
103
+
104
+ for translator_name, translator in self.translators.items():
105
+ if f"{self._name}_{path}" in translator_name:
106
+ new_name = translator_name.replace(f"{self._name}_{path}_", "")
107
+ sub_menu_translators[new_name] = translator
108
+ choices_commands.append(new_name)
109
+
110
+ if translator_name in self.CHOICES_COMMANDS:
111
+ self.CHOICES_COMMANDS.remove(translator_name)
112
+
113
+ # Create the sub controller as a new class
114
+ class_name = f"{self._name.capitalize()}{path.capitalize()}Controller"
115
+ SubController = type(
116
+ class_name,
117
+ (PlatformController,),
118
+ {
119
+ "CHOICES_GENERATION": True,
120
+ # "CHOICES_MENUS": [],
121
+ "CHOICES_COMMANDS": choices_commands,
122
+ },
123
+ )
124
+
125
+ self._generate_controller_call(
126
+ controller=SubController,
127
+ name=path,
128
+ parent_path=self.path,
129
+ translators=sub_menu_translators,
130
+ )
131
+
132
+ def _generate_commands(self):
133
+ """Generate commands."""
134
+ for name, translator in self.translators.items():
135
+ # Prepare the translator name to create a command call in the controller
136
+ new_name = name.replace(f"{self._name}_", "")
137
+
138
+ self._generate_command_call(name=new_name, translator=translator)
139
+
140
+ def _generate_command_call(self, name, translator):
141
+ """Generate command call."""
142
+
143
+ def method(self, other_args: List[str], translator=translator):
144
+ """Call the translator."""
145
+ parser = translator.parser
146
+
147
+ if ns_parser := self.parse_known_args_and_warn(
148
+ parser=parser,
149
+ other_args=other_args,
150
+ export_allowed="raw_data_and_figures",
151
+ ):
152
+ try:
153
+ ns_parser = self._intersect_data_processing_commands(ns_parser)
154
+
155
+ obbject = translator.execute_func(parsed_args=ns_parser)
156
+ df: pd.DataFrame = pd.DataFrame()
157
+ fig: OpenBBFigure = None
158
+ title = f"{self.PATH}{translator.func.__name__}"
159
+
160
+ if obbject:
161
+
162
+ if isinstance(obbject, OBBject):
163
+ if session.max_obbjects_exceeded() and obbject.results:
164
+ session.obbject_registry.remove()
165
+ session.console.print(
166
+ "[yellow]Maximum number of OBBjects reached. The oldest entry was removed.[yellow]"
167
+ )
168
+
169
+ # use the obbject to store the command so we can display it later on results
170
+ obbject.extra["command"] = f"{title} {' '.join(other_args)}"
171
+
172
+ register_result = session.obbject_registry.register(obbject)
173
+
174
+ # we need to force to re-link so that the new obbject
175
+ # is immediately available for data processing commands
176
+ self._link_obbject_to_data_processing_commands()
177
+ # also update the completer
178
+ self.update_completer(self.choices_default)
179
+
180
+ if (
181
+ session.settings.SHOW_MSG_OBBJECT_REGISTRY
182
+ and register_result
183
+ ):
184
+ session.console.print(
185
+ "Added `OBBject` to cached results."
186
+ )
187
+
188
+ # making the dataframe available
189
+ # either for printing or exporting (or both)
190
+ df = obbject.to_dataframe()
191
+
192
+ if hasattr(ns_parser, "chart") and ns_parser.chart:
193
+ obbject.show()
194
+ fig = obbject.chart.fig if obbject.chart else None
195
+ else:
196
+ if isinstance(df.columns, pd.RangeIndex):
197
+ df.columns = [str(i) for i in df.columns]
198
+
199
+ print_rich_table(df=df, show_index=True, title=title)
200
+
201
+ elif isinstance(obbject, dict):
202
+ df = pd.DataFrame.from_dict(obbject, orient="columns")
203
+ print_rich_table(df=df, show_index=True, title=title)
204
+
205
+ elif not isinstance(obbject, OBBject):
206
+ session.console.print(obbject)
207
+
208
+ if (
209
+ hasattr(ns_parser, "export")
210
+ and ns_parser.export
211
+ and not df.empty
212
+ ):
213
+ sheet_name = getattr(ns_parser, "sheet_name", None)
214
+ if sheet_name and isinstance(sheet_name, list):
215
+ sheet_name = sheet_name[0]
216
+
217
+ export_data(
218
+ export_type=",".join(ns_parser.export),
219
+ dir_path=os.path.dirname(os.path.abspath(__file__)),
220
+ func_name=translator.func.__name__,
221
+ df=df,
222
+ sheet_name=sheet_name,
223
+ figure=fig,
224
+ )
225
+ elif hasattr(ns_parser, "export") and ns_parser.export and df.empty:
226
+ session.console.print("[yellow]No data to export.[/yellow]")
227
+
228
+ except Exception as e:
229
+ session.console.print(f"[red]{e}[/]\n")
230
+ return
231
+
232
+ # Bind the method to the class
233
+ bound_method = MethodType(method, self)
234
+
235
+ # Update the wrapper and set the attribute
236
+ bound_method = update_wrapper( # type: ignore
237
+ partial(bound_method, translator=translator), method
238
+ )
239
+ setattr(self, f"call_{name}", bound_method)
240
+
241
+ def _generate_controller_call(self, controller, name, parent_path, translators):
242
+ """Generate controller call."""
243
+
244
+ def method(self, _, controller, name, parent_path, translators):
245
+ """Call the controller."""
246
+ self.queue = self.load_class(
247
+ class_ins=controller,
248
+ name=name,
249
+ parent_path=parent_path,
250
+ translators=translators,
251
+ queue=self.queue,
252
+ )
253
+
254
+ # Bind the method to the class
255
+ bound_method = MethodType(method, self)
256
+
257
+ # Update the wrapper and set the attribute
258
+ bound_method = update_wrapper( # type: ignore
259
+ partial(
260
+ bound_method,
261
+ name=name,
262
+ parent_path=parent_path,
263
+ translators=translators,
264
+ controller=controller,
265
+ ),
266
+ method,
267
+ )
268
+ setattr(self, f"call_{name}", bound_method)
269
+
270
+ def _get_command_description(self, command: str) -> str:
271
+ """Get command description."""
272
+ command_description = (
273
+ obb.reference["paths"] # type: ignore
274
+ .get(f"{self.PATH}{command}", {})
275
+ .get("description", "")
276
+ )
277
+
278
+ if not command_description:
279
+ trl = self.translators.get(
280
+ f"{self._name}_{command}"
281
+ ) or self.translators.get(command)
282
+ if trl and hasattr(trl, "parser"):
283
+ command_description = trl.parser.description
284
+
285
+ return command_description.split(".")[0].lower()
286
+
287
+ def _get_menu_description(self, menu: str) -> str:
288
+ """Get menu description."""
289
+
290
+ def _get_sub_menu_commands():
291
+ """Get sub menu commands."""
292
+ sub_path = f"{self.PATH[1:].replace('/','_')}{menu}"
293
+ commands = []
294
+ for trl in self.translators:
295
+ if sub_path in trl:
296
+ commands.append(trl.replace(f"{sub_path}_", ""))
297
+ return commands
298
+
299
+ menu_description = (
300
+ obb.reference["routers"] # type: ignore
301
+ .get(f"{self.PATH}{menu}", {})
302
+ .get("description", "")
303
+ ) or ""
304
+ if menu_description:
305
+ return menu_description.split(".")[0].lower()
306
+
307
+ # If no description is found, return the sub menu commands
308
+ return ", ".join(_get_sub_menu_commands())
309
+
310
+ def print_help(self):
311
+ """Print help."""
312
+ mt = MenuText(self.PATH)
313
+
314
+ if self.CHOICES_MENUS:
315
+ for menu in self.CHOICES_MENUS:
316
+ description = self._get_menu_description(menu)
317
+ mt.add_menu(name=menu, description=description)
318
+
319
+ if self.CHOICES_COMMANDS:
320
+ mt.add_raw("\n")
321
+
322
+ if self.CHOICES_COMMANDS:
323
+ for command in self.CHOICES_COMMANDS:
324
+ command_description = self._get_command_description(command)
325
+ mt.add_cmd(
326
+ name=command.replace(f"{self._name}_", ""),
327
+ description=command_description,
328
+ )
329
+
330
+ if session.obbject_registry.obbjects:
331
+ mt.add_info("\nCached Results")
332
+ for key, value in list(session.obbject_registry.all.items())[
333
+ : session.settings.N_TO_DISPLAY_OBBJECT_REGISTRY
334
+ ]:
335
+ mt.add_raw(
336
+ f"[yellow]OBB{key}[/yellow]: {value['command']}",
337
+ left_spacing=True,
338
+ )
339
+
340
+ session.console.print(text=mt.menu_text, menu=self.PATH)
341
+
342
+ settings = session.settings
343
+ dev_mode = settings.DEBUG_MODE or settings.TEST_MODE
344
+ if mt.warnings and dev_mode:
345
+ session.console.print("")
346
+ for w in mt.warnings:
347
+ w_str = str(w).replace("{", "").replace("}", "").replace("'", "")
348
+ session.console.print(f"[yellow]{w_str}[/yellow]")
349
+ session.console.print("")
@@ -0,0 +1,324 @@
1
+ """This module contains functions to build the choice map for the controllers."""
2
+
3
+ from argparse import SUPPRESS, ArgumentParser
4
+ from contextlib import contextmanager
5
+ from inspect import isfunction, unwrap
6
+ from types import MethodType
7
+ from typing import Callable, List, Literal
8
+ from unittest.mock import patch
9
+
10
+ from openbb_cli.controllers.utils import (
11
+ check_file_type_saved,
12
+ check_positive,
13
+ )
14
+ from openbb_cli.session import Session
15
+
16
+ session = Session()
17
+
18
+
19
+ def __mock_parse_known_args_and_warn(
20
+ controller, # pylint: disable=unused-argument
21
+ parser: ArgumentParser,
22
+ other_args: List[str],
23
+ export_allowed: Literal[
24
+ "no_export", "raw_data_only", "figures_only", "raw_data_and_figures"
25
+ ] = "no_export",
26
+ raw: bool = False,
27
+ limit: int = 0,
28
+ ) -> None:
29
+ """Add arguments.
30
+
31
+ Add the arguments that would have normally added by :
32
+ - openbb_cli.base_controller.BaseController.parse_known_args_and_warn
33
+
34
+ Parameters
35
+ ----------
36
+ parser: argparse.ArgumentParser
37
+ Parser with predefined arguments
38
+ other_args: List[str]
39
+ list of arguments to parse
40
+ export_allowed: Literal["no_export", "raw_data_only", "figures_only", "raw_data_and_figures"]
41
+ Export options
42
+ raw: bool
43
+ Add the --raw flag
44
+ limit: int
45
+ Add a --limit flag with this number default
46
+ """
47
+ _ = other_args
48
+
49
+ parser.add_argument(
50
+ "-h", "--help", action="store_true", help="show this help message"
51
+ )
52
+
53
+ if export_allowed != "no_export":
54
+ choices_export = []
55
+ help_export = "Does not export!"
56
+
57
+ if export_allowed == "raw_data_only":
58
+ choices_export = ["csv", "json", "xlsx"]
59
+ help_export = "Export raw data into csv, json, xlsx"
60
+ elif export_allowed == "figures_only":
61
+ choices_export = ["png", "jpg", "pdf", "svg"]
62
+ help_export = "Export figure into png, jpg, pdf, svg "
63
+ else:
64
+ choices_export = ["csv", "json", "xlsx", "png", "jpg", "pdf", "svg"]
65
+ help_export = "Export raw data into csv, json, xlsx and figure into png, jpg, pdf, svg "
66
+
67
+ parser.add_argument(
68
+ "--export",
69
+ default="",
70
+ type=check_file_type_saved(choices_export),
71
+ dest="export",
72
+ help=help_export,
73
+ choices=choices_export,
74
+ )
75
+
76
+ if raw:
77
+ parser.add_argument(
78
+ "--raw",
79
+ dest="raw",
80
+ action="store_true",
81
+ default=False,
82
+ help="Flag to display raw data",
83
+ )
84
+ if limit > 0:
85
+ parser.add_argument(
86
+ "-l",
87
+ "--limit",
88
+ dest="limit",
89
+ default=limit,
90
+ help="Number of entries to show in data.",
91
+ type=check_positive,
92
+ )
93
+
94
+
95
+ def __mock_parse_simple_args(parser: ArgumentParser, other_args: List[str]) -> None:
96
+ """Add arguments.
97
+
98
+ Add the arguments that would have normally added by:
99
+ - openbb_cli.parent_classes.BaseController.parse_simple_args
100
+
101
+ Parameters
102
+ ----------
103
+ parser: argparse.ArgumentParser
104
+ Parser with predefined arguments
105
+ other_args: List[str]
106
+ List of arguments to parse
107
+ """
108
+ parser.add_argument(
109
+ "-h", "--help", action="store_true", help="show this help message"
110
+ )
111
+ _ = other_args
112
+
113
+
114
+ def __get_command_func(controller, command: str):
115
+ """Get the function with the name `f"call_{command}"` from controller object.
116
+
117
+ Parameters
118
+ ----------
119
+ controller: BaseController
120
+ Instance of the CLI Controller.
121
+ command: str
122
+ A name from controller.CHOICES_COMMANDS
123
+
124
+ Returns
125
+ -------
126
+ Callable: Command function.
127
+ """
128
+ if command not in controller.CHOICES_COMMANDS:
129
+ raise AttributeError(
130
+ f"The following command is not inside `CHOICES_COMMANDS` : '{command}'"
131
+ )
132
+
133
+ command = f"call_{command}"
134
+ command_func = getattr(controller, command)
135
+ command_func = unwrap(func=command_func)
136
+
137
+ if isfunction(command_func):
138
+ command_func = MethodType(command_func, controller)
139
+
140
+ return command_func
141
+
142
+
143
+ def contains_functions_to_patch(command_func: Callable) -> bool:
144
+ """Check command function.
145
+
146
+ Check if a `command_func` actually contains the functions we want to mock, i.e.:
147
+ - parse_simple_args
148
+ - parse_known_args_and_warn
149
+
150
+ Parameters
151
+ ----------
152
+ command_func: Callable
153
+ Function to check.
154
+
155
+ Returns
156
+ -------
157
+ bool: Whether or not `command_func` contains the mocked functions.
158
+ """
159
+ co_names = command_func.__code__.co_names
160
+
161
+ return bool(
162
+ "parse_simple_args" in co_names or "parse_known_args_and_warn" in co_names
163
+ )
164
+
165
+
166
+ @contextmanager
167
+ def __patch_controller_functions(controller):
168
+ """Patch controller functions.
169
+
170
+ Patch the following function from a BaseController instance:
171
+ - parse_simple_args
172
+ - parse_known_args_and_warn
173
+
174
+ These functions take an 'argparse.ArgumentParser' object as parameter.
175
+ We want to intercept this 'argparse.ArgumentParser' object.
176
+
177
+ Parameters
178
+ ----------
179
+ controller: BaseController
180
+ BaseController object that needs to be patched.
181
+
182
+ Returns
183
+ -------
184
+ List[Callable]: List of mocked functions.
185
+ """
186
+ bound_mock_parse_known_args_and_warn = MethodType(
187
+ __mock_parse_known_args_and_warn,
188
+ controller,
189
+ )
190
+
191
+ rich = patch(
192
+ target="openbb_cli.config.console.Console.print",
193
+ return_value=None,
194
+ )
195
+
196
+ patcher_list = [
197
+ patch.object(
198
+ target=controller,
199
+ attribute="parse_simple_args",
200
+ side_effect=__mock_parse_simple_args,
201
+ return_value=None,
202
+ ),
203
+ patch.object(
204
+ target=controller,
205
+ attribute="parse_known_args_and_warn",
206
+ side_effect=bound_mock_parse_known_args_and_warn,
207
+ return_value=None,
208
+ ),
209
+ ]
210
+
211
+ if not session.settings.DEBUG_MODE:
212
+ rich.start()
213
+ patched_function_list = []
214
+ for patcher in patcher_list:
215
+ patched_function_list.append(patcher.start())
216
+
217
+ yield patched_function_list
218
+
219
+ if not session.settings.DEBUG_MODE:
220
+ rich.stop()
221
+ for patcher in patcher_list:
222
+ patcher.stop()
223
+
224
+
225
+ def _get_argument_parser(
226
+ controller,
227
+ command: str,
228
+ ) -> ArgumentParser:
229
+ """Intercept the ArgumentParser instance from the command function.
230
+
231
+ A command function being a function starting with `call_`, like:
232
+ - call_help
233
+ - call_overview
234
+ - call_load
235
+
236
+ Parameters
237
+ ----------
238
+ controller: BaseController
239
+ Instance of the CLI Controller.
240
+ command: str
241
+ A name from `controller.CHOICES_COMMANDS`.
242
+
243
+ Returns
244
+ -------
245
+ ArgumentParser: ArgumentParser instance from the command function.
246
+ """
247
+ command_func: Callable = __get_command_func(controller=controller, command=command)
248
+
249
+ if not contains_functions_to_patch(command_func=command_func):
250
+ raise AssertionError(
251
+ f"One of these functions should be inside `call_{command}`:\n"
252
+ " - parse_simple_args\n"
253
+ " - parse_known_args_and_warn\n"
254
+ )
255
+
256
+ with __patch_controller_functions(controller=controller) as patched_function_list:
257
+ command_func([])
258
+
259
+ call_count = 0
260
+ for patched_function in patched_function_list:
261
+ call_count += patched_function.call_count
262
+ if patched_function.call_count == 1:
263
+ args, kwargs = patched_function.call_args
264
+ argument_parser = (
265
+ kwargs["parser"] if kwargs.get("parser", None) else args[0]
266
+ )
267
+
268
+ if call_count != 1:
269
+ raise AssertionError(
270
+ f"One of these functions should be called once inside `call_{command}`:\n"
271
+ " - parse_simple_args\n"
272
+ " - parse_known_args_and_warn\n"
273
+ )
274
+
275
+ return argument_parser
276
+
277
+
278
+ def _build_command_choice_map(argument_parser: ArgumentParser) -> dict:
279
+ """Build the choice map for a command."""
280
+ choice_map: dict = {}
281
+ for action in argument_parser._actions: # pylint: disable=protected-access
282
+ if action.help == SUPPRESS:
283
+ continue
284
+ if len(action.option_strings) == 1:
285
+ long_name = action.option_strings[0]
286
+ short_name = ""
287
+ elif len(action.option_strings) == 2:
288
+ short_name = action.option_strings[0]
289
+ long_name = action.option_strings[1]
290
+ else:
291
+ raise AttributeError(f"Invalid argument_parser: {argument_parser}")
292
+
293
+ if hasattr(action, "choices") and action.choices:
294
+ choice_map[long_name] = {str(c): {} for c in action.choices}
295
+ else:
296
+ choice_map[long_name] = {}
297
+
298
+ if short_name and long_name:
299
+ choice_map[short_name] = long_name
300
+
301
+ return choice_map
302
+
303
+
304
+ def build_controller_choice_map(controller) -> dict:
305
+ """Build the choice map for a controller."""
306
+ command_list = controller.CHOICES_COMMANDS
307
+ controller_choice_map: dict = {c: {} for c in controller.controller_choices}
308
+
309
+ for command in command_list:
310
+ try:
311
+ argument_parser = _get_argument_parser(
312
+ controller=controller,
313
+ command=command,
314
+ )
315
+ controller_choice_map[command] = _build_command_choice_map(
316
+ argument_parser=argument_parser
317
+ )
318
+ except Exception as exception:
319
+ if session.settings.DEBUG_MODE:
320
+ raise Exception(
321
+ f"On command : `{command}`.\n{str(exception)}"
322
+ ) from exception
323
+
324
+ return controller_choice_map