diff options
author | montezdesousa <79287829+montezdesousa@users.noreply.github.com> | 2024-05-01 22:17:02 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-05-01 21:17:02 +0000 |
commit | a0d580ab25f91657b05acdadc47cd0999de102c7 (patch) | |
tree | 92fbf4cfb3214de58b05482fd3fe650ce4dbfb9f | |
parent | c45997567c81fdc39c3d6e232ad4f8a0690b81f1 (diff) |
[Feature] - Display command providers (#6355)
* fix: avoid calling Session() multiple times
* rename session refs
* fix: cmd text
* fix: avoid Session()
* fix: add providers by cmd
* fix: timezones
---------
Co-authored-by: Igor Radovanovic <74266147+IgorWounds@users.noreply.github.com>
-rw-r--r-- | cli/openbb_cli/config/console.py | 10 | ||||
-rw-r--r-- | cli/openbb_cli/config/menu_text.py | 169 | ||||
-rw-r--r-- | cli/openbb_cli/controllers/base_controller.py | 99 | ||||
-rw-r--r-- | cli/openbb_cli/controllers/base_platform_controller.py | 26 | ||||
-rw-r--r-- | cli/openbb_cli/controllers/choices.py | 8 | ||||
-rw-r--r-- | cli/openbb_cli/controllers/cli_controller.py | 77 | ||||
-rw-r--r-- | cli/openbb_cli/controllers/feature_flags_controller.py | 87 | ||||
-rw-r--r-- | cli/openbb_cli/controllers/hub_service.py | 13 | ||||
-rw-r--r-- | cli/openbb_cli/controllers/script_parser.py | 6 |
9 files changed, 254 insertions, 241 deletions
diff --git a/cli/openbb_cli/config/console.py b/cli/openbb_cli/config/console.py index f19904cedc4..cbb0f8f2696 100644 --- a/cli/openbb_cli/config/console.py +++ b/cli/openbb_cli/config/console.py @@ -31,12 +31,8 @@ class Console: self.menu_text = "" self.menu_path = "" - def capture(self): - """Capture the console output.""" - return self._console.capture() - @staticmethod - def filter_rich_tags(text): + def _filter_rich_tags(text): """Filter out rich tags from text.""" for val in RICH_TAGS: text = text.replace(val, "") @@ -44,7 +40,7 @@ class Console: return text @staticmethod - def blend_text( + def _blend_text( message: str, color1: Tuple[int, int, int], color2: Tuple[int, int, int] ) -> Text: """Blend text from one color to another.""" @@ -85,7 +81,7 @@ class Console: else: self._console.print(kwargs["text"]) else: - print(self.filter_rich_tags(kwargs["text"])) # noqa: T201 + print(self._filter_rich_tags(kwargs["text"])) # noqa: T201 elif not self._settings.TEST_MODE: self._console.print(*args, **kwargs) else: diff --git a/cli/openbb_cli/config/menu_text.py b/cli/openbb_cli/config/menu_text.py index 39bfa5787e9..d9bdc59656c 100644 --- a/cli/openbb_cli/config/menu_text.py +++ b/cli/openbb_cli/config/menu_text.py @@ -2,7 +2,7 @@ __docformat__ = "numpy" -from typing import Dict, List, Optional, Union +from typing import Dict, List import i18n from openbb import obb @@ -29,18 +29,18 @@ RICH_TAGS = [ USE_COLOR = True -def get_ordered_list_sources(command_path: str) -> List: - """Return the preferred source for the given command. +def get_ordered_providers(command_path: str) -> List: + """Return the preferred provider for the given command. Parameters ---------- command_path: str - The command to find the source for. E.g. "/equity/price/historical + The command to find the provider for. E.g. "/equity/price/historical Returns ------- List - The list of sources for the given command. + The list of providers for the given command. """ command_reference = obb.reference.get("paths", {}).get(command_path, {}) # type: ignore if command_reference: @@ -52,7 +52,12 @@ def get_ordered_list_sources(command_path: str) -> List: class MenuText: """Create menu text with rich colors to be displayed by CLI.""" - def __init__(self, path: str = "", column_sources: int = 100): + CMD_NAME_LENGTH = 18 + CMD_DESCRIPTION_LENGTH = 65 + CMD_PROVIDERS_LENGTH = 23 + SECTION_SPACING = 4 + + def __init__(self, path: str = ""): """Initialize menu help. Parameters @@ -64,7 +69,6 @@ class MenuText: """ self.menu_text = "" self.menu_path = path - self.col_src = column_sources self.warnings: List[Dict[str, str]] = [] def add_raw(self, raw_text: str): @@ -117,146 +121,153 @@ class MenuText: ) self.menu_text += f"[param]{parameter_translated}{space}:[/param] {value}\n" - def _adjust_command_length(self, key_command: str) -> str: + def _format_cmd_name(self, name: str) -> str: """Adjust the length of the command if it is too long. Parameters ---------- - key_command : str - command to be adjusted + name : str + command to be formatted Returns ------- str - adjusted command + formatted command """ - if len(key_command) > 18: - new_key_command = key_command[:18] # Default to trimming to 18 characters + if len(name) > self.CMD_NAME_LENGTH: + new_name = name[ + : self.CMD_NAME_LENGTH + ] # Default to trimming to 18 characters - if "_" in key_command: - key_command_split = key_command.split("_") + if "_" in name: + name_split = name.split("_") - new_key_command = ( - "_".join(key_command_split[:2]) - if len(key_command_split) > 2 - else key_command_split[0] + new_name = ( + "_".join(name_split[:2]) if len(name_split) > 2 else name_split[0] ) - if len(new_key_command) > 18: - new_key_command = new_key_command[:18] + if len(new_name) > self.CMD_NAME_LENGTH: + new_name = new_name[: self.CMD_NAME_LENGTH] - if new_key_command != key_command: + if new_name != name: self.warnings.append( { "warning": "Command name too long", - "command": key_command, - "trimmed_command": new_key_command, + "command": name, + "trimmed_command": new_name, } ) - key_command = new_key_command + name = new_name - return key_command + return name - def _handle_command_description( - self, key_command: str, command_description: str + def _format_cmd_description( + self, name: str, description: str, trim: bool = True ) -> str: """Handle the command description. Parameters ---------- - key_command : str + name : str command to be adjusted - command_description : str + description : str description of the command + trim : bool + If true, the description will be trimmed to the maximum length Returns ------- str adjusted command description """ - if not command_description: - command_description = i18n.t(self.menu_path + key_command) - if command_description == self.menu_path + key_command: - command_description = "" + if not description: + description = i18n.t(self.menu_path + name) + if description == self.menu_path + name: + description = "" return ( - command_description[:88] + "..." - if len(command_description) > 91 - else command_description + description[: self.CMD_DESCRIPTION_LENGTH - 3] + "..." + if len(description) > self.CMD_DESCRIPTION_LENGTH and trim + else description ) - def add_cmd( - self, key_command: str, condition: bool = True, command_description: str = "" - ): + def add_cmd(self, name: str, description: str = "", disable: bool = False): """Append command text (after translation from key) to a menu. Parameters ---------- - key_command : str + name : str key command to be executed by user. It is also used as a key to get description of command. - condition : bool - condition in which command is available to user. I.e. displays command and description. - If condition is false, the command line is greyed out. + description : str + description of the command + disable : bool + If disable is true, the command line is greyed out. """ - key_command = self._adjust_command_length(key_command) - command_description = self._handle_command_description( - key_command, command_description + formatted_name = self._format_cmd_name(name) + name_padding = (self.CMD_NAME_LENGTH - len(formatted_name)) * " " + providers = get_ordered_providers(f"{self.menu_path}{formatted_name}") + formatted_description = self._format_cmd_description( + formatted_name, + description, + bool(providers), ) - spacing = (23 - (len(key_command) + 4)) * " " - - cmd = f"{key_command}{spacing}{command_description}" - cmd = f"[cmds] {cmd}[/cmds]" if condition else f"[unvl] {cmd}[/unvl]" - - sources = get_ordered_list_sources(f"{self.menu_path}{key_command}") - - if sources: - space = (self.col_src - len(cmd)) * " " if self.col_src > len(cmd) else " " - cmd += f"{space}[src][{', '.join(sources)}][/src]" + description_padding = ( + self.CMD_DESCRIPTION_LENGTH - len(formatted_description) + ) * " " + spacing = self.SECTION_SPACING * " " + description_padding = ( + self.CMD_DESCRIPTION_LENGTH - len(formatted_description) + ) * " " + cmd = f"{spacing}{formatted_name + name_padding}{spacing}{formatted_description+description_padding}" + cmd = f"[unvl]{cmd}[/unvl]" if disable else f"[cmds]{cmd}[/cmds]" + + if providers: + cmd += rf"{spacing}[src]\[{', '.join(providers)}][/src]" self.menu_text += cmd + "\n" def add_menu( self, - key_menu: str, - condition: Optional[Union[bool, str]] = True, - menu_description: str = "", + name: str, + description: str = "", + disable: bool = False, ): """Append menu text (after translation from key) to a menu. Parameters ---------- - key_menu : str + name : str key menu to be executed by user. It is also used as a key to get description of menu. - condition : bool - condition in which menu is available to user. I.e. displays menu and description. - If condition is false, the menu line is greyed out. + disable : bool + If disable is true, the menu line is greyed out. """ - spacing = (23 - (len(key_menu) + 4)) * " " + spacing = (self.CMD_NAME_LENGTH - len(name) + self.SECTION_SPACING) * " " - if menu_description: - menu = f"{key_menu}{spacing}{menu_description}" + if description: + menu = f"{name}{spacing}{description}" else: - menu_description = i18n.t(self.menu_path + key_menu) - if menu_description == self.menu_path + key_menu: - menu_description = "" - menu = f"{key_menu}{spacing}{menu_description}" + description = i18n.t(self.menu_path + name) + if description == self.menu_path + name: + description = "" + menu = f"{name}{spacing}{description}" - if condition: - self.menu_text += f"[menu]> {menu}[/menu]\n" - else: + if disable: self.menu_text += f"[unvl]> {menu}[/unvl]\n" + else: + self.menu_text += f"[menu]> {menu}[/menu]\n" - def add_setting(self, key_setting: str, status: bool = True): + def add_setting(self, name: str, status: bool = True): """Append menu text (after translation from key) to a menu. Parameters ---------- - key_setting : str + name : str key setting to be set by user. It is also used as a key to get description of the setting. status : bool status of the current setting. If true the line will be green, otherwise red. """ - spacing = (23 - (len(key_setting) + 4)) * " " + spacing = (self.CMD_NAME_LENGTH - len(name) + self.SECTION_SPACING) * " " + indentation = self.SECTION_SPACING * " " if status: - self.menu_text += f"[green] {key_setting}{spacing}{i18n.t(self.menu_path + key_setting)}[/green]\n" + self.menu_text += f"[green]{indentation}{name}{spacing}{i18n.t(self.menu_path + name)}[/green]\n" else: - self.menu_text += f"[red] {key_setting}{spacing}{i18n.t(self.menu_path + key_setting)}[/red]\n" + self.menu_text += f"[red]{indentation}{name}{spacing}{i18n.t(self.menu_path + name)}[/red]\n" diff --git a/cli/openbb_cli/controllers/base_controller.py b/cli/openbb_cli/controllers/base_controller.py index 7d0c3fe27cc..a3d8b20aa16 100644 --- a/cli/openbb_cli/controllers/base_controller.py +++ b/cli/openbb_cli/controllers/base_controller.py @@ -31,6 +31,7 @@ from prompt_toolkit.styles import Style # pylint: disable=R0912 controllers: Dict[str, Any] = {} +session = Session() # TODO: We should try to avoid these global variables @@ -132,7 +133,7 @@ class BaseController(metaclass=ABCMeta): def load_class(self, class_ins, *args, **kwargs): """Check for an existing instance of the controller before creating a new one.""" - settings = Session().settings + settings = session.settings self.save_class() arguments = len(args) + len(kwargs) # Due to the 'arguments == 1' condition, we actually NEVER load a class @@ -267,7 +268,7 @@ class BaseController(metaclass=ABCMeta): def save_class(self) -> None: """Save the current instance of the class to be loaded later.""" - if Session().settings.REMEMBER_CONTEXTS: + if session.settings.REMEMBER_CONTEXTS: controllers[self.PATH] = self def custom_reset(self) -> List[str]: @@ -321,7 +322,7 @@ class BaseController(metaclass=ABCMeta): actions = self.parse_input(an_input) if an_input and an_input != "reset": - Session().console.print() + session.console.print() # Empty command if len(actions) == 0: @@ -374,7 +375,7 @@ class BaseController(metaclass=ABCMeta): not self.queue or (self.queue and self.queue[0] not in ("quit", "help")) ) ): - Session().console.print() + session.console.print() return self.queue @@ -385,7 +386,7 @@ class BaseController(metaclass=ABCMeta): def call_home(self, _) -> None: """Process home command.""" self.save_class() - if self.PATH.count("/") == 1 and Session().settings.ENABLE_EXIT_AUTO_HELP: + if self.PATH.count("/") == 1 and session.settings.ENABLE_EXIT_AUTO_HELP: self.print_help() for _ in range(self.PATH.count("/") - 1): self.queue.insert(0, "quit") @@ -406,9 +407,9 @@ class BaseController(metaclass=ABCMeta): for _ in range(self.PATH.count("/")): self.queue.insert(0, "quit") - if not Session().is_local(): + if not session.is_local(): remove_file( - Path(Session().user.preferences.export_directory, "routines", "hub") + Path(session.user.preferences.export_directory, "routines", "hub") ) def call_reset(self, _) -> None: @@ -502,7 +503,7 @@ class BaseController(metaclass=ABCMeta): if ns_parser: if not ns_parser.name: - Session().console.print( + session.console.print( "[red]Set a routine title by using the '-n' flag. E.g. 'record -n Morning routine'[/red]" ) return @@ -513,7 +514,7 @@ class BaseController(metaclass=ABCMeta): else ns_parser.tag1 ) if tag1 and tag1 not in SCRIPT_TAGS: - Session().console.print( + session.console.print( f"[red]The parameter 'tag1' needs to be one of the following {', '.join(SCRIPT_TAGS)}[/red]" ) return @@ -524,7 +525,7 @@ class BaseController(metaclass=ABCMeta): else ns_parser.tag2 ) if tag2 and tag2 not in SCRIPT_TAGS: - Session().console.print( + session.console.print( f"[red]The parameter 'tag2' needs to be one of the following {', '.join(SCRIPT_TAGS)}[/red]" ) return @@ -535,19 +536,19 @@ class BaseController(metaclass=ABCMeta): else ns_parser.tag3 ) if tag3 and tag3 not in SCRIPT_TAGS: - Session().console.print( + session.console.print( f"[red]The parameter 'tag3' needs to be one of the following {', '.join(SCRIPT_TAGS)}[/red]" ) return - if Session().is_local() and not ns_parser.local: - Session().console.print( + if session.is_local() and not ns_parser.local: + session.console.print( "[red]Recording session to the OpenBB Hub is not supported in guest mode.[/red]" ) - Session().console.print( + session.console.print( "\n[yellow]Sign to OpenBB Hub to register: http://openbb.co[/yellow]" ) - Session().console.print( + session.console.print( "\n[yellow]Otherwise set the flag '-l' to save the file locally.[/yellow]" ) return @@ -556,7 +557,7 @@ class BaseController(metaclass=ABCMeta): title = " ".join(ns_parser.name) if ns_parser.name else "" pattern = re.compile(r"^[a-zA-Z0-9\s]+$") if not pattern.match(title): - Session().console.print( + session.console.print( f"[red]Title '{title}' has invalid format. Please use only digits, characters and whitespaces.[/]" ) return @@ -582,10 +583,10 @@ class BaseController(metaclass=ABCMeta): SESSION_RECORDED_PUBLIC = ns_parser.public - Session().console.print( + session.console.print( f"[green]The routine '{title}' is successfully being recorded.[/green]" ) - Session().console.print( + session.console.print( "\n[yellow]Remember to run 'stop' command when you are done!\n[/yellow]" ) @@ -595,15 +596,15 @@ class BaseController(metaclass=ABCMeta): global SESSION_RECORDED # noqa: PLW0603 if not RECORD_SESSION: - Session().console.print( + session.console.print( "[red]There is no session being recorded. Start one using the command 'record'[/red]\n" ) elif len(SESSION_RECORDED) < 5: - Session().console.print( + session.console.print( "[red]Run at least 4 commands before stopping recording a session.[/red]\n" ) else: - current_user = Session().user + current_user = session.user # Check if the user just wants to store routine locally # This works regardless of whether they are logged in or not @@ -620,14 +621,14 @@ class BaseController(metaclass=ABCMeta): # If file already exists, add a timestamp to the name if os.path.isfile(routine_file): - i = Session().console.input( + i = session.console.input( "A local routine with the same name already exists, " "do you want to override it? (y/n): " ) - Session().console.print("") + session.console.print("") while i.lower() not in ["y", "yes", "n", "no"]: - i = Session().console.input("Select 'y' or 'n' to proceed: ") - Session().console.print("") + i = session.console.input("Select 'y' or 'n' to proceed: ") + session.console.print("") if i.lower() in ["n", "no"]: new_name = ( @@ -639,7 +640,7 @@ class BaseController(metaclass=ABCMeta): "routines", new_name, ) - Session().console.print( + session.console.print( f"[yellow]The routine name has been updated to '{new_name}'[/yellow]\n" ) @@ -650,7 +651,7 @@ class BaseController(metaclass=ABCMeta): lines = ["# OpenBB Platform CLI - Routine", "\n"] username = getattr( - Session().user.profile.hub_session, "username", "local" + session.user.profile.hub_session, "username", "local" ) lines += [f"# Author: {username}", "\n\n"] if username else ["\n"] @@ -666,13 +667,13 @@ class BaseController(metaclass=ABCMeta): # Writing data to a file file1.writelines(lines) - Session().console.print( + session.console.print( f"[green]Your routine has been recorded and saved here: {routine_file}[/green]\n" ) # If user doesn't specify they want to store routine locally # Confirm that the user is logged in - elif not Session().is_local(): + elif not session.is_local(): routine = "\n".join(SESSION_RECORDED[:-1]) hub_session = current_user.profile.hub_session @@ -692,16 +693,16 @@ class BaseController(metaclass=ABCMeta): } response = upload_routine(**kwargs) # type: ignore if response is not None and response.status_code == 409: - i = Session().console.input( + i = session.console.input( "A routine with the same name already exists, " "do you want to replace it? (y/n): " ) - Session().console.print("") + session.console.print("") if i.lower() in ["y", "yes"]: kwargs["override"] = True # type: ignore response = upload_routine(**kwargs) # type: ignore else: - Session().console.print("[info]Aborted.[/info]") + session.console.print("[info]Aborted.[/info]") # Clear session to be recorded again RECORD_SESSION = False @@ -718,14 +719,14 @@ class BaseController(metaclass=ABCMeta): ns_parser = self.parse_simple_args(parser, other_args) if ns_parser: - current_user = Session().user - local_user = Session().is_local() + current_user = session.user + local_user = session.is_local() if not local_user: hub_session = current_user.profile.hub_session - Session().console.print( + session.console.print( f"[info]email:[/info] {hub_session.email if hub_session else 'N/A'}" ) - Session().console.print( + session.console.print( f"[info]uuid:[/info] {hub_session.user_uuid if hub_session else 'N/A'}" ) else: @@ -751,23 +752,23 @@ class BaseController(metaclass=ABCMeta): "-h", "--help", action="store_true", help="show this help message" ) - if Session().settings.USE_CLEAR_AFTER_CMD: + if session.settings.USE_CLEAR_AFTER_CMD: system_clear() try: (ns_parser, l_unknown_args) = parser.parse_known_args(other_args) except SystemExit: # In case the command has required argument that isn't specified - Session().console.print("\n") + session.console.print("\n") return None if ns_parser.help: txt_help = parser.format_help() - Session().console.print(f"[help]{txt_help}[/help]") + session.console.print(f"[help]{txt_help}[/help]") return None if l_unknown_args: - Session().console.print( + session.console.print( f"The following args couldn't be interpreted: {l_unknown_args}\n" ) @@ -870,7 +871,7 @@ class BaseController(metaclass=ABCMeta): help="Number of entries to show in data.", type=check_positive, ) - if Session().settings.USE_CLEAR_AFTER_CMD: + if session.settings.USE_CLEAR_AFTER_CMD: system_clear() if "--help" in other_args or "-h" in other_args: @@ -880,7 +881,7 @@ class BaseController(metaclass=ABCMeta): f"For more information and examples, use 'about {parser.prog}' " f"to access the related guide.\n" ) - Session().console.print(f"[help]{txt_help}[/help]") + session.console.print(f"[help]{txt_help}[/help]") return None try: @@ -910,14 +911,14 @@ class BaseController(metaclass=ABCMeta): setup.set_last_legend(" ".join(ns_parser.hold_legend_str)) if l_unknown_args: - Session().console.print( + session.console.print( f"The following args couldn't be interpreted: {l_unknown_args}" ) return ns_parser def menu(self, custom_path_menu_above: str = ""): """Enter controller menu.""" - settings = Session().settings + settings = session.settings an_input = "HELP_ME" while True: @@ -948,7 +949,7 @@ class BaseController(metaclass=ABCMeta): and an_input != "help" and an_input.split(" ")[0] in self.controller_choices ): - Session().console.print( + session.console.print( f"{get_flair_and_username()} {self.PATH} $ {an_input}" ) @@ -959,7 +960,7 @@ class BaseController(metaclass=ABCMeta): self.print_help() try: - prompt_session = Session().prompt_session + prompt_session = session.prompt_session if prompt_session and settings.USE_PROMPT_TOOLKIT: # Check if toolbar hint was enabled if settings.TOOLBAR_HINT: @@ -1002,7 +1003,7 @@ class BaseController(metaclass=ABCMeta): self.queue = self.switch(an_input) except SystemExit: - Session().console.print( + session.console.print( f"[red]The command '{an_input}' doesn't exist on the {self.PATH} menu.[/red]\n", ) similar_cmd = difflib.get_close_matches( @@ -1019,14 +1020,14 @@ class BaseController(metaclass=ABCMeta): if candidate_input == an_input: an_input = "" self.queue = [] - Session().console.print("\n") + session.console.print("\n") continue an_input = candidate_input else: an_input = similar_cmd[0] - Session().console.print( + session.console.print( f"[green]Replacing by '{an_input}'.[/green]\n" ) self.queue.insert(0, an_input) diff --git a/cli/openbb_cli/controllers/base_platform_controller.py b/cli/openbb_cli/controllers/base_platform_controller.py index c6b7d6b0374..0fc55c0ff1c 100644 --- a/cli/openbb_cli/controllers/base_platform_controller.py +++ b/cli/openbb_cli/controllers/base_platform_controller.py @@ -18,6 +18,8 @@ from openbb_cli.controllers.base_controller import BaseCo |