Set guest by default and put authentication behind environment variable (#4197)

* small bug

* small bug on routines list

* guest as default

* limit logout to users2

* simpler this way

* add logout

* move logout to account & add env var

* put authentication under env var

* fix tests

* remove login

* remove openbb vars on login

* ruff

* revert .toml

* Delete Icon

* Update .env

* fix sdk guest not loading keys

* ruff

* remove press enter & --login

* ruff

* move env load

* load env

* move dot env loading to terminal.py

* fix user sdk login mixing .env with user

* now it works

* fix reset

* fix tests

* ruff

* Login on the SDK using the OpenBB Personal Access Token (#4206)

* openbb key on keys menu

* adding sdk endpoints

* adding login via token to the sdk

* base url

* rename to personal access token

* added docstrings and examples

* openbb is special

* removing extra break line

* making keys working with the personal access token

* resolve conflicting tests

* Revert "resolve conflicting tests"

This reverts commit 2fca2c0ec3.

* using custom requests

* tests

* tests

* tests

* using process_session_response instead

* rewriting some tests

* Fix sdk guest not loading keys (#4207)

* fix sdk guest not loading keys

* ruff

* remove comment

* tests

---------

Co-authored-by: montezdesousa <79287829+montezdesousa@users.noreply.github.com>
Co-authored-by: Diogo Sousa <montezdesousa@gmail.com>
Co-authored-by: James Maslek <jmaslek11@gmail.com>

* test record mode vcr none

* remove yes from tests yml

* specify modules to reload

* try disable check api

* UPdate spec file

* Small stocks bug

* avoid reloading in test mode

* will this work?

* skip session tests

* try with env var as well

* mock sdk reload

* pylint

* bump poetry on python 3.8

* adapt to python 3.8

* adapt to python 3.8 remaining tests

* Tests : update cassettes

* Fix linting

* Makes login conditions simpler

* Tests : txt

---------

Co-authored-by: James Maslek <jmaslek11@gmail.com>
Co-authored-by: Chavithra <chavithra@gmail.com>
Co-authored-by: Henrique Joaquim <h.joaquim@campus.fct.unl.pt>
This commit is contained in:
montezdesousa
2023-02-14 09:56:03 +00:00
committed by GitHub
parent c2f88d4499
commit 825b8955df
52 changed files with 2099 additions and 1758 deletions

View File

@@ -90,4 +90,4 @@ jobs:
- name: Start Terminal and exit
shell: bash -l {0}
run: yes "" | python terminal.py
run: python terminal.py

View File

@@ -6,6 +6,7 @@ env:
OPENBB_USE_ION: false
OPENBB_USE_PROMPT_TOOLKIT: false
OPENBB_FILE_OVERWRITE: true
OPENBB_ENABLE_CHECK_API: false
PIP_DEFAULT_TIMEOUT: 100
on:
@@ -102,7 +103,7 @@ jobs:
- name: Start Terminal and exit
run: |
source $VENV
yes "" | python terminal.py
python terminal.py
tests-python:
name: Vanilla Python Tests - ${{ matrix.python-version }}
@@ -129,7 +130,7 @@ jobs:
- name: Install Poetry
uses: snok/install-poetry@v1
with:
version: 1.1.13
version: 1.3.2
virtualenvs-create: true
virtualenvs-in-project: true
@@ -160,7 +161,7 @@ jobs:
- name: Start Terminal and exit
run: |
source $VENV
yes "" | python terminal.py
python terminal.py
full-test:
name: Full Test Suite - (${{ matrix.python-version }}, ${{ matrix.os }})
@@ -188,7 +189,7 @@ jobs:
- name: Install Poetry
uses: snok/install-poetry@v1
with:
version: 1.1.13
version: 1.3.2
virtualenvs-create: true
virtualenvs-in-project: true
@@ -219,7 +220,7 @@ jobs:
- name: Start Terminal and exit
run: |
source $VENV
yes "" | python terminal.py
python terminal.py
tests-conda:
name: Anaconda Python Tests - (${{ matrix.python-version }}, ${{ matrix.os }})
@@ -324,4 +325,4 @@ jobs:
- name: Start Terminal and exit
shell: bash -l {0}
run: yes "" | python terminal.py
run: python terminal.py

View File

@@ -104,4 +104,4 @@ jobs:
- name: Start Terminal and exit
shell: bash -l {0}
run: yes "" | python terminal.py
run: python terminal.py

View File

@@ -69,10 +69,6 @@ added_files = [
os.path.join(pathex, "statsmodels", "datasets"),
os.path.join("statsmodels", "datasets"),
),
(
os.path.join(pathex, "investpy", "resources"),
os.path.join("investpy", "resources"),
),
(
os.path.join(pathex, "debugpy", "_vendored"),
os.path.join("debugpy", "_vendored"),

View File

@@ -21,6 +21,7 @@ from openbb_terminal.session import (
hub_model as Hub,
local_model as Local,
)
from openbb_terminal.session.session_model import logout
from openbb_terminal.session.user import User
logger = logging.getLogger(__name__)
@@ -30,6 +31,7 @@ class AccountController(BaseController):
"""Account Controller Class"""
CHOICES_COMMANDS = [
"logout",
"sync",
"pull",
"clear",
@@ -88,8 +90,29 @@ class AccountController(BaseController):
mt.add_cmd("upload")
mt.add_cmd("download")
mt.add_cmd("delete")
mt.add_raw("\n")
mt.add_info("_authentication_")
mt.add_cmd("logout")
console.print(text=mt.menu_text, menu="Account")
@log_start_end(log=logger)
def call_logout(self, other_args: List[str]) -> None:
"""Process logout command."""
parser = argparse.ArgumentParser(
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog="logout",
description="Logout from current session.",
)
ns_parser = self.parse_known_args_and_warn(parser, other_args)
if ns_parser:
logout(
auth_header=User.get_auth_header(),
token=User.get_token(),
guest=User.is_guest(),
cls=True,
)
@log_start_end(log=logger)
def call_sync(self, other_args: List[str]):
"""Sync"""

View File

@@ -1,9 +1,19 @@
# This is for helpers that do NOT import any OpenBB Modules
import importlib
import logging
import os
from typing import Any, Callable, Literal
import sys
from typing import Any, Callable, List, Literal, Optional
from dotenv import load_dotenv
from rich.console import Console
from openbb_terminal.core.config.paths import (
PACKAGE_ENV_FILE,
REPOSITORY_ENV_FILE,
USER_ENV_FILE,
)
console = Console()
menus = Literal["", "featflags", "settings"]
@@ -85,3 +95,59 @@ def strtobool(val):
raise ValueError(f"invalid truth value {val}")
return output
def load_dotenv_and_reload_configs():
"""Loads the dotenv files in the following order:
1. Repository .env file
2. Package .env file
3. User .env file
This allows the user to override the package settings with their own
settings, and the package to override the repository settings.
openbb_terminal modules are reloaded to refresh config files with new env,
otherwise they will use cache with old variables.
"""
load_dotenv(REPOSITORY_ENV_FILE, override=True)
load_dotenv(PACKAGE_ENV_FILE, override=True)
load_dotenv(USER_ENV_FILE, override=True)
reload_openbb_config_modules()
def reload_openbb_config_modules():
"""Reloads openbb config modules"""
reload_modules(
to_reload=[
"openbb_terminal.config_plot",
"openbb_terminal.config_terminal",
"openbb_terminal.feature_flags",
"openbb_terminal.core.config",
]
)
def reload_modules(to_reload: List[str]):
"""Reloads modules"""
modules = sys.modules.copy()
for module in modules:
if module in to_reload:
importlib.reload(sys.modules[module])
def remove_log_handlers():
"""Remove the log handlers - needs to be done before reloading modules."""
for handler in logging.root.handlers[:]:
logging.root.removeHandler(handler)
def clear_openbb_env_vars(exceptions: Optional[List[str]] = None):
"""Clear openbb environment variables."""
for v in os.environ:
if v.startswith("OPENBB"):
if not exceptions:
os.environ.pop(v)
elif v not in exceptions:
os.environ.pop(v)

View File

@@ -3,6 +3,7 @@ import os
# IMPORTATION INTERNAL
from openbb_terminal.base_helpers import load_env_vars, strtobool
from .helper_classes import TerminalStyle as _TerminalStyle
SENSITIVE_KEYS = [
@@ -194,5 +195,10 @@ API_TOKEN_TERMINAL_KEY = os.getenv("OPENBB_API_TOKEN_TERMINAL_KEY") or "REPLACE_
# https://stocksera.pythonanywhere.com/accounts/developers
API_STOCKSERA_KEY = os.getenv("OPENBB_API_STOCKSERA_KEY") or "REPLACE_ME"
# https://my.openbb.co
OPENBB_PERSONAL_ACCESS_TOKEN = (
os.getenv("OPENBB_OPENBB_PERSONAL_ACCESS_TOKEN") or "REPLACE_ME"
)
# https://intrinio.com/starter-plan
API_INTRINIO_KEY = os.getenv("OPENBB_API_INTRINIO_KEY") or "REPLACE_ME"

View File

@@ -2,22 +2,6 @@
import os
from pathlib import Path
from dotenv import load_dotenv
def load_dotenv_with_priority():
"""Loads the dotenv files in the following order:
1. Repository .env file
2. Package .env file
3. User .env file
This allows the user to override the package settings with their own
settings, and the package to override the repository settings.
"""
load_dotenv(REPOSITORY_ENV_FILE, override=True)
load_dotenv(PACKAGE_ENV_FILE, override=True)
load_dotenv(USER_ENV_FILE, override=True)
def get_user_data_directory():
"""Gets user data path from .env file or returns default path"""

View File

@@ -54,7 +54,14 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
def check_keys_status(self) -> None:
"""Check keys status"""
for api in optional_rich_track(self.API_LIST, desc="Checking keys status"):
self.status_dict[api] = getattr(keys_model, "check_" + str(api) + "_key")()
if api == "openbb":
self.status_dict[api] = getattr(
keys_model, "check_" + str(api) + "_personal_access_token"
)()
else:
self.status_dict[api] = getattr(
keys_model, "check_" + str(api) + "_key"
)()
def print_help(self):
"""Print help"""
@@ -136,7 +143,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
)
if not other_args:
console.print(
"For your API Key, visit: https://www.alphavantage.co/support/#api-key\n"
"For your API Key, visit: https://www.alphavantage.co/support/#api-key"
)
return
@@ -165,9 +172,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
help="key",
)
if not other_args:
console.print(
"For your API Key, visit: https://financialmodelingprep.com\n"
)
console.print("For your API Key, visit: https://financialmodelingprep.com")
return
if other_args and "-" not in other_args[0][0]:
@@ -195,7 +200,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
help="key",
)
if not other_args:
console.print("For your API Key, visit: https://www.quandl.com\n")
console.print("For your API Key, visit: https://www.quandl.com")
return
if other_args and "-" not in other_args[0][0]:
@@ -223,7 +228,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
help="key",
)
if not other_args:
console.print("For your API Key, visit: https://polygon.io\n")
console.print("For your API Key, visit: https://polygon.io")
return
if other_args and "-" not in other_args[0][0]:
@@ -281,7 +286,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
help="key",
)
if not other_args:
console.print("For your API Key, visit: https://fred.stlouisfed.org\n")
console.print("For your API Key, visit: https://fred.stlouisfed.org")
return
if other_args and "-" not in other_args[0][0]:
@@ -309,7 +314,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
help="key",
)
if not other_args:
console.print("For your API Key, visit: https://newsapi.org\n")
console.print("For your API Key, visit: https://newsapi.org")
return
if other_args and "-" not in other_args[0][0]:
@@ -337,7 +342,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
help="key",
)
if not other_args:
console.print("For your API Key, visit: https://developer.tradier.com\n")
console.print("For your API Key, visit: https://developer.tradier.com")
return
if other_args and "-" not in other_args[0][0]:
@@ -365,7 +370,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
help="key",
)
if not other_args:
console.print("For your API Key, visit: https://coinmarketcap.com\n")
console.print("For your API Key, visit: https://coinmarketcap.com")
return
if other_args and "-" not in other_args[0][0]:
other_args.insert(0, "-k")
@@ -392,7 +397,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
help="key",
)
if not other_args:
console.print("For your API Key, visit: https://finnhub.io\n")
console.print("For your API Key, visit: https://finnhub.io")
return
if other_args and "-" not in other_args[0][0]:
other_args.insert(0, "-k")
@@ -453,7 +458,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
nargs="+",
)
if not other_args:
console.print("For your API Key, visit: https://www.reddit.com\n")
console.print("For your API Key, visit: https://www.reddit.com")
return
ns_parser = self.parse_simple_args(parser, other_args)
if ns_parser:
@@ -506,7 +511,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
required="-h" not in other_args,
)
if not other_args:
console.print("For your API Key, visit: https://developer.twitter.com\n")
console.print("For your API Key, visit: https://developer.twitter.com")
return
ns_parser = self.parse_simple_args(parser, other_args)
if ns_parser:
@@ -542,7 +547,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
help="password",
)
if not other_args:
console.print("For your API Key, visit: https://robinhood.com/us/en/\n")
console.print("For your API Key, visit: https://robinhood.com/us/en/")
return
ns_parser = self.parse_simple_args(parser, other_args)
if ns_parser:
@@ -587,7 +592,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
default="",
)
if not other_args:
console.print("For your API Key, visit: https://www.degiro.fr\n")
console.print("For your API Key, visit: https://www.degiro.fr")
return
ns_parser = self.parse_simple_args(parser, other_args)
if ns_parser:
@@ -630,7 +635,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
help="account type ('live' or 'practice')",
)
if not other_args:
console.print("For your API Key, visit: https://developer.oanda.com\n")
console.print("For your API Key, visit: https://developer.oanda.com")
return
ns_parser = self.parse_simple_args(parser, other_args)
if ns_parser:
@@ -668,7 +673,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
required="-h" not in other_args,
)
if not other_args:
console.print("For your API Key, visit: https://binance.com\n")
console.print("For your API Key, visit: https://binance.com")
return
ns_parser = self.parse_simple_args(parser, other_args)
if ns_parser:
@@ -696,7 +701,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
help="key",
)
if not other_args:
console.print("For your API Key, visit: https://bitquery.io/\n")
console.print("For your API Key, visit: https://bitquery.io/")
return
if other_args and "-" not in other_args[0][0]:
other_args.insert(0, "-k")
@@ -740,7 +745,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
required="-h" not in other_args,
)
if not other_args:
console.print("For your API Key, visit: https://docs.pro.coinbase.com/\n")
console.print("For your API Key, visit: https://docs.pro.coinbase.com/")
return
ns_parser = self.parse_simple_args(parser, other_args)
if ns_parser:
@@ -769,7 +774,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
help="key",
)
if not other_args:
console.print("For your API Key, visit: https://docs.whale-alert.io/\n")
console.print("For your API Key, visit: https://docs.whale-alert.io/")
return
if other_args and "-" not in other_args[0][0]:
other_args.insert(0, "-k")
@@ -797,7 +802,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
)
if not other_args:
console.print(
"For your API Key, visit: https://docs.glassnode.com/basic-api/api-key#how-to-get-an-api-key/\n"
"For your API Key, visit: https://docs.glassnode.com/basic-api/api-key#how-to-get-an-api-key/"
)
return
if other_args and "-" not in other_args[0][0]:
@@ -826,7 +831,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
)
if not other_args:
console.print(
"For your API Key, visit: https://coinglass.github.io/API-Reference/#api-key\n"
"For your API Key, visit: https://coinglass.github.io/API-Reference/#api-key"
)
return
if other_args and "-" not in other_args[0][0]:
@@ -855,7 +860,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
)
if not other_args:
console.print(
"For your API Key, visit: https://cryptopanic.com/developers/api/\n"
"For your API Key, visit: https://cryptopanic.com/developers/api/"
)
return
if other_args and "-" not in other_args[0][0]:
@@ -884,7 +889,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
)
if not other_args:
console.print(
"For your API Key, visit: https://github.com/EverexIO/Ethplorer/wiki/Ethplorer-API\n"
"For your API Key, visit: https://github.com/EverexIO/Ethplorer/wiki/Ethplorer-API"
)
return
if other_args and "-" not in other_args[0][0]:
@@ -921,7 +926,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
required="-h" not in other_args,
)
if not other_args:
console.print("For your API Key, visit: https://www.smartstake.io\n")
console.print("For your API Key, visit: https://www.smartstake.io")
return
ns_parser = self.parse_simple_args(parser, other_args)
@@ -951,7 +956,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
)
if not other_args:
console.print(
"For your API Key, visit: https://docs.github.com/en/rest/guides/getting-started-with-the-rest-api\n"
"For your API Key, visit: https://docs.github.com/en/rest/guides/getting-started-with-the-rest-api"
)
return
@@ -980,7 +985,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
help="key",
)
if not other_args:
console.print("For your API Key, visit: https://messari.io/api/docs\n")
console.print("For your API Key, visit: https://messari.io/api/docs")
return
if other_args and "-" not in other_args[0][0]:
@@ -1009,7 +1014,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
)
if not other_args:
console.print(
"For your API Key, visit: https://eodhistoricaldata.com/r/?ref=869U7F4J\n"
"For your API Key, visit: https://eodhistoricaldata.com/r/?ref=869U7F4J"
)
return
if other_args and "-" not in other_args[0][0]:
@@ -1039,7 +1044,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
if not other_args:
console.print(
"For your API Key, visit: "
"https://academy.santiment.net/products-and-plans/create-an-api-key\n"
"https://academy.santiment.net/products-and-plans/create-an-api-key"
)
return
if other_args and "-" not in other_args[0][0]:
@@ -1068,7 +1073,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
)
if not other_args:
console.print(
"For your API Key, visit: https://sdk.flipsidecrypto.xyz/shroomdk\n"
"For your API Key, visit: https://sdk.flipsidecrypto.xyz/shroomdk"
)
return
@@ -1097,7 +1102,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
help="key",
)
if not other_args:
console.print("For your API Key, visit: https://tokenterminal.com/\n")
console.print("For your API Key, visit: https://tokenterminal.com/")
return
if other_args and "-" not in other_args[0][0]:
@@ -1126,7 +1131,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
)
if not other_args:
console.print(
"For your API Key, https://stocksera.pythonanywhere.com/accounts/developers\n"
"For your API Key, https://stocksera.pythonanywhere.com/accounts/developers"
)
return
@@ -1137,3 +1142,31 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
self.status_dict["stocksera"] = keys_model.set_stocksera_key(
key=ns_parser.key, persist=True, show_output=True
)
@log_start_end(log=logger)
def call_openbb(self, other_args: List[str]):
"""Process openbb command"""
parser = argparse.ArgumentParser(
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog="openbb",
description="Set OpenBB Personal Access Token. ",
)
parser.add_argument(
"-k",
"--key",
type=str,
dest="key",
help="key",
)
if not other_args:
console.print("For your Personal Access Token, visit: https://openbb.co/")
return
if other_args and "-" not in other_args[0][0]:
other_args.insert(0, "-k")
ns_parser = self.parse_simple_args(parser, other_args)
if ns_parser:
self.status_dict["openbb"] = keys_model.set_openbb_personal_access_token(
key=ns_parser.key, persist=True, show_output=True
)

