Files
OpenBB/openbb_terminal/core/library/operation.py
jose-donato 2e2e5e2fb6 fix about command (#3532)
* fix(docs): added margin between sections

* fix: about command

* fix: about command to match new docs

* sdk aout

* fix: fix homepage

* fix: features -> reference on generate; about on homepage

* fix: juan magic

* update on merge workflows

Co-authored-by: James Maslek <jmaslek11@gmail.com>
Co-authored-by: montezdesousa <79287829+montezdesousa@users.noreply.github.com>
Co-authored-by: Diogo Sousa <montezdesousa@gmail.com>
Co-authored-by: teh_coderer <me@tehcoderer.com>
2022-11-25 18:42:07 -06:00

296 lines
8.2 KiB
Python

import json
from importlib import import_module
from logging import getLogger, Logger
from typing import Any, Callable, Dict, List, Optional
from inspect import signature
import openbb_terminal.config_terminal as cfg
from openbb_terminal.core.library.metadata import Metadata
from openbb_terminal.core.library.trail_map import TrailMap
# pylint: disable=import-outside-toplevel
class MetadataBuilder:
def __init__(
self,
method: Callable,
trail: str,
trail_map: TrailMap,
) -> None:
self.__method = method
self.__trail = trail
self.__trail_map = trail_map
@staticmethod
def build_docstring(
method: Callable,
trail: str,
trail_map: TrailMap,
) -> str:
doc = ""
view_trail = f"{trail}_chart"
if trail_map.get_view_function(view_trail):
doc += f"Use '{view_trail}' to access the view.\n"
if method.__doc__:
doc += method.__doc__
return doc
def build(self) -> Metadata:
method = self.__method
trail = self.__trail
trail_map = self.__trail_map
dir_list: List[str] = []
docstring = self.build_docstring(
method=method,
trail=trail,
trail_map=trail_map,
)
metadata = Metadata(dir_list=dir_list, docstring=docstring)
return metadata
class Operation:
@staticmethod
def __get_method(method_path: str) -> Callable:
module_path, function_name = method_path.rsplit(".", 1)
module = import_module(module_path)
func = getattr(module, function_name)
return func
def __init__(
self,
trail: str,
trail_map: Optional[TrailMap] = None,
metadata: Optional[Metadata] = None,
) -> None:
trail_map = trail_map or TrailMap()
method_path = trail_map.get_model_or_view(trail=trail)
method = self.__get_method(method_path=method_path)
metadata = (
metadata
or MetadataBuilder(method=method, trail=trail, trail_map=trail_map).build()
)
self._trail = trail
self._trail_map = trail_map
self._method = method
self.__doc__ = metadata.docstring
self.__signature__ = signature(method)
def __call__(self, *args: Any, **kwargs: Any) -> Any:
method = self._method
operation_logger = OperationLogger(
trail=self._trail,
method_chosen=method,
args=args,
kwargs=kwargs,
)
operation_logger.log_before_call()
method_result = method(*args, **kwargs)
operation_logger.log_after_call(method_result=method_result)
return method_result
def about(self):
import webbrowser
trail = self._trail
url = "https://docs.openbb.co/sdk/reference/"
url += "/".join(trail.split("."))
webbrowser.open(url)
class OperationLogger:
last_method: Dict[Any, Any] = {}
def __init__(
self,
trail: str,
method_chosen: Callable,
args: Any,
kwargs: Any,
logger: Optional[Logger] = None,
) -> None:
self.__trail = trail
self.__method_chosen = method_chosen
self.__logger = logger or getLogger(self.__method_chosen.__module__)
self.__args = args
self.__kwargs = kwargs
def log_before_call(
self,
):
if self.__check_logging_conditions():
logger = self.__logger
self.__log_start(logger=logger, method_chosen=self.__method_chosen)
self.__log_method_info(
logger=logger,
trail=self.__trail,
method_chosen=self.__method_chosen,
args=self.__args,
kwargs=self.__kwargs,
)
@staticmethod
def __log_start(logger: Logger, method_chosen: Callable):
logger.info(
"START",
extra={"func_name_override": method_chosen.__name__},
)
def __log_method_info(
self,
logger: Logger,
trail: str,
method_chosen: Callable,
args: Any,
kwargs: Any,
):
merged_args = self.__merge_function_args(method_chosen, args, kwargs)
merged_args = self.__remove_key_and_log_state(
method_chosen.__module__, merged_args
)
logging_info: Dict[str, Any] = {}
logging_info["INPUT"] = {
key: str(value)[:100] for key, value in merged_args.items()
}
logging_info["VIRTUAL_PATH"] = trail
logging_info["CHART"] = "chart" in kwargs and kwargs["chart"] is True
logger.info(
f"{json.dumps(logging_info)}",
extra={"func_name_override": method_chosen.__name__},
)
@staticmethod
def __merge_function_args(func: Callable, args: tuple, kwargs: dict) -> dict:
"""
Merge user input args and kwargs with signature defaults into a dictionary.
Parameters
----------
func : Callable
Function to get the args from
args : tuple
Positional args
kwargs : dict
Keyword args
Returns
----------
dict
Merged user args and signature defaults
"""
import inspect # pylint: disable=C0415
sig = inspect.signature(func)
sig_args = {
param.name: param.default
for param in sig.parameters.values()
if param.default is not inspect.Parameter.empty
}
# merge args with sig_args
sig_args.update(dict(zip(sig.parameters, args)))
# add kwargs elements to sig_args
sig_args.update(kwargs)
return sig_args
@staticmethod
def __remove_key_and_log_state(func_module: str, function_args: dict) -> dict:
"""
Remove API key from the function args and log state of keys.
Parameters
----------
func_module : str
Module of the function
function_args : dict
Function args
Returns
----------
dict
Function args with API key removed
"""
if func_module == "openbb_terminal.keys_model":
from openbb_terminal.core.log.generation.settings_logger import ( # pylint: disable=C0415
log_keys,
)
# remove key if defined
function_args.pop("key", None)
log_keys()
return function_args
def log_after_call(
self,
method_result: Any,
):
if self.__check_logging_conditions():
logger = self.__logger
self.__log_exception_if_any(
logger=logger,
method_result=method_result,
method_chosen=self.__method_chosen,
)
self.__log_end(
logger=logger,
method_chosen=self.__method_chosen,
)
OperationLogger.last_method = {
f"{self.__method_chosen.__module__}.{self.__method_chosen.__name__}": {
"args": str(self.__args)[:100],
"kwargs": str(self.__kwargs)[:100],
}
}
@staticmethod
def __log_exception_if_any(
logger: Logger,
method_chosen: Callable,
method_result: Any,
):
if isinstance(method_result, Exception):
logger.exception(
f"Exception: {method_result}",
extra={"func_name_override": method_chosen.__name__},
)
@staticmethod
def __log_end(logger: Logger, method_chosen: Callable):
logger.info(
"END",
extra={"func_name_override": method_chosen.__name__},
)
def __check_logging_conditions(self) -> bool:
return not cfg.LOGGING_SUPPRESS and not self.__check_last_method()
def __check_last_method(self) -> bool:
current_method = {
f"{self.__method_chosen.__module__}.{self.__method_chosen.__name__}": {
"args": str(self.__args)[:100],
"kwargs": str(self.__kwargs)[:100],
}
}
return OperationLogger.last_method == current_method