Files
OpenBB/openbb_terminal/core/library/operation.py
montezdesousa 5953be0a42 Terminal authentication + keys/settings/routines (#4015)
* import order

* pylint

* no login, go to prompt

* add logout and whoami

* clear before prompt

* black

* these are unecessary

* error

* improve login prompt message

* login does not follow the controller pattern, so will name it differently

* nop

* move logout and change to results

* change return obj

* ruff

* pyupgrade

* remove user from statics

* message

* fetch configs at start

* remove unecessary func

* unecessary dataclass

* change user vars

* change login flow

* remote logout should not be used

* simplify

* fix double exit logout bug

* wrong exception

* apply keys

* change controller

* mypy

* save if no internet

* pylint

* logout everywhere

* breaking changes: move to local/hub models

* unused import

* fix logout bug

* whoami in guest mode

* add todo

* small bug guest allowed

* limit flair length

* fix bug on user pull

* logout working

* handle no connection

* logout user when logout

* remove keys push

* split logger funcs

* commant apply configs for now

* account to session

* rename user

* clear sounds better

* breaking changes: splitting guest from logged users

* pull imports up

* delete session model

* move logout func

* add ability to save and apply keys from remote

* no sensitive keys

* add support for feature flags

* unecessary print

* allow settings from differnt modules

* change setff to static

* change way to set ff

* fix bug in applying configs

* simplify obbff application

* bug on setting configs

* allow text ff

* obbff in settings

* add upload to cfg_plot 1

* data sources + backend

* this solves circular import?

* move create_session to model

* rever logs changes, not needed for now

* sync optionally

* outline account controller interface

* don't sync when guest

* block guest on installer

* or not and

* start routines

* fix bug on ff, settings

* is installer

* implementing chavi suggestions

* fix colors on installer prompt

* Logging login (#4046)

* basic logging for the login

* removing unused import

* hacking the logs custom filter to not filter the email

* black

* cooping with the new changes

* removing redundant method

* ruff

* ruff

* mypy

* SDK login (#4051)

* sdk login

* adding to the trail map

* minor fix

* improve sdk expirience

* adapting to new changes

* add docstrings

* block guest from account

* change guest msg

* change guest messages

* add sync on/off

* display onoff

* missing paragraph

* fix glitch on logout

* add sys clear

* filtering password

* filtering the email from user

* avoid writing to multiple log files if a user logs out

* fix bug, guest allowed in installer without connection

* log user on the sdk

* split login from lauch

* add clear and change messages on account

* docstrings

* start pull cmd

* change account menu description

* Fix login bug and decouple some logic (#4056)

* fix bug, guest allowed in installer without connection

* split login from lauch

* improvements on the sdk login

---------

Co-authored-by: hjoaquim <h.joaquim@campus.fct.unl.pt>

* sync fix

* change account controller description

* ruff

* add help on account cmds

* update clear prompt

* fix email not in jwt

* remove jwt from requiremnts

* pull command output

* remove character

* pull cmd when no data

* add space below whoami

* fix log filenotfound exception

* changed arg name

* delete .hist on logout

* add asteriscs to password

* pylint

* uncomment rich config

* show only diffs on pull

* black#

* warn when no changes#

* pull msg was switched

* apply diff only

* breaking?: strbool change

* reduce some code

* add timeout to hub requests

* black

* add remote logout

* success msg only if success

* remove colors in prompt

* update docstrings

* keep timeout outside funcs

* update clear

* clear and patch

* delete session

* routine upload

* move .his file to .openbb folder

* change apply configs

* refactor logout

* remove mixed portfolio changes

* fix bug in lock file

* fix bug pwidth, pheight are floats

* fix sources command & add choices

* refactor timezone setting

* userdata

* fix flair

* need to cast paths

* cast on diff

* fix flair default

* save session test

* get session

* data

* test remove

* test remove with assert

* add bad test cases

* add exception cases to unittests

* test remove cli hist file

* black

* test apply configs

* test apply with sync flag

* test create session, session model

* login tests, session model

* some tests for the hub_model

* test logout user

* black

* test clear vars, reload

* test create session

* update user-json to user

* update user class

* add user tests

* tests sdk session

* tests for hub_model

* start account controller tsts

* unused import

* tests account controller: clear, pull

* tests for session_controller

* logout success bugfix

* add integration test for account

* black

* move routine related commands to other PR

* test for the user input

* completed tests for session controller

* convert to hist to str)

* fix import errors

* ruff

* ruff tests

* fix qa

* add help to settings cmds

* ruff

* fix pull keys bug

* point to dev

* account model tests

* black

* ruff

* fix for empty feature settings

* ruff

* don't display config msg if wrong token

* general msgs

* remove print

* ruff

* fix sources not updated real time

* missing return doc

* add guest if no email

* Routine remote storage (#4143)

* add upload routine command

* remove prints

* point to local again

* add commands

* add delete

* add download command and upload 409

* list comprehension

* add download & save plus upload fixe

* add user uuid folder

* add list command

* ruff

* en.yml detail

* provide choices to delete & download

* add choices to upload

* change menu text

* starting adding unit tests

* bad english

* Update test_print_help.txt

* genearl msgs

* remove print

* add docstrings

* add unit tests to account controller

* change parser

* fix routine upload test

* add to be implemented tests hub

* add to be implemented tests local

* add total pages to title

* split list into model and view

* add docstring

* print msg if character

* add local model tests

* hub tests routines

* fix test

* point to dev and glob

* fix small bug on upload

* missing return

* log decorators missing

* black

* ruff

* change prompt display

* rewrite test

* rewrite expected

* rewrite call

* try passing git action

* change .yml everywhere

---------

Co-authored-by: Henrique Joaquim <h.joaquim@campus.fct.unl.pt>
Co-authored-by: James Maslek <jmaslek11@gmail.com>
2023-02-09 13:35:44 +00:00

307 lines
8.7 KiB
Python

import json
from importlib import import_module
from inspect import signature
from logging import Logger, getLogger
from typing import Any, Callable, Dict, List, Optional
import openbb_terminal.config_terminal as cfg
from openbb_terminal.core.library.metadata import Metadata
from openbb_terminal.core.library.trail_map import TrailMap
from openbb_terminal.session.sdk_session import login
# 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=import-outside-toplevel
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_if_login(
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_if_login(
method_chosen: Callable,
):
if method_chosen.__name__ == login.__name__:
from openbb_terminal.core.log.generation.user_logger import ( # pylint: disable=import-outside-toplevel
log_user,
)
log_user(with_rollover=False)
@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