View File

@@ -5,6 +5,7 @@ __docformat__ = "numpy"
import contextlib
import io
import json
import logging
import os
import sys
@@ -35,7 +36,8 @@ from openbb_terminal.cryptocurrency.coinbase_helpers import (
from openbb_terminal.helper_funcs import request
from openbb_terminal.portfolio.brokers.degiro.degiro_model import DegiroModel
from openbb_terminal.rich_config import console
from openbb_terminal.session.hub_model import patch_user_configs
from openbb_terminal.session.hub_model import BASE_URL, patch_user_configs
from openbb_terminal.session.local_model import SESSION_FILE_PATH
from openbb_terminal.session.user import User
from openbb_terminal.terminal_helper import suppress_stdout
@@ -66,6 +68,7 @@ API_DICT: Dict = {
"rh": "ROBINHOOD",
"degiro": "DEGIRO",
"oanda": "OANDA",
"openbb": "OPENBB",
"binance": "BINANCE",
"bitquery": "BITQUERY",
"coinbase": "COINBASE",
@@ -243,7 +246,7 @@ def set_key(env_var_name: str, env_var_value: str, persist: bool = False) -> Non
not User.is_guest()
and User.is_sync_enabled()
and env_var_name not in cfg.SENSITIVE_KEYS
and env_var_name.startswith("API_")
and (env_var_name.startswith("API_") or env_var_name.startswith("OPENBB_"))
):
patch_user_configs(
key=env_var_name,
@@ -280,14 +283,17 @@ def get_keys(show: bool = False) -> pd.DataFrame:
# TODO: Refactor api variables without prefix API_ and extend API_SOURCE_KEY format
var_list = [v for v in dir(cfg) if v.startswith("API_")]
var_list = [v for v in dir(cfg) if v.startswith("API_") or v.startswith("OPENBB_")]
current_keys = {}
for cfg_var_name in var_list:
cfg_var_value = getattr(cfg, cfg_var_name)
if cfg_var_value != "REPLACE_ME":
current_keys[cfg_var_name[4:]] = cfg_var_value
if cfg_var_name.startswith("OPENBB_"):
current_keys[cfg_var_name] = cfg_var_value
else:
current_keys[cfg_var_name[4:]] = cfg_var_value
if current_keys:
df = pd.DataFrame.from_dict(current_keys, orient="index")
@@ -2485,6 +2491,86 @@ def check_stocksera_key(show_output: bool = False):
return str(status)
def set_openbb_personal_access_token(
key: str, persist: bool = False, show_output: bool = False
):
"""Set OpenBB Personal Access Token.
Parameters
----------
key: str
Personal Access Token
persist: bool, optional
If False, api key change will be contained to where it was changed. For example, a Jupyter notebook session.
If True, api key change will be global, i.e. it will affect terminal environment variables.
By default, False.
show_output: bool, optional
Display status string or not. By default, False.
Returns
-------
str
Status of key set
Examples
--------
>>> from openbb_terminal.sdk import openbb
>>> openbb.keys.openbb(key="example_key")
"""
set_key("OPENBB_OPENBB_PERSONAL_ACCESS_TOKEN", key, persist)
return check_openbb_personal_access_token(show_output)
def check_openbb_personal_access_token(show_output: bool = False):
"""Check OpenBB Personal Access Token
Returns
-------
str
Status of key set
"""
if cfg.OPENBB_PERSONAL_ACCESS_TOKEN == "REPLACE_ME":
logger.info("OpenBB Personal Access Token not defined")
status = KeyStatus.NOT_DEFINED
else:
try:
access_token = ""
# TODO: is there a better way to test the key?
# This requires a valid session file
if os.path.isfile(SESSION_FILE_PATH):
with open(SESSION_FILE_PATH) as f:
access_token = json.load(f).get("access_token")
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json",
}
response = request(
url=f"{BASE_URL}sdk/token", method="GET", headers=headers
)
token = response.json().get("token")
if (
response.status_code == 200
and token == cfg.OPENBB_PERSONAL_ACCESS_TOKEN
):
logger.info("OpenBB Personal Access Token defined, test passed")
status = KeyStatus.DEFINED_TEST_PASSED
else:
logger.warning("OpenBB Personal Access Token. defined, test failed")
status = KeyStatus.DEFINED_TEST_FAILED
except requests.exceptions.RequestException:
logger.warning("OpenBB Personal Access Token. defined, test failed")
status = KeyStatus.DEFINED_TEST_FAILED
if show_output:
console.print(status.colorize())
return str(status)
def set_intrinio_key(key: str, persist: bool = False, show_output: bool = False) -> str:
"""Set Intrinio key

View File

@@ -18,6 +18,8 @@ en:
exe: execute .openbb routine scripts (use exe --example for an example)
_configure_: Configure your own terminal
_main_menu_: Main menu
account/_authentication_: Authentication
account/logout: Log out from OpenBB account
account/_info_: Cloud storage of keys, settings and feature flags
account/sync: Turns the cloud synchronization on/off
account/pull: Pull data from cloud

View File

@@ -533,6 +533,7 @@ keys.santiment,openbb_terminal.keys_model.set_santiment_key,
keys.tokenterminal,openbb_terminal.keys_model.set_tokenterminal_key,
keys.shroom,openbb_terminal.keys_model.set_shroom_key,
keys.stocksera,openbb_terminal.keys_model.set_stocksera_key,
keys.openbb,openbb_terminal.keys_model.set_openbb_personal_access_token,
futures.search,openbb_terminal.futures.yfinance_model.get_search_futures,
futures.historical,openbb_terminal.futures.yfinance_model.get_historical_futures,openbb_terminal.futures.yfinance_view.display_historical
futures.curve,openbb_terminal.futures.yfinance_model.get_curve_futures,openbb_terminal.futures.yfinance_view.display_curve
@@ -541,3 +542,4 @@ stocks.disc.filings,openbb_terminal.stocks.fundamental_analysis.fmp_model.get_fi
stocks.fa.sec_fmp,openbb_terminal.stocks.fundamental_analysis.fmp_model.get_filings,openbb_terminal.stocks.fundamental_analysis.fmp_view.display_filings
login,openbb_terminal.session.sdk_session.login,
logout,openbb_terminal.session.sdk_session.logout,
whoami,openbb_terminal.session.sdk_session.whoami,
1 trail model view
533 keys.tokenterminal openbb_terminal.keys_model.set_tokenterminal_key
534 keys.shroom openbb_terminal.keys_model.set_shroom_key
535 keys.stocksera openbb_terminal.keys_model.set_stocksera_key
536 keys.openbb openbb_terminal.keys_model.set_openbb_personal_access_token
537 futures.search openbb_terminal.futures.yfinance_model.get_search_futures
538 futures.historical openbb_terminal.futures.yfinance_model.get_historical_futures openbb_terminal.futures.yfinance_view.display_historical
539 futures.curve openbb_terminal.futures.yfinance_model.get_curve_futures openbb_terminal.futures.yfinance_view.display_curve
542 stocks.fa.sec_fmp openbb_terminal.stocks.fundamental_analysis.fmp_model.get_filings openbb_terminal.stocks.fundamental_analysis.fmp_view.display_filings
543 login openbb_terminal.session.sdk_session.login
544 logout openbb_terminal.session.sdk_session.logout
545 whoami openbb_terminal.session.sdk_session.whoami

View File

@@ -45,10 +45,9 @@ from openbb_terminal.helper_funcs import (
)
from openbb_terminal.menu import session
from openbb_terminal.rich_config import console, get_ordered_list_sources
from openbb_terminal.session.session_model import logout
from openbb_terminal.session.user import User
from openbb_terminal.stocks import stocks_helper
from openbb_terminal.terminal_helper import open_openbb_documentation
from openbb_terminal.terminal_helper import is_auth_enabled, open_openbb_documentation
logger = logging.getLogger(__name__)
@@ -96,10 +95,11 @@ class BaseController(metaclass=ABCMeta):
"record",
"stop",
"screenshot",
"logout",
"whoami",
]
if is_auth_enabled():
CHOICES_COMMON += ["whoami"]
CHOICES_COMMANDS: List[str] = []
CHOICES_MENUS: List[str] = []
SUPPORT_CHOICES: dict = {}
@@ -385,6 +385,9 @@ class BaseController(metaclass=ABCMeta):
self.log_queue()
if not self.queue or (self.queue and self.queue[0] not in ("quit", "help")):
console.print()
return self.queue
@log_start_end(log=logger)
@@ -675,25 +678,6 @@ class BaseController(metaclass=ABCMeta):
if ns_parser:
screenshot()
@log_start_end(log=logger)
def call_logout(self, other_args: List[str]) -> None:
"""Process logout command."""
parser = argparse.ArgumentParser(
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog="logout",
description="Logout from OpenBB",
)
ns_parser = self.parse_simple_args(parser, other_args)
if ns_parser:
logout(
auth_header=User.get_auth_header(),
token=User.get_token(),
guest=User.is_guest(),
cls=True,
)
@log_start_end(log=logger)
def call_whoami(self, other_args: List[str]) -> None:
"""Process whoami command."""
@@ -707,7 +691,6 @@ class BaseController(metaclass=ABCMeta):
if ns_parser:
User.whoami()
console.print("")
@staticmethod
def parse_simple_args(parser: argparse.ArgumentParser, other_args: List[str]):
@@ -964,11 +947,6 @@ class BaseController(metaclass=ABCMeta):
if an_input == "logout":
return ["logout"]
if not self.queue or (
self.queue and self.queue[0] not in ("quit", "help")
):
console.print()
except SystemExit:
if not self.contains_keys(an_input):
logger.exception(

View File

@@ -1,13 +1,18 @@
# pylint: disable=unused-import
from openbb_terminal import helper_funcs as helper # noqa: F401
from openbb_terminal.base_helpers import load_dotenv_and_reload_configs
from openbb_terminal.config_terminal import theme # noqa: F401
from openbb_terminal.core.library.breadcrumb import Breadcrumb, MetadataBuilder
from openbb_terminal.core.library.trail_map import TrailMap
from openbb_terminal.cryptocurrency.due_diligence.pycoingecko_model import ( # noqa: F401
Coin,
from openbb_terminal.cryptocurrency.due_diligence.pycoingecko_model import (
Coin, # noqa: F401
)
from openbb_terminal.helper_classes import TerminalStyle
from openbb_terminal.reports import widget_helpers as widgets # noqa: F401
from openbb_terminal.session.user import User
if User.is_guest():
load_dotenv_and_reload_configs()
TerminalStyle().applyMPLstyle()
trail = ""

View File

@@ -4,8 +4,9 @@ import requests
from openbb_terminal.rich_config import console
REGISTER_LINK = "https://my.openbb.dev/register"
REGISTER_URL = "https://my.openbb.dev/register"
BASE_URL = "https://payments.openbb.dev/"
# BASE_URL = "http://127.0.0.1:8000/"
TIMEOUT = 15
CONNECTION_ERROR_MSG = "[red]Connection error.[/red]"
@@ -51,6 +52,36 @@ def create_session(
return None
def create_session_from_token(
token: str, base_url: str = BASE_URL, timeout: int = TIMEOUT
):
"""Create a session from token.
Parameters
----------
token : str
The token.
base_url : str
The base url, by default BASE_URL
timeout : int
The timeout, by default TIMEOUT
"""
try:
data = {
"token": token,
}
return requests.post(url=base_url + "sdk/login", json=data, timeout=timeout)
except requests.exceptions.ConnectionError:
console.print("\n[red]Connection error.[/red]")
return None
except requests.exceptions.Timeout:
console.print("\n[red]Connection timeout.[/red]")
return None
except Exception:
console.print("\n[red]Failed to request login info.[/red]")
return None
def delete_session(
auth_header: str, token: str, base_url: str = BASE_URL, timeout: int = TIMEOUT
) -> Optional[requests.Response]:
@@ -140,6 +171,25 @@ def get_session(email: str, password: str) -> dict:
return process_session_response(response)
def get_session_from_token(token: str) -> dict:
"""Get the session info from token.
Parameters
----------
token : str
The token.
Returns
-------
dict
The session info.
"""
response = create_session_from_token(token)
if response is None:
return {}
return process_session_response(response)
def fetch_user_configs(
session: dict, base_url: str = BASE_URL, timeout: int = TIMEOUT
) -> Optional[requests.Response]:

View File

@@ -6,31 +6,89 @@ from openbb_terminal.session import (
from openbb_terminal.session.user import User
def get_session(email: str, password: str, save: bool):
session = session_model.create_session(email, password, save)
def get_session(email: str, password: str, token: str, save: bool):
session = ""
if token:
console.print("Creating session from token.")
session = session_model.create_session_from_token(token, save)
if not session:
console.print("Creating session from email and password.")
session = session_model.create_session(email, password, save)
if not (isinstance(session, dict) and session):
raise Exception("Failed to create session")
raise Exception("Failed to create session.")
return session
def login(email: str = "", password: str = "", keep_session: bool = False):
def login(
email: str = "", password: str = "", token: str = "", keep_session: bool = False
):
"""
Login and load user info.
If there is a saved session it will be used (this can be achieved by `keep_session=True`).
If there's not a local session,
the user can use either email and password or the OpenBB Personal Access Token.
Parameters
----------
email : str
The email.
password : str
The password.
token : str
The OpenBB Personal Access Token.
keep_session : bool
Keep the session, i.e., next time the user logs in,
there is no need to enter the email and password or the token.
Examples
--------
>>> from openbb_terminal.sdk import openbb
>>> openbb.login(email="your_email", password="your_password")
"""
session = Local.get_session()
if not session:
session = get_session(email, password, keep_session)
session = get_session(email, password, token, keep_session)
else:
console.print("Using saved session to login.")
if session_model.login(session) in [
session_model.LoginStatus.FAILED,
session_model.LoginStatus.NO_RESPONSE,
]:
raise Exception("Login failed")
raise Exception("Login failed.")
console.print("[green]Login successful[/green]")
console.print("[green]Login successful.[/green]")
def logout():
"""
Logout and clear session.
Examples
--------
>>> from openbb_terminal.sdk import openbb
>>> openbb.logout()
"""
session_model.logout(
auth_header=User.get_auth_header(),
token=User.get_token(),
guest=User.is_guest(),
)
def whoami():
"""
Display user info.
Examples
--------
>>> from openbb_terminal.sdk import openbb
>>> openbb.whoami()
"""
User.whoami()

View File

@@ -6,7 +6,7 @@ import openbb_terminal.session.local_model as Local
from openbb_terminal import terminal_controller
from openbb_terminal.core.config.paths import PACKAGE_DIRECTORY
from openbb_terminal.rich_config import console
from openbb_terminal.session.hub_model import REGISTER_LINK
from openbb_terminal.session.hub_model import REGISTER_URL
from openbb_terminal.session.session_model import (
LoginStatus,
create_session,
@@ -18,7 +18,7 @@ def display_welcome_message():
"""Display welcome message"""
with open(PACKAGE_DIRECTORY / "session" / "banner.txt") as f:
console.print(f"[menu]{f.read()}[/menu]\n")
console.print(f"Register : [cmds]{REGISTER_LINK}[/cmds]")
console.print(f"Register : [cmds]{REGISTER_URL}[/cmds]")
console.print("Ask support : [cmds]https://openbb.co/support[/cmds]")
console.print(
"[yellow]\nWARNING: This is a pre-release version published for testing.[/yellow]"
@@ -34,9 +34,7 @@ def get_user_input() -> Tuple[str, str, bool]:
Tuple[str, str, bool]
The user email, password and save login option.
"""
console.print(
"[info]\nPlease enter your credentials or press enter for guest mode:[/info]"
)
console.print("[info]\nPlease enter your credentials:[/info]")
s: PromptSession = PromptSession()
@@ -58,7 +56,7 @@ def get_user_input() -> Tuple[str, str, bool]:
return email, password, save
def prompt(welcome=True, guest_allowed=False):
def prompt(welcome=True):
"""Prompt and launch terminal if login is successful.
Parameters
@@ -72,13 +70,11 @@ def prompt(welcome=True, guest_allowed=False):
while True:
email, password, save = get_user_input()
if not email or not password:
if guest_allowed:
return launch_terminal()
continue
session = create_session(email, password, save)
if isinstance(session, dict) and session:
return login_and_launch(session=session, guest_allowed=guest_allowed)
return login_and_launch(session=session)
def launch_terminal():
@@ -86,38 +82,30 @@ def launch_terminal():
terminal_controller.parse_args_and_run()
def login_and_launch(session: dict, guest_allowed: bool = True):
def login_and_launch(session: dict):
"""Login and launch terminal.
Parameters
----------
session : dict
The session info.
guest_allowed : bool, optional
Allow guest login, by default True
"""
status = login(session)
if status == LoginStatus.SUCCESS:
launch_terminal()
elif status == LoginStatus.FAILED:
prompt(welcome=False, guest_allowed=guest_allowed)
prompt(welcome=False)
else:
prompt(welcome=True, guest_allowed=guest_allowed)
prompt(welcome=True)
def main(guest_allowed: bool = True):
"""Main function
Parameters
----------
guest_allowed : bool, optional
Allow guest login, by default True
"""
def main():
"""Main function"""
local_session = Local.get_session()
if not local_session:
prompt(guest_allowed=guest_allowed)
prompt()
else:
login_and_launch(session=local_session, guest_allowed=guest_allowed)
login_and_launch(session=local_session)
if __name__ == "__main__":

View File

@@ -1,8 +1,4 @@
import importlib
import json
import logging
import os
import sys
from enum import Enum
from typing import Optional
@@ -10,6 +6,11 @@ import matplotlib.pyplot as plt
import openbb_terminal.session.hub_model as Hub
import openbb_terminal.session.local_model as Local
from openbb_terminal.base_helpers import (
clear_openbb_env_vars,
reload_openbb_config_modules,
remove_log_handlers,
)
from openbb_terminal.helper_funcs import system_clear
from openbb_terminal.rich_config import console
from openbb_terminal.session.user import User
@@ -42,6 +43,23 @@ def create_session(email: str, password: str, save: bool) -> dict:
return session
def create_session_from_token(token: str, save: bool) -> dict:
"""Create a session from token.
Parameters
----------
token : str
The token.
save : bool
Save the session.
"""
session = Hub.get_session_from_token(token)
if session and save:
Local.save_session(session)
return session
def login(session: dict) -> LoginStatus:
"""Login and load user info.
@@ -50,6 +68,8 @@ def login(session: dict) -> LoginStatus:
session : dict
The session info.
"""
clear_openbb_env_vars(exceptions=["OPENBB_ENABLE_AUTHENTICATION"])
reload_openbb_config_modules()
response = Hub.fetch_user_configs(session)
if response is not None:
if response.status_code == 200:
@@ -99,39 +119,19 @@ def logout(
r = Hub.delete_session(auth_header, token)
if not r or r.status_code != 200:
success = False
User.clear()
User.clear()
clear_openbb_env_vars()
remove_log_handlers()
reload_openbb_modules()
if not Local.remove_session_file():
success = False
if not Local.remove_session_file():
success = False
if not Local.remove_cli_history_file():
success = False
clear_openbb_env_vars()
remove_log_handlers()
reload_openbb_config_modules()
plt.close("all")
if success:
console.print("[green]\nLogout successful.[/green]")
def clear_openbb_env_vars():
"""Clear openbb environment variables."""
for v in os.environ:
if v.startswith("OPENBB"):
os.environ.pop(v)
def remove_log_handlers():
"""Remove the log handlers - needs to be done before reloading modules."""
for handler in logging.root.handlers[:]:
logging.root.removeHandler(handler)
def reload_openbb_modules():
"""Reload all openbb modules to clear memorized variables."""
modules = sys.modules.copy()
for module in modules:
if module.startswith("openbb"):
importlib.reload(sys.modules[module])

View File

@@ -1,6 +1,6 @@
import openbb_terminal.feature_flags as obbff
from openbb_terminal.rich_config import console
from openbb_terminal.session.hub_model import REGISTER_LINK
from openbb_terminal.session.hub_model import REGISTER_URL
class User:
@@ -58,10 +58,7 @@ class User:
sync = "OFF"
console.print(f"[info]sync:[/info] {sync}")
else:
console.print(
"[info]You are currently logged as a guest.\n"
f"Create an account here {REGISTER_LINK}.[/info]"
)
User.print_guest_message()
@classmethod
def clear(cls):
@@ -91,3 +88,11 @@ class User:
def get_token(cls):
"""Get token."""
return cls._token
@classmethod
def print_guest_message(cls):
"""Print guest message."""
console.print(
"[info]You are currently logged as a guest.\n"
f"[info]Register: [/info][cmds]{REGISTER_URL}\n[/cmds]"
)

View File

@@ -315,7 +315,7 @@ class StocksController(StockBaseController):
self.custom_load_wrapper([self.ticker])
stocks_view.display_quote(
ns_parser.s_ticker,
self.ticker,
ns_parser.export,
" ".join(ns_parser.sheet_name) if ns_parser.sheet_name else None,
)

View File

@@ -28,7 +28,9 @@ def display_quote(symbol: str, export: str = "", sheet_name: str = None):
if quote.empty:
console.print("[red]Data not found[/red]\n")
else:
print_rich_table(quote, headers=[""], title=f"{symbol} Quote", show_index=True)
print_rich_table(
quote, headers=[""], title=f"{symbol.upper()} Quote", show_index=True
)
export_data(
export,

View File

@@ -5,7 +5,6 @@ __docformat__ = "numpy"
import argparse
import contextlib
import difflib
import importlib
import logging
import os
import re
@@ -33,7 +32,6 @@ from openbb_terminal.core.config.paths import (
USER_DATA_DIRECTORY,
USER_ENV_FILE,
USER_ROUTINES_DIRECTORY,
load_dotenv_with_priority,
)
from openbb_terminal.core.log.generation.custom_logger import log_terminal
from openbb_terminal.helper_funcs import (
@@ -48,12 +46,11 @@ from openbb_terminal.parent_classes import BaseController
from openbb_terminal.reports.reports_model import ipykernel_launcher
from openbb_terminal.rich_config import MenuText, console
from openbb_terminal.session import session_controller
from openbb_terminal.session.hub_model import REGISTER_LINK
from openbb_terminal.session.user import User
from openbb_terminal.terminal_helper import (
bootup,
check_for_updates,
is_installer,
is_auth_enabled,
is_packaged_application,
is_reset,
print_goodbye,
@@ -82,7 +79,6 @@ class TerminalController(BaseController):
"""Terminal Controller class."""
CHOICES_COMMANDS = [
"account",
"keys",
"settings",
"survey",
@@ -110,6 +106,9 @@ class TerminalController(BaseController):
"funds",
]
if is_auth_enabled():
CHOICES_MENUS.append("account")
PATH = "/"
GUESS_TOTAL_TRIES = 0
@@ -173,7 +172,8 @@ class TerminalController(BaseController):
mt.add_cmd("news")
mt.add_raw("\n")
mt.add_info("_configure_")
mt.add_menu("account")
if is_auth_enabled():
mt.add_menu("account")
mt.add_menu("keys")
mt.add_menu("featflags")
mt.add_menu("sources")
@@ -369,10 +369,7 @@ class TerminalController(BaseController):
from openbb_terminal.account.account_controller import AccountController
if User.is_guest():
console.print(
"[info]You need to be logged in to use this menu.\n"
f"Create an account here {REGISTER_LINK}.[/info]\n"
)
User.print_guest_message()
return
self.queue = self.load_class(AccountController, self.queue)
@@ -836,13 +833,6 @@ class TerminalController(BaseController):
def terminal(jobs_cmds: List[str] = None, test_mode=False):
"""Terminal Menu."""
if User.is_guest():
load_dotenv_with_priority()
modules = sys.modules.copy()
for module in modules:
if module.startswith("openbb"):
importlib.reload(sys.modules[module])
log_terminal(test_mode=test_mode)
if jobs_cmds is not None and jobs_cmds:
@@ -949,6 +939,9 @@ def terminal(jobs_cmds: List[str] = None, test_mode=False):
an_input = input(f"{get_flair()} / $ ")
try:
if an_input == "logout":
break
# Process the input command
t_controller.queue = t_controller.switch(an_input)
@@ -963,9 +956,6 @@ def terminal(jobs_cmds: List[str] = None, test_mode=False):
print_goodbye()
break
if an_input == "logout":
break
except SystemExit:
logger.exception(
"The command '%s' doesn't exist on the / menu.",
@@ -998,7 +988,7 @@ def terminal(jobs_cmds: List[str] = None, test_mode=False):
t_controller.queue.insert(0, an_input)
if an_input == "logout":
return session_controller.main(guest_allowed=not is_installer())
return session_controller.main()
def insert_start_slash(cmds: List[str]) -> List[str]:
@@ -1217,15 +1207,6 @@ def parse_args_and_run():
type=lambda s: [str(item) for item in s.split(",")],
default=None,
)
# The args -m, -f and --HistoryManager.hist_file are used only in reports menu
# by papermill and that's why they have suppress help.
parser.add_argument(
"-m",
help=argparse.SUPPRESS,
dest="module",
default="",
type=str,
)
parser.add_argument(
"-t",
"--test",
@@ -1235,6 +1216,21 @@ def parse_args_and_run():
" to see testing argument options."
),
)
if is_auth_enabled():
parser.add_argument(
"--login",
action="store_true",
help="Go to login prompt.",
)
# The args -m, -f and --HistoryManager.hist_file are used only in reports menu
# by papermill and that's why they have suppress help.
parser.add_argument(
"-m",
help=argparse.SUPPRESS,
dest="module",
default="",
type=str,
)
parser.add_argument(
"-f",
help=argparse.SUPPRESS,

View File

@@ -24,6 +24,7 @@ from openbb_terminal import (
from openbb_terminal.config_terminal import LOGGING_APP_NAME, LOGGING_COMMIT_HASH
from openbb_terminal.helper_funcs import request
from openbb_terminal.rich_config import console
from openbb_terminal.session.user import User
# pylint: disable=too-many-statements,no-member,too-many-branches,C0302
@@ -229,6 +230,21 @@ def is_packaged_application() -> bool:
return LOGGING_APP_NAME == "gst_packaged"
def is_auth_enabled() -> bool:
"""Tell whether or not authentication is enabled.
Returns
-------
bool
If authentication is enabled
"""
# TODO: This function is a temporary way to block authentication
return (
str(os.getenv("OPENBB_ENABLE_AUTHENTICATION")).lower() == "true"
or "--login" in sys.argv[1:]
)
def is_installer() -> bool:
"""Tell whether or not it is a packaged version."""
# TODO: Merge this with is_packaged_application
@@ -338,15 +354,19 @@ def reset(queue: List[str] = None):
logger.info("resetting")
plt.close("all")
flag = ""
if not User.is_guest():
flag = " --login"
if queue and len(queue) > 0:
completed_process = subprocess.run( # nosec
f"{sys.executable} terminal.py {'/'.join(queue) if len(queue) > 0 else ''}",
f"{sys.executable} terminal.py {'/'.join(queue) if len(queue) > 0 else ''}{flag}",
shell=True,
check=False,
)
else:
completed_process = subprocess.run( # nosec
f"{sys.executable} terminal.py", shell=True, check=False
f"{sys.executable} terminal.py{flag}", shell=True, check=False
)
if completed_process.returncode != 0:
console.print("Unfortunately, resetting wasn't possible!\n")

View File

@@ -1,14 +1,23 @@
import multiprocessing
import sys
from openbb_terminal.core.integration_tests import integration_controller
from openbb_terminal.session import session_controller
from openbb_terminal.terminal_helper import is_installer
from openbb_terminal.base_helpers import load_dotenv_and_reload_configs
from openbb_terminal.terminal_helper import is_auth_enabled
if __name__ == "__main__":
multiprocessing.freeze_support()
sent_args = sys.argv[1:]
load_dotenv_and_reload_configs()
if "-t" in sent_args or "--test" in sent_args:
from openbb_terminal.core.integration_tests import integration_controller
integration_controller.main()
else:
session_controller.main(guest_allowed=not is_installer())
from openbb_terminal.session import session_controller
if is_auth_enabled():
session_controller.main()
else:
session_controller.launch_terminal()

View File

@@ -323,6 +323,11 @@ def pytest_addoption(parser: Parser):
action="store_true",
help="To run tests with the marker : @pytest.mark.optimization",
)
parser.addoption(
"--session",
action="store_true",
help="To run tests with the marker : @pytest.mark.session",
)
parser.addoption(
"--rewrite-expected",
action="store_true",

View File

@@ -250,6 +250,7 @@ def test_call_func_expect_queue(expected_queue, func, queue):
@pytest.mark.parametrize(
"func",
[
"call_logout",
"call_sync",
"call_pull",
"call_clear",
@@ -273,6 +274,20 @@ def test_call_func_no_parser(func, mocker):
controller.parse_known_args_and_warn.assert_called_once()
def test_call_logout(mocker):
controller = account_controller.AccountController(queue=None)
path_controller = "openbb_terminal.account.account_controller"
mock_logout = mocker.patch(
target=f"{path_controller}.logout",
)
controller.call_logout([])
assert controller.queue == []
mock_logout.assert_called_once()
@pytest.mark.vcr
@pytest.mark.record_stdout
@pytest.mark.parametrize(

View File

@@ -9,3 +9,6 @@ Cloud storage of routines:
download Download routine
delete Delete routine
Authentication:
logout Log out from OpenBB account

View File

@@ -0,0 +1,38 @@
interactions:
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Authorization:
- Bearer a4eaa48d-299c-4305-973f-4398e40eeb49
Connection:
- keep-alive
Content-Type:
- application/json
User-Agent:
- Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:82.1) Gecko/20100101 Firefox/82.1
method: GET
uri: https://payments.openbb.dev/sdk/token
response:
body:
string: '{"detail":"Could not validate credentials"}'
headers:
Connection:
- keep-alive
Content-Length:
- '43'
Content-Type:
- application/json
Date:
- Mon, 13 Feb 2023 10:31:27 GMT
Server:
- nginx/1.22.1
www-authenticate:
- Bearer
status:
code: 401
message: Unauthorized
version: 1

View File

@@ -0,0 +1,38 @@
interactions:
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Authorization:
- Bearer a4eaa48d-299c-4305-973f-4398e40eeb49
Connection:
- keep-alive
Content-Type:
- application/json
User-Agent:
- Mozilla/5.0 (Windows NT 6.1; WOW64; rv:84.0) Gecko/20100101 Firefox/84.0
method: GET
uri: https://payments.openbb.dev/sdk/token
response:
body:
string: '{"detail":"Could not validate credentials"}'
headers:
Connection:
- keep-alive
Content-Length:
- '43'
Content-Type:
- application/json
Date:
- Mon, 13 Feb 2023 10:31:28 GMT
Server:
- nginx/1.22.1
www-authenticate:
- Bearer
status:
code: 401
message: Unauthorized
version: 1

View File

@@ -0,0 +1,38 @@
interactions:
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Authorization:
- Bearer a4eaa48d-299c-4305-973f-4398e40eeb49
Connection:
- keep-alive
Content-Type:
- application/json
User-Agent:
- Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.10; rv:86.1) Gecko/20100101 Firefox/86.1
method: GET
uri: https://payments.openbb.dev/sdk/token
response:
body:
string: '{"detail":"Could not validate credentials"}'
headers:
Connection:
- keep-alive
Content-Length:
- '43'
Content-Type:
- application/json
Date:
- Mon, 13 Feb 2023 10:31:29 GMT
Server:
- nginx/1.22.1
www-authenticate:
- Bearer
status:
code: 401
message: Unauthorized
version: 1

View File

@@ -0,0 +1,7 @@
import pytest
from _pytest.nodes import Node
def pytest_runtest_setup(item: Node):
if not item.config.getoption("--session"):
pytest.skip(msg="Runs only with option : --session")

View File

@@ -61,6 +61,44 @@ def test_create_session_exception(email, password):
assert response is None
@pytest.mark.parametrize("token", [("test_token")])
def test_create_session_from_token_success(token):
with patch("requests.post") as mock_post:
mock_post.return_value.json.return_value = TEST_RESPONSE
response = hub_model.create_session_from_token(token)
assert response.json() == TEST_RESPONSE
mock_post.assert_called_once_with(
url=hub_model.BASE_URL + "sdk/login",
json={"token": token},
timeout=hub_model.TIMEOUT,
)
@pytest.mark.parametrize("token", [("test_token")])
def test_create_session_from_token_connection_error(token):
with patch("requests.post") as mock_post:
mock_post.side_effect = requests.exceptions.ConnectionError
response = hub_model.create_session_from_token(token)
assert response is None
@pytest.mark.parametrize("token", [("test_token")])
def test_create_session_from_token_timeout(token):
with patch("requests.post") as mock_post:
mock_post.side_effect = requests.exceptions.Timeout
response = hub_model.create_session_from_token(token)
assert response is None
@pytest.mark.parametrize("token", [("test_token")])
def test_create_session_from_token_exception(token):
with patch("requests.post") as mock_post:
mock_post.side_effect = Exception
response = hub_model.create_session_from_token(token)
assert response is None
@pytest.mark.parametrize("auth_header, token", TEST_HEADER_TOKEN)
def test_delete_session_success(auth_header, token):
with patch("requests.post") as mock_post:
@@ -186,6 +224,56 @@ def test_get_session_failed_to_request():
create_session_mock.assert_called_once_with("email", "password")
def test_get_session_from_token():
mock_response = MagicMock(spec=requests.Response)
mock_response.status_code = 200
mock_response.json.return_value = {"session": "info"}
with patch(
"openbb_terminal.session.hub_model.create_session_from_token",
return_value=mock_response,
) as create_session_mock:
result = hub_model.get_session_from_token("token")
assert result == {"session": "info"}
create_session_mock.assert_called_once_with("token")
def test_get_session_from_token_401():
mock_response = MagicMock(spec=requests.Response)
mock_response.status_code = 401
with patch(
"openbb_terminal.session.hub_model.create_session_from_token",
return_value=mock_response,
) as create_session_mock:
result = hub_model.get_session_from_token("token")
assert result == {}
create_session_mock.assert_called_once_with("token")
def test_get_session_from_token_403():
mock_response = MagicMock(spec=requests.Response)
mock_response.status_code = 403
with patch(
"openbb_terminal.session.hub_model.create_session_from_token",
return_value=mock_response,
) as create_session_mock:
result = hub_model.get_session_from_token("token")
assert result == {}
create_session_mock.assert_called_once_with("token")
def test_get_session_from_token_failed_to_request():
with patch(
"openbb_terminal.session.hub_model.create_session_from_token",
return_value=None,
) as create_session_mock:
result = hub_model.get_session_from_token("token")
assert result == {}
create_session_mock.assert_called_once_with("token")
@pytest.mark.parametrize("token_type, access_token", [("TokenType", "AccessToken")])
def test_fetch_user_configs_success(token_type, access_token):
mock_response = MagicMock(spec=requests.Response)

View File

@@ -18,51 +18,83 @@ TEST_SESSION = {
@pytest.mark.parametrize(
"email, password, save",
"email, password, token, save",
[
(
"test_email",
"test_pass",
"test_token",
True,
),
],
)
def test_get_session_exception(email: str, password: str, save: bool):
def test_get_session_exception(email: str, password: str, token: str, save: bool):
path = "openbb_terminal.session.sdk_session."
with patch(path + "session_model.create_session") as mock_create_session:
mock_create_session.return_value = {}
with patch(
path + "session_model.create_session_from_token"
) as mock_create_session_from_token:
mock_create_session.return_value = {}
mock_create_session_from_token.return_value = {}
with pytest.raises(Exception):
sdk_session.get_session(email=email, password=password, save=save)
with pytest.raises(Exception):
sdk_session.get_session(
email=email, password=password, token=token, save=save
)
mock_create_session.assert_called_once_with(email, password, save)
mock_create_session.assert_called_once_with(email, password, save)
mock_create_session_from_token.assert_called_once_with(token, save)
@pytest.mark.parametrize(
"email, password, save",
"email, password, token, save",
[
(
"test_email",
"test_pass",
"test_token",
True,
),
],
)
def test_get_session(email: str, password: str, save: bool):
def test_get_session(email: str, password: str, token: str, save: bool):
path = "openbb_terminal.session.sdk_session."
with patch(path + "session_model.create_session") as mock_create_session:
mock_create_session.return_value = TEST_SESSION
sdk_session.get_session(email=email, password=password, save=save)
sdk_session.get_session(email=email, password=password, token=token, save=save)
mock_create_session.assert_called_once_with(email, password, save)
@pytest.mark.parametrize(
"email, password, keep_session, has_session, status",
"email, password, token, save",
[
(
"test_email",
"test_pass",
"test_token",
True,
),
],
)
def test_get_session_from_token(email: str, password: str, token: str, save: bool):
path = "openbb_terminal.session.sdk_session."
with patch(
path + "session_model.create_session_from_token"
) as mock_create_session_from_token:
mock_create_session_from_token.return_value = TEST_SESSION
sdk_session.get_session(email=email, password=password, token=token, save=save)
mock_create_session_from_token.assert_called_once_with(token, save)
@pytest.mark.parametrize(
"email, password, token, keep_session, has_session, status",
[
(
"test_email",
"test_pass",
"test_token",
True,
False,
LoginStatus.SUCCESS,
@@ -70,6 +102,7 @@ def test_get_session(email: str, password: str, save: bool):
(
"test_email",
"test_pass",
"test_token",
True,
True,
LoginStatus.SUCCESS,
@@ -77,6 +110,7 @@ def test_get_session(email: str, password: str, save: bool):
(
"test_email",
"test_pass",
"test_token",
True,
False,
LoginStatus.FAILED,
@@ -84,6 +118,7 @@ def test_get_session(email: str, password: str, save: bool):
(
"test_email",
"test_pass",
"test_token",
True,
False,
LoginStatus.NO_RESPONSE,
@@ -91,40 +126,48 @@ def test_get_session(email: str, password: str, save: bool):
],
)
def test_login(
mocker,
email: str,
password: str,
token: str,
keep_session: bool,
has_session: bool,
status: LoginStatus,
):
path = "openbb_terminal.session.sdk_session."
with (
patch(path + "session_model.login") as mock_login,
patch(path + "Local.get_session") as mock_local_get_session,
patch(path + "get_session") as mock_hub_get_session,
):
if has_session:
mock_local_get_session.return_value = TEST_SESSION
else:
mock_local_get_session.return_value = None
mock_login = mocker.patch(path + "session_model.login")
mock_local_get_session = mocker.patch(path + "Local.get_session")
mock_hub_get_session = mocker.patch(path + "get_session")
mock_hub_get_session.return_value = TEST_SESSION
mock_login.return_value = status
if has_session:
mock_local_get_session.return_value = TEST_SESSION
else:
mock_local_get_session.return_value = None
if status in [LoginStatus.FAILED, LoginStatus.NO_RESPONSE]:
with pytest.raises(Exception):
sdk_session.login(
email=email, password=password, keep_session=keep_session
)
else:
sdk_session.login(email=email, password=password, keep_session=keep_session)
mock_hub_get_session.return_value = TEST_SESSION
mock_login.return_value = status
mock_local_get_session.assert_called_once()
if has_session:
mock_hub_get_session.assert_not_called()
else:
mock_hub_get_session.assert_called_once_with(email, password, keep_session)
mock_login.assert_called_once_with(TEST_SESSION)
if status in [LoginStatus.FAILED, LoginStatus.NO_RESPONSE]:
with pytest.raises(Exception):
sdk_session.login(
email=email,
password=password,
token=token,
keep_session=keep_session,
)
else:
sdk_session.login(
email=email, password=password, token=token, keep_session=keep_session
)
mock_local_get_session.assert_called_once()
if has_session:
mock_hub_get_session.assert_not_called()
else:
mock_hub_get_session.assert_called_once_with(
email, password, token, keep_session
)
mock_login.assert_called_once_with(TEST_SESSION)
def test_logout():
@@ -137,3 +180,21 @@ def test_logout():
token=User.get_token(),
guest=User.is_guest(),
)
@pytest.mark.record_stdout
def test_whoami_guest():
sdk_session.whoami()
@pytest.mark.record_stdout
def test_whoami():
User.load_user_info(
{
"token_type": "MOCK_TOKEN_TYPE",
"access_token": "MOCK_ACCESS_TOKEN",
"uuid": "MOCK_UUID",
},
email="MOCK_EMAIL",
)
sdk_session.whoami()

View File

@@ -47,18 +47,6 @@ def test_prompt():
assert create_session_mock.call_count == 1
def test_prompt_guest_allowed():
with patch(
"openbb_terminal.session.session_controller.get_user_input",
return_value=(None, None, False),
) as get_user_input_mock, patch(
"openbb_terminal.session.session_controller.launch_terminal", return_value=True
) as launch_terminal_mock:
session_controller.prompt(guest_allowed=True)
assert get_user_input_mock.call_count == 1
assert launch_terminal_mock.call_count == 1
def test_launch_terminal():
with patch(
"openbb_terminal.session.session_controller.terminal_controller.parse_args_and_run",

View File

@@ -2,8 +2,6 @@
# IMPORTATION THIRDPARTY
import json
import os
from unittest.mock import patch
import pytest
from requests import Response
@@ -47,14 +45,13 @@ CONFIGS = {
),
],
)
def test_create_session(email, password, save):
def test_create_session(mocker, email, password, save):
path = "openbb_terminal.session.session_model."
with (
patch(path + "Hub.get_session") as mock_get_session,
patch(path + "Local.save_session") as mock_save_session,
):
mock_get_session.return_value = TEST_SESSION
session = session_model.create_session(email, password, save)
mock_get_session = mocker.patch(path + "Hub.get_session")
mock_save_session = mocker.patch(path + "Local.save_session")
mock_get_session.return_value = TEST_SESSION
session = session_model.create_session(email, password, save)
assert session == TEST_SESSION
@@ -63,63 +60,76 @@ def test_create_session(email, password, save):
mock_save_session.assert_called_once_with(session)
def test_login_no_response():
def test_login_no_response(mocker):
path = "openbb_terminal.session.session_model."
with (
patch(path + "Hub.fetch_user_configs") as mock_fetch_user_configs,
patch(path + "Local.apply_configs") as mock_apply_configs,
patch(path + "User.load_user_info") as mock_load_user_info,
patch(path + "User.update_flair") as mock_update_flair,
):
mock_fetch_user_configs.return_value = None
mock_clear_openbb_env_vars = mocker.patch(path + "clear_openbb_env_vars")
mock_reload_openbb_config_modules = mocker.patch(
path + "reload_openbb_config_modules"
)
mock_fetch_user_configs = mocker.patch(path + "Hub.fetch_user_configs")
mock_apply_configs = mocker.patch(path + "Local.apply_configs")
mock_load_user_info = mocker.patch(path + "User.load_user_info")
mock_update_flair = mocker.patch(path + "User.update_flair")
assert (
session_model.login(TEST_SESSION) == session_model.LoginStatus.NO_RESPONSE
)
mock_fetch_user_configs.return_value = None
assert session_model.login(TEST_SESSION) == session_model.LoginStatus.NO_RESPONSE
mock_clear_openbb_env_vars.assert_called_once()
mock_reload_openbb_config_modules.assert_called_once()
mock_fetch_user_configs.assert_called_once_with(TEST_SESSION)
mock_apply_configs.assert_not_called()
mock_load_user_info.assert_not_called()
mock_update_flair.assert_not_called()
def test_login_fail_response():
def test_login_fail_response(mocker):
path = "openbb_terminal.session.session_model."
with (
patch(path + "Hub.fetch_user_configs") as mock_fetch_user_configs,
patch(path + "Local.apply_configs") as mock_apply_configs,
patch(path + "User.load_user_info") as mock_load_user_info,
patch(path + "User.update_flair") as mock_update_flair,
):
response = Response()
response.status_code = 400
mock_fetch_user_configs.return_value = response
mock_clear_openbb_env_vars = mocker.patch(path + "clear_openbb_env_vars")
mock_reload_openbb_config_modules = mocker.patch(
path + "reload_openbb_config_modules"
)
mock_fetch_user_configs = mocker.patch(path + "Hub.fetch_user_configs")
mock_apply_configs = mocker.patch(path + "Local.apply_configs")
mock_load_user_info = mocker.patch(path + "User.load_user_info")
mock_update_flair = mocker.patch(path + "User.update_flair")
assert session_model.login(TEST_SESSION) == session_model.LoginStatus.FAILED
response = Response()
response.status_code = 400
mock_fetch_user_configs.return_value = response
assert session_model.login(TEST_SESSION) == session_model.LoginStatus.FAILED
mock_clear_openbb_env_vars.assert_called_once()
mock_reload_openbb_config_modules.assert_called_once()
mock_fetch_user_configs.assert_called_once_with(TEST_SESSION)
mock_apply_configs.assert_not_called()
mock_load_user_info.assert_not_called()
mock_update_flair.assert_not_called()
def test_login_success_response():
def test_login_success_response(mocker):
path = "openbb_terminal.session.session_model."
with (
patch(path + "Hub.fetch_user_configs") as mock_fetch_user_configs,
patch(path + "Local.apply_configs") as mock_apply_configs,
patch(path + "User.load_user_info") as mock_load_user_info,
patch(path + "User.update_flair") as mock_update_flair,
):
response = Response()
response.status_code = 200
response._content = json.dumps( # pylint: disable=protected-access
CONFIGS
).encode("utf-8")
mock_fetch_user_configs.return_value = response
mock_clear_openbb_env_vars = mocker.patch(path + "clear_openbb_env_vars")
mock_reload_openbb_config_modules = mocker.patch(
path + "reload_openbb_config_modules"
)
mock_fetch_user_configs = mocker.patch(path + "Hub.fetch_user_configs")
mock_apply_configs = mocker.patch(path + "Local.apply_configs")
mock_load_user_info = mocker.patch(path + "User.load_user_info")
mock_update_flair = mocker.patch(path + "User.update_flair")
assert session_model.login(TEST_SESSION) == session_model.LoginStatus.SUCCESS
response = Response()
response.status_code = 200
response._content = json.dumps(CONFIGS).encode( # pylint: disable=protected-access
"utf-8"
)
mock_fetch_user_configs.return_value = response
assert session_model.login(TEST_SESSION) == session_model.LoginStatus.SUCCESS
mock_clear_openbb_env_vars.assert_called_once()
mock_reload_openbb_config_modules.assert_called_once()
mock_fetch_user_configs.assert_called_once_with(TEST_SESSION)
mock_apply_configs.assert_called_once()
mock_load_user_info.assert_called_once()
@@ -133,43 +143,27 @@ def test_login_success_response():
True,
],
)
def test_logout_user(guest):
def test_logout_user(mocker, guest):
path = "openbb_terminal.session.session_model."
with (
patch(path + "Hub.delete_session") as mock_delete_session,
patch(path + "Local.remove_session_file") as mock_remove_session_file,
patch(path + "Local.remove_cli_history_file") as mock_remove_cli_history_file,
patch(path + "reload_openbb_modules") as mock_reload_openbb_modules,
patch(path + "clear_openbb_env_vars") as mock_clear_openbb_env_vars,
patch(path + "User.clear") as mock_clear_user,
patch(path + "plt.close") as mock_plt_close,
):
auth_header = "Bearer test_token"
token = "test_token"
session_model.logout(auth_header, token, guest)
mock_delete_session = mocker.patch(path + "Hub.delete_session")
mock_remove_session_file = mocker.patch(path + "Local.remove_session_file")
mock_remove_cli_history_file = mocker.patch(path + "Local.remove_cli_history_file")
mock_reload_openbb_config_modules = mocker.patch(
path + "reload_openbb_config_modules"
)
mock_clear_openbb_env_vars = mocker.patch(path + "clear_openbb_env_vars")
mock_clear_user = mocker.patch(path + "User.clear")
mock_plt_close = mocker.patch(path + "plt.close")
auth_header = "Bearer test_token"
token = "test_token"
session_model.logout(auth_header, token, guest)
if not guest:
mock_delete_session.assert_called_once_with(auth_header, token)
mock_clear_user.assert_called_once()
mock_clear_user.assert_called_once()
mock_remove_session_file.assert_called_once()
mock_clear_openbb_env_vars.assert_called_once()
mock_reload_openbb_modules.assert_called_once()
mock_remove_session_file.assert_called_once()
mock_reload_openbb_config_modules.assert_called_once()
mock_remove_cli_history_file.assert_called_once()
mock_plt_close.assert_called_once()
def test_clear_openbb_env_vars():
mock_env = {"OPENBB_TEST": "test", "OPENBB_TEST2": "test2", "TEST": "test"}
with patch.dict("openbb_terminal.session.session_model.os.environ", mock_env):
session_model.clear_openbb_env_vars()
assert "OPENBB_TEST" not in os.environ
assert "OPENBB_TEST2" not in os.environ
assert "TEST" in os.environ
def test_reload_openbb_modules():
with patch("openbb_terminal.session.session_model.importlib.reload") as mock_reload:
session_model.reload_openbb_modules()
mock_reload.assert_called()

View File

@@ -0,0 +1,3 @@
[info]email:[/info] MOCK_EMAIL
[info]uuid:[/info] MOCK_UUID
[info]sync:[/info] ON

View File

@@ -0,0 +1,3 @@
[info]email:[/info] test@email.com
[info]uuid:[/info] test_uuid
[info]sync:[/info] ON

View File

@@ -1,2 +1,2 @@
[info]
Please enter your credentials or press enter for guest mode:[/info]
Please enter your credentials:[/info]

View File

@@ -1,2 +1,2 @@
[info]
Please enter your credentials or press enter for guest mode:[/info]
Please enter your credentials:[/info]

View File

@@ -1,2 +1,3 @@
[info]You are currently logged as a guest.
Create an account here https://my.openbb.dev/register.[/info]
[info]Register: [/info][cmds]https://my.openbb.dev/register
[/cmds]

View File

@@ -208,7 +208,7 @@ interactions:
HSr/n476/3TU/6l01E+UkXp8evrwdPeL95/fvv3pp/8BRKYJwViiAAA=
headers:
Age:
- '1'
- '0'
Connection:
- keep-alive
Expect-CT:

View File

@@ -1,4 +1,6 @@
Loading Daily data for TSLA with starting period 2020-02-10.
1 Failed download:
- TSLA: No data found for this date range, symbol may be delisted
Company Rating Price Target
Date
2023-02-06 Wedbush Morgan Securities Inc. BUY 225.000000

View File

@@ -16,6 +16,6 @@ Ten day average volume 3.910 M
Three month average volume 4.860 M
Timezone America/New_York
Two hundred day average 97.606
Year change -0.063
Year change -0.067
Year high 112.480
Year low 82.850

View File

@@ -0,0 +1,32 @@
# IMPORTATION STANDARD
import os
# IMPORTATION THIRDPARTY
from unittest.mock import patch
import pytest
# IMPORTATION INTERNAL
from openbb_terminal import base_helpers
@pytest.mark.parametrize(
"key, value",
[
("OPENBB_NOT_TO_REMOVE", "value"),
],
)
def test_clear_openbb_env_vars(key, value):
mock_env = {
"OPENBB_TEST": "test",
"OPENBB_TEST2": "test2",
"TEST": "test",
key: value,
}
with patch.dict("openbb_terminal.base_helpers.os.environ", mock_env):
base_helpers.clear_openbb_env_vars(exceptions=[key])
assert "OPENBB_TEST" not in os.environ
assert "OPENBB_TEST2" not in os.environ
assert "TEST" in os.environ
assert key in os.environ

View File

@@ -234,3 +234,20 @@ def test_call_tokenterminal(other):
@pytest.mark.parametrize("other", [[], ["-k", "1234", "-t", "456"]])
def test_call_shroom(other):
controller.call_shroom(other)
@pytest.mark.vcr
@pytest.mark.parametrize("other", [[], ["-k", "1234", "-t", "456"]])
def test_call_openbb(mocker, other):
# MOCK GET
attrs = {
"status_code": 200,
"json.return_value": {"token": "MOCK_TOKEN"},
}
mock_response = mocker.Mock(**attrs)
mocker.patch(
target="openbb_terminal.keys_model.request",
new=mocker.Mock(return_value=mock_response),
)
controller.call_openbb(other)

View File

@@ -1563,3 +1563,53 @@ def test_set_shroom_key(
)
assert_keys_and_status(args, persist, expected, env_var_name_list, status)
@patch.dict(os.environ, {})
@pytest.mark.vcr
@pytest.mark.record_stdout
@pytest.mark.parametrize(
"args, persist, show_output, expected",
[
(
["test_key"],
False,
True,
keys_model.KeyStatus.DEFINED_TEST_FAILED,
),
(
["test_key"],
False,
False,
keys_model.KeyStatus.DEFINED_TEST_FAILED,
),
(
["test_key"],
True,
True,
keys_model.KeyStatus.DEFINED_TEST_FAILED,
),
(
["REPLACE_ME"],
False,
True,
keys_model.KeyStatus.NOT_DEFINED,
),
],
)
def test_set_openbb_key(
args: List[str], persist: bool, show_output: bool, expected: str
):
env_var_name_list = [
"OPENBB_OPENBB_PERSONAL_ACCESS_TOKEN",
]
set_naive_environment(env_var_name_list)
status = keys_model.set_openbb_personal_access_token(
key=args[0],
persist=persist,
show_output=show_output,
)
assert_keys_and_status(args, persist, expected, env_var_name_list, status)

View File

@@ -1,8 +1,12 @@
from openbb_terminal.sdk import openbb
# pylint: disable=import-outside-toplevel
def test_openbb():
def test_openbb(mocker):
"""Test the openbb function"""
mock = mocker.patch("openbb_terminal.base_helpers.load_dotenv_and_reload_configs")
from openbb_terminal.sdk import openbb
mock.assert_called_once()
assert "stocks" in dir(openbb)

View File

@@ -0,0 +1 @@
[red]defined, test failed[/red]

View File

@@ -0,0 +1 @@
[red]defined, test failed[/red]

View File

@@ -0,0 +1 @@
[grey30]not defined[/grey30]