diff --git a/openbb_terminal/keys_controller.py b/openbb_terminal/keys_controller.py index 7aa908e4eed..a5f67e40fe5 100644 --- a/openbb_terminal/keys_controller.py +++ b/openbb_terminal/keys_controller.py @@ -5,7 +5,7 @@ __docformat__ = "numpy" import argparse import logging -from typing import Dict, List, Optional +from typing import Callable, Dict, List, Optional from openbb_terminal import ( keys_model, @@ -38,9 +38,10 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods API_DICT = keys_model.API_DICT API_LIST = list(API_DICT.keys()) - CHOICES_COMMANDS: List[str] = ["mykeys"] + API_LIST + CHOICES_COMMANDS: List[str] = ["mykeys", "status"] + API_LIST PATH = "/keys/" status_dict: Dict = {} + help_status_text: str = "" def __init__( self, @@ -56,21 +57,24 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods self.completer = NestedCompleter.from_nested_dict(choices) - def check_keys_status(self) -> None: - """Check keys status""" - for api in optional_rich_track(self.API_LIST, desc="Checking keys status"): - try: - self.status_dict[api] = getattr( - keys_model, "check_" + str(api) + "_key" - )() - except Exception: - self.status_dict[api] = str( - keys_model.KeyStatus.DEFINED_TEST_INCONCLUSIVE - ) + def get_check_keys_function(self, key: str) -> Callable: + """Get check `key` function from keys_model""" + return getattr(keys_model, f"check_{key}_key") - def print_help(self): + def check_keys_status(self, reevaluate: bool = False) -> None: + """Check keys status""" + if not self.status_dict or reevaluate: + for api in optional_rich_track(self.API_LIST, desc="Checking keys status"): + try: + self.status_dict[api] = self.get_check_keys_function(api)() + except Exception: + self.status_dict[api] = str( + keys_model.KeyStatus.DEFINED_TEST_INCONCLUSIVE + ) + + def print_help(self, update_status: bool = False, reevaluate: bool = False): """Print help""" - self.check_keys_status() + self.check_keys_status(reevaluate=reevaluate) mt = MenuText("keys/") mt.add_param( "_source", @@ -82,32 +86,32 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods mt.add_info("_keys_") mt.add_raw("\n") mt.add_cmd("mykeys") + mt.add_cmd("status") mt.add_raw("\n") mt.add_info("_status_") - for cmd_name, status_msg in self.status_dict.items(): - api_name = self.API_DICT[cmd_name] + status_color_map = { + keys_model.KeyStatus.DEFINED_TEST_PASSED.value: "green", + keys_model.KeyStatus.DEFINED_TEST_FAILED.value: "red", + keys_model.KeyStatus.DEFINED_NOT_TESTED.value: "yellow", + keys_model.KeyStatus.DEFINED_TEST_INCONCLUSIVE.value: "yellow", + keys_model.KeyStatus.NOT_DEFINED.value: "grey30", + } - c = "grey30" - if status_msg == str(keys_model.KeyStatus.DEFINED_TEST_PASSED): - c = "green" - elif status_msg == str(keys_model.KeyStatus.DEFINED_TEST_FAILED): - c = "red" - elif status_msg == str(keys_model.KeyStatus.DEFINED_NOT_TESTED): - c = "yellow" - elif status_msg == str(keys_model.KeyStatus.DEFINED_TEST_INCONCLUSIVE): - c = "yellow" - elif status_msg == str(keys_model.KeyStatus.NOT_DEFINED): - c = "grey30" + if not self.help_status_text or update_status: + for cmd_name, status_msg in self.status_dict.items(): + api_name = self.API_DICT[cmd_name] - if status_msg is None: - status_msg = str(keys_model.KeyStatus.NOT_DEFINED) - mt.add_raw( - f" [cmds]{cmd_name}[/cmds] {(20 - len(cmd_name)) * ' '}" - f" [{c}] {api_name} {(25 - len(api_name)) * ' '} {translate(status_msg)} [/{c}]\n" - ) + c = status_color_map.get(status_msg, "grey30") + status_msg = status_msg or keys_model.KeyStatus.NOT_DEFINED.value - console.print(text=mt.menu_text, menu="Keys") + mt.add_raw( + f" [cmds]{cmd_name}[/cmds] {(20 - len(cmd_name)) * ' '}" + f" [{c}] {api_name} {(25 - len(api_name)) * ' '} {translate(status_msg)} [/{c}]\n" + ) + self.help_status_text = mt.menu_text + + console.print(text=self.help_status_text, menu="Keys") @log_start_end(log=logger) def call_mykeys(self, other_args: List[str]): @@ -119,7 +123,12 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods description="Display current keys.", ) parser.add_argument( - "-s", "--show", type=bool, dest="show", help="show", default=False + "-s", + "--show", + type=bool, + dest="show", + help="See keys in raw text instead of the default protected display.", + default=False, ) if other_args and "-s" in other_args[0]: other_args.insert(1, "True") @@ -137,6 +146,50 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods else None, ) + @log_start_end(log=logger) + def call_status(self, other_args: List[str]): + """Display current keys status with the option to force the check""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="status", + description="Reevaluate keys status.", + ) + parser.add_argument( + "-k", + "--key", + type=str, + dest="key", + help="Force the status to reevaluate for a specific key.", + default=None, + ) + + if other_args and "-" not in other_args[0][0]: + other_args.insert(0, "-k") + + ns_parser = self.parse_known_args_and_warn( + parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED + ) + + is_key = bool(ns_parser and ns_parser.key) + + if is_key: + if ns_parser.key in self.API_LIST: + try: + status = self.get_check_keys_function(ns_parser.key)(True) + self.status_dict[ns_parser.key] = status + console.print("") + except Exception: + status = keys_model.KeyStatus.DEFINED_TEST_INCONCLUSIVE + self.status_dict[ns_parser.key] = str(status) + console.print(f"{status.colorize()}\n") + else: + console.print(f"[red]Key `{ns_parser.key}` is not a valid key.[/red]") + return + + if "-h" not in other_args and "--help" not in other_args: + self.print_help(update_status=True, reevaluate=not is_key) + @log_start_end(log=logger) def call_av(self, other_args: List[str]): """Process av command""" diff --git a/openbb_terminal/keys_model.py b/openbb_terminal/keys_model.py index f80e70e1b61..4a7b2a4cf8c 100644 --- a/openbb_terminal/keys_model.py +++ b/openbb_terminal/keys_model.py @@ -49,8 +49,9 @@ logger = logging.getLogger(__name__) # This format is used by the KeysController and get_keys_info(). # E.g. tokenterminal -> set_tokenterminal_key & check_tokenterminal_key # -# Don't forget to add the set function to api.py endpoints dictionary. -# E.g. "keys.tokenterminal": {"model": "openbb_terminal.keys_model.set_tokenterminal_key"}, +# Don't forget to add it to the SDK. +# E.g. `keys.av,keys_model.set_av_key` +# For more info, please refer to the CONTRIBUTING.md file. API_DICT: Dict = { "av": "ALPHA_VANTAGE", diff --git a/openbb_terminal/miscellaneous/i18n/en.yml b/openbb_terminal/miscellaneous/i18n/en.yml index e31cda176ab..d93795ec39b 100644 --- a/openbb_terminal/miscellaneous/i18n/en.yml +++ b/openbb_terminal/miscellaneous/i18n/en.yml @@ -35,6 +35,7 @@ en: keys/_source: Source keys/_keys_: This menu allows you to set your own API keys. Type 'about' for more information keys/mykeys: get your defined keys + keys/status: reevaluate keys status keys/_status_: Status defined, test passed: defined, test passed defined, test failed: defined, test failed diff --git a/tests/openbb_terminal/common/json/test_ultima_newsmonitor_model/test_get_company_info[AAPL-response0].json b/tests/openbb_terminal/common/json/test_ultima_newsmonitor_model/test_get_company_info[AAPL-response0].json new file mode 100644 index 00000000000..a8d302ef16b --- /dev/null +++ b/tests/openbb_terminal/common/json/test_ultima_newsmonitor_model/test_get_company_info[AAPL-response0].json @@ -0,0 +1 @@ +{"companyShortName": "Apple", "companyFullName": "Apple Inc.", "risks": ["Competition Risk"], "primary_industry": "Consumer Electronics", "primary_sector": "Technology"} \ No newline at end of file diff --git a/tests/openbb_terminal/common/json/test_ultima_newsmonitor_model/test_get_company_info[AAPL].json b/tests/openbb_terminal/common/json/test_ultima_newsmonitor_model/test_get_company_info[AAPL].json deleted file mode 100644 index 3b3687d18a4..00000000000 --- a/tests/openbb_terminal/common/json/test_ultima_newsmonitor_model/test_get_company_info[AAPL].json +++ /dev/null @@ -1 +0,0 @@ -{"message": "Invalid Authorization Header"} \ No newline at end of file diff --git a/tests/openbb_terminal/common/json/test_ultima_newsmonitor_model/test_get_company_info[FCX-response2].json b/tests/openbb_terminal/common/json/test_ultima_newsmonitor_model/test_get_company_info[FCX-response2].json new file mode 100644 index 00000000000..03ae2ee3fca --- /dev/null +++ b/tests/openbb_terminal/common/json/test_ultima_newsmonitor_model/test_get_company_info[FCX-response2].json @@ -0,0 +1 @@ +{"companyShortName": "Freeport-McMoRan", "companyFullName": "Freeport-McMoRan Inc.", "risks": ["Competition Risk"], "primary_industry": "Metals & Mining", "primary_sector": "Basic Materials"} \ No newline at end of file diff --git a/tests/openbb_terminal/common/json/test_ultima_newsmonitor_model/test_get_company_info[FCX].json b/tests/openbb_terminal/common/json/test_ultima_newsmonitor_model/test_get_company_info[FCX].json deleted file mode 100644 index 3b3687d18a4..00000000000 --- a/tests/openbb_terminal/common/json/test_ultima_newsmonitor_model/test_get_company_info[FCX].json +++ /dev/null @@ -1 +0,0 @@ -{"message": "Invalid Authorization Header"} \ No newline at end of file diff --git a/tests/openbb_terminal/common/json/test_ultima_newsmonitor_model/test_get_company_info[TSLA-response1].json b/tests/openbb_terminal/common/json/test_ultima_newsmonitor_model/test_get_company_info[TSLA-response1].json new file mode 100644 index 00000000000..7f61e8f0706 --- /dev/null +++ b/tests/openbb_terminal/common/json/test_ultima_newsmonitor_model/test_get_company_info[TSLA-response1].json @@ -0,0 +1 @@ +{"companyShortName": "Tesla", "companyFullName": "Tesla, Inc.", "risks": ["Competition Risk"], "primary_industry": "Auto Manufacturers", "primary_sector": "Consumer Cyclical"} \ No newline at end of file diff --git a/tests/openbb_terminal/common/json/test_ultima_newsmonitor_model/test_get_company_info[TSLA].json b/tests/openbb_terminal/common/json/test_ultima_newsmonitor_model/test_get_company_info[TSLA].json deleted file mode 100644 index 3b3687d18a4..00000000000 --- a/tests/openbb_terminal/common/json/test_ultima_newsmonitor_model/test_get_company_info[TSLA].json +++ /dev/null @@ -1 +0,0 @@ -{"message": "Invalid Authorization Header"} \ No newline at end of file diff --git a/tests/openbb_terminal/common/json/test_ultima_newsmonitor_model/test_get_company_info[asdf$#].json b/tests/openbb_terminal/common/json/test_ultima_newsmonitor_model/test_get_company_info[asdf$#-response3].json similarity index 100% rename from tests/openbb_terminal/common/json/test_ultima_newsmonitor_model/test_get_company_info[asdf$#].json rename to tests/openbb_terminal/common/json/test_ultima_newsmonitor_model/test_get_company_info[asdf$#-response3].json diff --git a/tests/openbb_terminal/common/json/test_ultima_newsmonitor_model/test_supported_terms.json b/tests/openbb_terminal/common/json/test_ultima_newsmonitor_model/test_supported_terms.json deleted file mode 100644 index 60e9aedf500..00000000000 --- a/tests/openbb_terminal/common/json/test_ultima_newsmonitor_model/test_supported_terms.json +++ /dev/null @@ -1 +0,0 @@ -["FCX", "BMY", "SCHW", "WMT", "GM", "EXC", "RTX", "CVX", "MSFT", "NRG", "ACN", "GOOGL", "V", "MDLZ", "F", "DHR", "TSLA", "SO", "AMT", "PG", "GE", "RJF", "MS", "IBM", "VZ", "AAPL", "USB", "LIN", "WAB", "JNJ", "MET", "PEP", "LLY", "KO", "WFC", "ABT", "TMO", "GD", "MCD", "TXN", "BKNG", "CVS", "LOW", "COF", "UNH", "AIG", "PFE", "MO", "ABBV", "JPM", "JEF", "CAT", "BAC", "CL", "MRK", "ORCL", "MA", "META", "CSX", "KHC", "AMD", "TMUS", "CTVA", "UPS", "DIS", "BRK-B", "PM", "AMZN", "LMT", "XOM", "CMCSA", "ADM", "AVGO", "NEE", "HON", "DUK", "COST", "WM", "CRM", "DOW", "CMS", "INTC", "UNP", "SPG", "FDX", "ADBE", "GILD", "MDT", "C", "SBUX", "AA", "QCOM", "AMGN", "BA", "BLK", "GOOG", "T", "WBA", "NVDA", "CSCO", "AXP", "COP", "BK", "NKE", "EMR", "CHTR", "MMM", "HD", "CF", "SF", "TGT", "GS", "PYPL", "STLD", "NFLX", "AMPH", "ASYS", "CPRX", "DAL", "KKR", "PRPH", "RUM", "SAVE", "STAG", "SYK", "TITN", "MODG", "WBD", "CELH", "KR", "SKLZ", "MSGS", "HUBS", "BABA", "UBER", "DG", "SQ", "BX", "SOFI", "TPC", "CLVT", "STKS", "DWAC", "WSM", "TRMB", "CZR", "IQV", "JKHY", "SEE", "NTRS", "LAW", "TTWO", "ZBRA", "BEN", "AMCR", "RIVN", "K", "CSV", "LH", "MNTV", "ATRA", "NTAP", "CMA", "CRWD", "PFG", "FSLR", "LYB", "FTV", "TFC", "BIIB", "PKI", "ETR", "FDS", "SWKS", "OSIS", "HSIC", "KLAC", "CYTK", "IT", "NDAQ", "BALL", "SIRI", "DLR", "DFS", "PGR", "MOH", "STZ", "IFF", "UHS", "ZBH", "RBLX", "MGM", "WAL", "NDSN", "ENR", "CHRS", "CB", "EQR", "ADSK", "CME", "APA", "HRL", "OGN", "SLB", "FICO", "EZPW", "X", "IDXX", "KTOS", "BDX", "WTW", "CCL", "BR", "MOS", "IAC", "CLX", "NWS", "WAFD", "WHR", "MELI", "ABC", "EFX", "LRCX", "HPQ", "STE", "QTWO", "LVS", "LDOS", "TPR", "MPWR", "ALK", "WAT", "ABML", "LNT", "NEM", "WYNN", "AJG", "AAL", "CCI", "IEX", "AES", "ADI", "MTCH", "FAST", "AIZ", "VMC", "SBAC", "BIGC", "CDW", "BBBY", "EA", "NVR", "SRE", "DVN", "CAG", "ZTS", "AME", "CTLT", "FOX", "NCLH", "FITB", "PWR", "TAP", "WEC", "TDG", "FLT", "J", "VLO", "CBRE", "DLTR", "NOC", "MAS", "HPE", "PCG", "ES", "PARA", "CEG", "POOL", "HST", "SNA", "ABNB", "SEMR", "WVE", "GWW", "DPZ", "AMAT", "PEG", "SWK", "JCI", "VTR", "FMC", "WMB", "KEYS", "MAR", "AVB", "MKC", "MRVL", "MNST", "CHD", "BG", "ROP", "EL", "HII", "ZION", "QRVO", "MCK", "PNR", "ON", "XRAY", "NWSA", "INTA", "JBHT", "PSX", "CRL", "BCO", "JNPR", "HES", "REG", "DTE", "PXD", "HLT", "APTV", "GWRE", "BBW", "OVV", "ATO", "BLKB", "ANSS", "CTRA", "CMI", "HSY", "CSL", "WDAY", "CFG", "GPC", "KIM", "GL", "DD", "MMC", "PKG", "ITW", "WST", "OKE", "UAL", "PAYX", "PCAR", "HOLX", "EQT", "INCY", "CPRT", "AVY", "WDC", "SYF", "BSX", "SG", "DDOG", "SHW", "BXP", "TECH", "BF-B", "MCO", "LEN", "CTAS", "PLUG", "TRGP", "ROST", "APH", "ED", "CHRW", "PLTR", "RF", "EQIX", "BKR", "SPT", "PH", "DXCM", "EW", "KMB", "COO", "ELV", "AZO", "ESS", "WRB", "MPC", "XYL", "LW", "EXPE", "PAYC", "DHI", "VTRS", "NOW", "EPAM", "VRSK", "HBAN", "STX", "EXPD", "DRI", "CMG", "HCA", "LVOX", "LCID", "D", "SCCO", "CI", "ETN", "GLW", "SNPS", "BIO", "GPK", "ODFL", "PHM", "WRK", "CE", "URI", "PRU", "CINF", "INVH", "ILMN", "MHK", "ORLY", "UDR", "GRMN", "MKTX", "PPL", "VRTX", "ROK", "DVA", "ICE", "CNP", "MRO", "CPB", "OMC", "KEY", "IRM", "SPGI", "EIX", "TER", "SYY", "BWA", "ANET", "EVBG", "ALLY", "ECL", "CDNS", "FE", "CNC", "ENPH", "AKAM", "PANW", "CC", "NSC", "DE", "PLD", "TRV", "DOV", "UBX", "NFG", "DGX", "MSCI", "SEDG", "EVRG", "AFL", "GEN", "EMN", "ENFN", "KMI", "PODD", "GIS", "TJX", "TEL", "PNW", "TFX", "L", "MED", "OXY", "UGI", "REGN", "GME", "RE", "ZM", "FISV", "ATVI", "BBIO", "MSI", "CAH", "ALB", "IR", "TSCO", "FIS", "CPT", "AMC", "TSN", "HWM", "LZB", "FOXA", "EBAY", "MTB", "TYL", "LKQ", "AWK", "IVZ", "INTU", "RCL", "TWLO", "GPN", "PRO", "KMX", "CHPT", "AOS", "CSGP", "MU", "ZI", "XEL", "ADP", "RSG", "MRNA", "LNC", "VICI", "MTD", "PTC", "DISH", "HUM", "ACGL", "NI", "DXC", "BRO", "SPSC", "NET", "VFC", "WELL", "LHX", "RL", "LYV", "SIVB", "PII", "APD", "PRI", "LUV", "FRC", "EOG", "MAA", "FANG", "AEE", "GEHC", "EGAN", "OTIS", "YUM", "DCT", "TT", "CXM", "VRSN", "FFIV", "NKTX", "FTNT", "LULU", "SJM", "ALGN", "KDP", "AMP", "HAS", "PEAK", "ETSY", "EXR", "CARR", "PPG", "MTTR", "O", "AAP", "RMD", "BBWI", "VSTO", "WY", "DKS", "ALL", "BBY", "AON", "TXT", "FRT", "RHI", "HIG", "MCHP", "ASMB", "PSA", "PNC", "SGEN", "STT", "ROL", "MLM", "NWL", "CDAY", "IP", "TROW", "INSE", "BAX", "GNRC", "MLNK", "NXPI", "ULTA", "ZS", "AEP", "COLL", "ALLE", "IPG", "TDY", "NUE", "A", "CBOE", "CTSH", "ARE", "HAL", "LAZR", "ISRG"] \ No newline at end of file diff --git a/tests/openbb_terminal/common/test_ultima_newsmonitor_model.py b/tests/openbb_terminal/common/test_ultima_newsmonitor_model.py index ffbb2c78ef7..98660b57c27 100644 --- a/tests/openbb_terminal/common/test_ultima_newsmonitor_model.py +++ b/tests/openbb_terminal/common/test_ultima_newsmonitor_model.py @@ -10,6 +10,36 @@ from openbb_terminal.common.ultima_newsmonitor_model import ( supported_terms, ) +MOCK_REQUEST_RESPONSE = { + "TSLA": { + "companyShortName": "Tesla", + "companyFullName": "Tesla, Inc.", + "risks": [ + "Competition Risk", + ], + "primary_industry": "Auto Manufacturers", + "primary_sector": "Consumer Cyclical", + }, + "AAPL": { + "companyShortName": "Apple", + "companyFullName": "Apple Inc.", + "risks": [ + "Competition Risk", + ], + "primary_industry": "Consumer Electronics", + "primary_sector": "Technology", + }, + "FCX": { + "companyShortName": "Freeport-McMoRan", + "companyFullName": "Freeport-McMoRan Inc.", + "risks": [ + "Competition Risk", + ], + "primary_industry": "Metals & Mining", + "primary_sector": "Basic Materials", + }, +} + @pytest.mark.vcr @pytest.mark.parametrize( @@ -27,19 +57,48 @@ def test_get_news(term, recorder): recorder.capture(df) -# @pytest.mark.default_cassette("test_supported_terms.yaml") -# @pytest.mark.vcr -def test_supported_terms(recorder): - r = supported_terms() - recorder.capture(r) +def test_supported_terms(mocker): + # MOCK REQUEST + attrs = { + "status_code": 200, + "json.return_value": list(MOCK_REQUEST_RESPONSE.keys()), + } + mock_response = mocker.Mock(**attrs) + + mocker.patch( + target="openbb_terminal.common.ultima_newsmonitor_model.request", + new=mocker.Mock(return_value=mock_response), + ) + terms = supported_terms() + assert terms == list(MOCK_REQUEST_RESPONSE.keys()) -# @pytest.mark.default_cassette("test_get_company_info.yaml") -# @pytest.mark.vcr @pytest.mark.parametrize( - "ticker", - ["AAPL", "TSLA", "FCX", "asdf$#"], + "ticker, response", + [ + ("AAPL", MOCK_REQUEST_RESPONSE["AAPL"]), + ("TSLA", MOCK_REQUEST_RESPONSE["TSLA"]), + ("FCX", MOCK_REQUEST_RESPONSE["FCX"]), + ("asdf$#", {}), + ], ) -def test_get_company_info(ticker, recorder): +def test_get_company_info(ticker, response, recorder, mocker): + # MOCK SUPPORTED TERMS + mocker.patch( + target="openbb_terminal.common.ultima_newsmonitor_model.supported_terms", + new=mocker.Mock(return_value=list(MOCK_REQUEST_RESPONSE.keys())), + ) + + # MOCK REQUEST + attrs = { + "status_code": 200, + "json.return_value": response, + } + mock_response = mocker.Mock(**attrs) + + mocker.patch( + target="openbb_terminal.common.ultima_newsmonitor_model.request", + new=mocker.Mock(return_value=mock_response), + ) df = get_company_info(ticker=ticker) recorder.capture(df)