Skip to content

Commit

Permalink
Merge branch 'develop' into feature/hub-defaults
Browse files Browse the repository at this point in the history
  • Loading branch information
montezdesousa authored Jun 18, 2024
2 parents 086243e + fdfacc8 commit 3f0ab6e
Show file tree
Hide file tree
Showing 39 changed files with 2,549 additions and 2,265 deletions.
43 changes: 37 additions & 6 deletions cli/openbb_cli/argparse_translator/obbject_registry.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Registry for OBBjects."""

import json
from typing import Dict, List
from typing import Dict, List, Optional, Union

from openbb_core.app.model.obbject import OBBject

Expand Down Expand Up @@ -29,11 +29,32 @@ def register(self, obbject: OBBject) -> bool:
return True
return False

def get(self, idx: int) -> OBBject:
def get(self, arg: Union[int, str]) -> Optional[OBBject]:
"""Return the obbject with index or key."""
if isinstance(arg, int):
return self._get_by_index(arg)
if isinstance(arg, str):
return self._get_by_key(arg)

raise ValueError("Couldn't get the `OBBject` with the provided argument.")

def _get_by_key(self, key: str) -> Optional[OBBject]:
"""Return the obbject with key."""
for obbject in self._obbjects:
if obbject.extra.get("register_key", "") == key:
return obbject
return None

def _get_by_index(self, idx: int) -> Optional[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(self._obbjects))

# check if the index is out of bounds
if idx >= len(reversed_list):
return None

return reversed_list[idx]

def remove(self, idx: int = -1):
Expand All @@ -46,10 +67,10 @@ def remove(self, idx: int = -1):

@property
def all(self) -> Dict[int, Dict]:
"""Return all obbjects in the registry"""
"""Return all obbjects in the registry."""

def _handle_standard_params(obbject: OBBject) -> str:
"""Handle standard params for obbjects"""
"""Handle standard params for obbjects."""
standard_params_json = ""
std_params = getattr(
obbject, "_standard_params", {}
Expand All @@ -63,7 +84,7 @@ def _handle_standard_params(obbject: OBBject) -> str:
return standard_params_json

def _handle_data_repr(obbject: OBBject) -> str:
"""Handle data representation for obbjects"""
"""Handle data representation for obbjects."""
data_repr = ""
if hasattr(obbject, "results") and obbject.results:
data_schema = (
Expand All @@ -86,11 +107,21 @@ def _handle_data_repr(obbject: OBBject) -> str:
"standard params": _handle_standard_params(obbject),
"data": _handle_data_repr(obbject),
"command": obbject.extra.get("command", ""),
"key": obbject.extra.get("register_key", ""),
}

return obbjects

@property
def obbjects(self) -> List[OBBject]:
"""Return all obbjects in the registry"""
"""Return all obbjects in the registry."""
return self._obbjects

@property
def obbject_keys(self) -> List[str]:
"""Return all obbject keys in the registry."""
return [
obbject.extra["register_key"]
for obbject in self._obbjects
if "register_key" in obbject.extra
]
5 changes: 5 additions & 0 deletions cli/openbb_cli/argparse_translator/reference_processor.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
"""Module for the ReferenceToArgumentsProcessor class."""

# `ForwardRef`needs to be imported because the usage of `eval()`,
# which creates a ForwardRef
# which would raise a not defined error if it's not imported here.
# pylint: disable=unused-import
from typing import (
Any,
Dict,
ForwardRef, # noqa: F401
List,
Literal,
Optional,
Expand Down
2 changes: 1 addition & 1 deletion cli/openbb_cli/argparse_translator/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def get_argument_optional_choices(parser: ArgumentParser, argument_name: str) ->
and hasattr(action, "optional_choices")
):
return (
action.optional_choices # pylint: disable=no-member # this is a custom attribute
action.optional_choices # type: ignore[attr-defined] # this is a custom attribute
)
return False

Expand Down
97 changes: 86 additions & 11 deletions cli/openbb_cli/controllers/base_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
print_rich_table,
remove_file,
system_clear,
validate_register_key,
)
from openbb_cli.session import Session
from prompt_toolkit.formatted_text import HTML
Expand Down Expand Up @@ -628,19 +629,77 @@ def call_results(self, other_args: List[str]):
"'OBBjects' where all execution results are stored. "
"It is organized as a stack, with the most recent result at index 0.",
)
parser.add_argument("--index", dest="index", help="Index of the result.")
parser.add_argument("--key", dest="key", help="Key of the result.")
parser.add_argument(
"--chart", action="store_true", dest="chart", help="Display chart."
)
parser.add_argument(
"--export", dest="export", help="Export data.", nargs="+", default=None
)

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]")
if not ns_parser.index and not ns_parser.key:
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]")
elif ns_parser.index:
try:
index = int(ns_parser.index)
obbject = session.obbject_registry.get(index)
if obbject:
if ns_parser.chart and obbject.chart:
obbject.show()
else:
title = obbject.extra.get("command", "")
df = obbject.to_dataframe()
print_rich_table(
df=df,
show_index=True,
title=title,
export=ns_parser.export,
)
if ns_parser.chart and not obbject.chart:
session.console.print(
"[info]No chart available.[/info]"
)
else:
session.console.print(
f"[info]No result found at index {index}.[/info]"
)
except ValueError:
session.console.print(
f"[red]Index must be an integer, not '{ns_parser.index}'.[/red]"
)
elif ns_parser.key:
obbject = session.obbject_registry.get(ns_parser.key)
if obbject:
if ns_parser.chart and obbject.chart:
obbject.show()
else:
title = obbject.extra.get("command", "")
df = obbject.to_dataframe()
print_rich_table(
df=df,
show_index=True,
title=title,
export=ns_parser.export,
)
if ns_parser.chart and not obbject.chart:
session.console.print("[info]No chart available.[/info]")
else:
session.console.print(
f"[info]No result found with key '{ns_parser.key}'.[/info]"
)

@staticmethod
def parse_simple_args(parser: argparse.ArgumentParser, other_args: List[str]):
Expand Down Expand Up @@ -774,6 +833,22 @@ def parse_known_args_and_warn(
help="Number of entries to show in data.",
type=check_positive,
)

parser.add_argument(
"--register_obbject",
dest="register_obbject",
action="store_false",
default=True,
help="Flag to store data in the OBBject registry, True by default.",
)
parser.add_argument(
"--register_key",
dest="register_key",
default="",
help="Key to reference data in the OBBject registry.",
type=validate_register_key,
)

if session.settings.USE_CLEAR_AFTER_CMD:
system_clear()

Expand Down
78 changes: 59 additions & 19 deletions cli/openbb_cli/controllers/base_platform_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,20 +75,31 @@ def _link_obbject_to_data_processing_commands(self):
for _, trl in self.translators.items():
for action in trl._parser._actions: # pylint: disable=protected-access
if action.dest == "data":
# Generate choices by combining indexed and key-based choices
action.choices = [
"OBB" + str(i)
for i in range(len(session.obbject_registry.obbjects))
] + [
obbject.extra["register_key"]
for obbject in session.obbject_registry.obbjects
if "register_key" in obbject.extra
]

action.type = str
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"):
ns_parser.data = int(ns_parser.data.replace("OBB", ""))
if ns_parser.data in range(len(session.obbject_registry.obbjects)):
if "OBB" in ns_parser.data:
ns_parser.data = int(ns_parser.data.replace("OBB", ""))

if (ns_parser.data in range(len(session.obbject_registry.obbjects))) or (
ns_parser.data in session.obbject_registry.obbject_keys
):
obbject = session.obbject_registry.get(ns_parser.data)
setattr(ns_parser, "data", obbject.results)
if obbject and isinstance(obbject, OBBject):
setattr(ns_parser, "data", obbject.results)

return ns_parser

Expand Down Expand Up @@ -152,40 +163,69 @@ def method(self, other_args: List[str], translator=translator):
try:
ns_parser = self._intersect_data_processing_commands(ns_parser)

store_obbject = (
hasattr(ns_parser, "register_obbject")
and ns_parser.register_obbject
)

obbject = translator.execute_func(parsed_args=ns_parser)
df: pd.DataFrame = pd.DataFrame()
fig: Optional[OpenBBFigure] = None
title = f"{self.PATH}{translator.func.__name__}"

if obbject:
if isinstance(obbject, OBBject):
if session.max_obbjects_exceeded() and obbject.results:
if (
session.max_obbjects_exceeded()
and obbject.results
and store_obbject
):
session.obbject_registry.remove()
session.console.print(
"[yellow]Maximum number of OBBjects reached. The oldest entry was removed.[yellow]"
)

# use the obbject to store the command so we can display it later on results
obbject.extra["command"] = f"{title} {' '.join(other_args)}"

register_result = 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 there is a registry key in the parser, store to the obbject
if (
session.settings.SHOW_MSG_OBBJECT_REGISTRY
and register_result
hasattr(ns_parser, "register_key")
and ns_parser.register_key
):
session.console.print(
"Added `OBBject` to cached results."
if (
ns_parser.register_key
not in session.obbject_registry.obbject_keys
):
obbject.extra["register_key"] = str(
ns_parser.register_key
)
else:
session.console.print(
f"[yellow]Key `{ns_parser.register_key}` already exists in the registry."
"The `OBBject` was kept without the key.[/yellow]"
)

if store_obbject:
# store the obbject in the registry
register_result = session.obbject_registry.register(
obbject
)

# making the dataframe available
# either for printing or exporting
# 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
and register_result
):
session.console.print(
"Added `OBBject` to cached results."
)

# making the dataframe available either for printing or exporting
df = obbject.to_dataframe()

export = hasattr(ns_parser, "export") and ns_parser.export
Expand Down
16 changes: 16 additions & 0 deletions cli/openbb_cli/controllers/choices.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from openbb_cli.controllers.utils import (
check_file_type_saved,
check_positive,
validate_register_key,
)
from openbb_cli.session import Session

Expand Down Expand Up @@ -93,6 +94,21 @@ def __mock_parse_known_args_and_warn(
type=check_positive,
)

parser.add_argument(
"--register_obbject",
dest="register_obbject",
action="store_false",
default=True,
help="Flag to store data in the OBBject registry, True by default.",
)
parser.add_argument(
"--register_key",
dest="register_key",
default="",
help="Key to reference data in the OBBject registry.",
type=validate_register_key,
)


def __mock_parse_simple_args(parser: ArgumentParser, other_args: List[str]) -> None:
"""Add arguments.
Expand Down
Loading

0 comments on commit 3f0ab6e

Please sign in to comment.