Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Improve OBBject Registry #6364

Merged
merged 14 commits into from
May 6, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 25 additions & 12 deletions cli/openbb_cli/argparse_translator/obbject_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,43 @@
import json
from typing import Dict, List

from openbb_core.app.model.abstract.singleton import SingletonMeta
from openbb_core.app.model.obbject import OBBject


class Registry(metaclass=SingletonMeta):
class Registry:
"""Registry for OBBjects."""

obbjects: List[OBBject] = []
def __init__(self):
"""Initialize the registry."""
self._obbjects: List[OBBject] = []

@staticmethod
def _contains_obbject(uuid: str, obbjects: List[OBBject]) -> bool:
"""Check if obbject with uuid is in the registry."""
return any(obbject.id == uuid for obbject in obbjects)

@classmethod
def register(cls, obbject: OBBject):
def register(self, obbject: OBBject):
"""Designed to add an OBBject instance to the registry."""
if isinstance(obbject, OBBject) and not cls._contains_obbject(
obbject.id, cls.obbjects
if isinstance(obbject, OBBject) and not self._contains_obbject(
obbject.id, self._obbjects
):
cls.obbjects.append(obbject)
self._obbjects.append(obbject)

@classmethod
def get(cls, idx: int) -> OBBject:
def get(self, idx: int) -> OBBject:
"""Return the obbject at index idx."""
# the list should work as a stack
# i.e., the last element needs to be accessed by idx=0 and so on
reversed_list = list(reversed(cls.obbjects))
reversed_list = list(reversed(self._obbjects))
return reversed_list[idx]

def remove(self, idx: int = -1):
"""Remove the obbject at index idx, default is the last element."""
# the list should work as a stack
# i.e., the last element needs to be accessed by idx=0 and so on
reversed_list = list(reversed(self._obbjects))
del reversed_list[idx]
self._obbjects = list(reversed(reversed_list))

@property
def all(self) -> Dict[int, Dict]:
"""Return all obbjects in the registry"""
Expand Down Expand Up @@ -65,7 +73,7 @@ def _handle_data_repr(obbject: OBBject) -> str:
return data_repr

obbjects = {}
for i, obbject in enumerate(list(reversed(self.obbjects))):
for i, obbject in enumerate(list(reversed(self._obbjects))):
obbjects[i] = {
"route": obbject._route, # pylint: disable=protected-access
"provider": obbject.provider,
Expand All @@ -74,3 +82,8 @@ def _handle_data_repr(obbject: OBBject) -> str:
}

return obbjects

@property
def obbjects(self) -> List[OBBject]:
"""Return all obbjects in the registry"""
return self._obbjects
2 changes: 2 additions & 0 deletions cli/openbb_cli/assets/i18n/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,5 @@ en:
settings/language: translation language
settings/n_rows: number of rows to show on non interactive tables
settings/n_cols: number of columns to show on non interactive tables
settings/obbject_msg: show obbject registry message after a new result is added
settings/obbject_res: define the maximum number of obbjects allowed in the registry
32 changes: 32 additions & 0 deletions cli/openbb_cli/controllers/base_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from pathlib import Path
from typing import Any, Dict, List, Literal, Optional, Union

import pandas as pd
from openbb_cli.config import setup
from openbb_cli.config.completer import NestedCompleter
from openbb_cli.config.constants import SCRIPT_TAGS
Expand All @@ -20,6 +21,7 @@
get_flair_and_username,
parse_and_split_input,
print_guest_block_msg,
print_rich_table,
remove_file,
system_clear,
)
Expand Down Expand Up @@ -64,6 +66,7 @@ class BaseController(metaclass=ABCMeta):
"stop",
"hold",
"whoami",
"results",
]

CHOICES_COMMANDS: List[str] = []
Expand Down Expand Up @@ -119,6 +122,11 @@ def __init__(self, queue: Optional[List[str]] = None) -> None:
self.parser.exit_on_error = False # type: ignore
self.parser.add_argument("cmd", choices=self.controller_choices)

def update_completer(self, choices) -> None:
"""Update the completer with new choices."""
if session.prompt_session and session.settings.USE_PROMPT_TOOLKIT:
self.completer = NestedCompleter.from_nested_dict(choices)

def check_path(self) -> None:
"""Check if command path is valid."""
path = self.PATH
Expand Down Expand Up @@ -732,6 +740,30 @@ def call_whoami(self, other_args: List[str]) -> None:
else:
print_guest_block_msg()

def call_results(self, other_args: List[str]):
"""Process results command."""
parser = argparse.ArgumentParser(
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog="results",
description="Process results command. This command displays a registry of "
"'OBBjects' where all execution results are stored. "
"It is organized as a stack, with the most recent result at index 0.",
)
ns_parser = self.parse_simple_args(parser, other_args)
if ns_parser:
results = session.obbject_registry.all
if results:
df = pd.DataFrame.from_dict(results, orient="index")
print_rich_table(
df,
show_index=True,
index_name="stack index",
title="OBBject Results",
)
else:
session.console.print("[info]No results found.[/info]")

@staticmethod
def parse_simple_args(parser: argparse.ArgumentParser, other_args: List[str]):
"""Parse list of arguments into the supplied parser.
Expand Down
33 changes: 23 additions & 10 deletions cli/openbb_cli/controllers/base_platform_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@
from openbb_cli.argparse_translator.argparse_class_processor import (
ArgparseClassProcessor,
)
from openbb_cli.argparse_translator.obbject_registry import Registry
from openbb_cli.config.completer import NestedCompleter
from openbb_cli.config.menu_text import MenuText
from openbb_cli.controllers.base_controller import BaseController
from openbb_cli.controllers.utils import export_data, print_rich_table
Expand Down Expand Up @@ -69,26 +67,23 @@ def __init__(
self._link_obbject_to_data_processing_commands()
self._generate_commands()
self._generate_sub_controllers()

if session.prompt_session and session.settings.USE_PROMPT_TOOLKIT:
choices: dict = self.choices_default
self.completer = NestedCompleter.from_nested_dict(choices)
self.update_completer(self.choices_default)

def _link_obbject_to_data_processing_commands(self):
"""Link data processing commands to OBBject registry."""
for _, trl in self.translators.items():
for action in trl._parser._actions: # pylint: disable=protected-access
if action.dest == "data":
action.choices = range(len(Registry.obbjects))
action.choices = range(len(session.obbject_registry.obbjects))
action.type = int
action.nargs = None

def _intersect_data_processing_commands(self, ns_parser):
"""Intersect data processing commands and change the obbject id into an actual obbject."""
if hasattr(ns_parser, "data") and ns_parser.data in range(
len(Registry.obbjects)
len(session.obbject_registry.obbjects)
):
obbject = Registry.get(ns_parser.data)
obbject = session.obbject_registry.get(ns_parser.data)
setattr(ns_parser, "data", obbject.results)

return ns_parser
Expand Down Expand Up @@ -159,7 +154,25 @@ def method(self, other_args: List[str], translator=translator):
title = f"{self.PATH}{translator.func.__name__}"

if obbject:
Registry.register(obbject)
if (
len(session.obbject_registry.obbjects)
>= session.settings.N_TO_KEEP_OBBJECT_REGISTRY
):
session.console.print(
"Maximum number of OBBjects reached. Removing oldest."
)

session.obbject_registry.register(obbject)
# we need to force to re-link so that the new obbject
# is immediately available for data processing commands
self._link_obbject_to_data_processing_commands()
# also update the completer
self.update_completer(self.choices_default)

if session.settings.SHOW_MSG_OBBJECT_REGISTRY:
session.console.print(
f"Added OBBject to registry. Total: {len(session.obbject_registry.obbjects)}"
)

if hasattr(ns_parser, "chart") and ns_parser.chart:
obbject.show()
Expand Down
15 changes: 1 addition & 14 deletions cli/openbb_cli/controllers/cli_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@
import pandas as pd
import requests
from openbb import obb
from openbb_cli.argparse_translator.obbject_registry import Registry
from openbb_cli.config import constants
from openbb_cli.config.completer import NestedCompleter
from openbb_cli.config.constants import (
ASSETS_DIRECTORY,
ENV_FILE_SETTINGS,
Expand Down Expand Up @@ -217,7 +215,7 @@ def update_runtime_choices(self):
"--tag3": {c: None for c in constants.SCRIPT_TAGS},
}

self.completer = NestedCompleter.from_nested_dict(choices)
self.update_completer(choices)

def print_help(self):
"""Print help."""
Expand Down Expand Up @@ -475,17 +473,6 @@ def call_exe(self, other_args: List[str]):
)
self.queue = self.queue[1:]

def call_results(self, _):
"""Process results command."""
results = Registry().all
if results:
df = pd.DataFrame.from_dict(results, orient="index")
print_rich_table(
df, show_index=True, index_name="stack index", title="OBBject Results"
)
else:
session.console.print("[info]No results found.[/info]")


def handle_job_cmds(jobs_cmds: Optional[List[str]]) -> Optional[List[str]]:
"""Handle job commands."""
Expand Down
43 changes: 39 additions & 4 deletions cli/openbb_cli/controllers/feature_flags_controller.py
hjoaquim marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import argparse
from typing import List, Optional

from openbb_cli.config.completer import NestedCompleter
from openbb_cli.config.constants import AVAILABLE_FLAIRS
from openbb_cli.config.menu_text import MenuText

Expand Down Expand Up @@ -40,6 +39,8 @@ class FeatureFlagsController(BaseController):
"language",
"n_rows",
"n_cols",
"obbject_registry_msg",
"obbject_registry_res",
hjoaquim marked this conversation as resolved.
Show resolved Hide resolved
]
PATH = "/settings/"
CHOICES_GENERATION = True
Expand All @@ -48,9 +49,7 @@ def __init__(self, queue: Optional[List[str]] = None):
"""Initialize the Constructor."""
super().__init__(queue)

if session.prompt_session and session.settings.USE_PROMPT_TOOLKIT:
choices: dict = self.choices_default
self.completer = NestedCompleter.from_nested_dict(choices)
self.update_completer(self.choices_default)

def print_help(self):
"""Print help."""
Expand All @@ -68,6 +67,7 @@ def print_help(self):
mt.add_setting("tbhint", settings.TOOLBAR_HINT)
mt.add_setting("overwrite", settings.FILE_OVERWRITE)
mt.add_setting("version", settings.SHOW_VERSION)
mt.add_setting("obbject_msg", settings.SHOW_MSG_OBBJECT_REGISTRY)
mt.add_raw("\n")
mt.add_info("_settings_")
mt.add_raw("\n")
Expand All @@ -77,6 +77,7 @@ def print_help(self):
mt.add_cmd("language")
mt.add_cmd("n_rows")
mt.add_cmd("n_cols")
mt.add_cmd("obbject_res")

session.console.print(text=mt.menu_text, menu="Feature Flags")

Expand Down Expand Up @@ -134,6 +135,13 @@ def call_tbhint(self, _):
session.console.print("Will take effect when running CLI again.")
session.settings.set_item("TOOLBAR_HINT", not session.settings.TOOLBAR_HINT)

def call_obbject_msg(self, _):
"""Process obbject_msg command."""
session.settings.set_item(
"SHOW_MSG_OBBJECT_REGISTRY",
not session.settings.SHOW_MSG_OBBJECT_REGISTRY,
)

def call_console_style(self, other_args: List[str]) -> None:
"""Process cosole_style command."""
parser = argparse.ArgumentParser(
Expand Down Expand Up @@ -290,3 +298,30 @@ def call_n_cols(self, other_args: List[str]) -> None:
session.console.print(
f"Current number of columns: {session.settings.ALLOWED_NUMBER_OF_COLUMNS}"
)

def call_obbject_res(self, other_args: List[str]):
"""Process obbject_res command."""
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog="obbject_res",
description="Maximum allowed number of results to keep in the OBBject Registry.",
add_help=False,
)
parser.add_argument(
"-n",
"--number",
dest="number",
action="store",
required=False,
type=int,
)
ns_parser = self.parse_simple_args(parser, other_args)

if ns_parser and ns_parser.number:
session.settings.set_item("N_TO_KEEP_OBBJECT_REGISTRY", ns_parser.columns)

elif not other_args:
session.console.print(
f"Current maximum allowed number of results to keep in the OBBject registry:"
f" {session.settings.N_TO_KEEP_OBBJECT_REGISTRY}"
)
6 changes: 4 additions & 2 deletions cli/openbb_cli/models/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,21 @@ class Settings(BaseModel):
REMEMBER_CONTEXTS: bool = True
ENABLE_RICH_PANEL: bool = True
TOOLBAR_HINT: bool = True
SHOW_MSG_OBBJECT_REGISTRY: bool = False

# GENERAL
TIMEZONE: str = "America/New_York"
FLAIR: str = ":openbb"
USE_LANGUAGE: str = "en"
PREVIOUS_USE: bool = False
N_TO_KEEP_OBBJECT_REGISTRY: int = 10

# STYLE
RICH_STYLE: str = "dark"

# OUTPUT
ALLOWED_NUMBER_OF_ROWS: int = 366
ALLOWED_NUMBER_OF_COLUMNS: int = 15
ALLOWED_NUMBER_OF_ROWS: int = 20
ALLOWED_NUMBER_OF_COLUMNS: int = 5

# OPENBB
HUB_URL: str = "https://my.openbb.co"
Expand Down
7 changes: 7 additions & 0 deletions cli/openbb_cli/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from openbb_core.app.model.user_settings import UserSettings as User
from prompt_toolkit import PromptSession

from openbb_cli.argparse_translator.obbject_registry import Registry
from openbb_cli.config.completer import CustomFileHistory
from openbb_cli.config.console import Console
from openbb_cli.config.constants import HIST_FILE_PROMPT
Expand All @@ -31,6 +32,7 @@ def __init__(self):
settings=self._settings, style=self._style.console_style
)
self._prompt_session = self._get_prompt_session()
self._obbject_registry = Registry()

@property
def user(self) -> User:
Expand All @@ -52,6 +54,11 @@ def console(self) -> Console:
"""Get console."""
return self._console

@property
def obbject_registry(self) -> Registry:
"""Get obbject registry."""
return self._obbject_registry

@property
def prompt_session(self) -> Optional[PromptSession]:
"""Get prompt session."""
Expand Down
Loading