Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Store defaults in hub account #6491

Merged
merged 11 commits into from
Jun 18, 2024
48 changes: 24 additions & 24 deletions assets/extensions/provider.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
"credentials": [
"alpha_vantage_api_key"
],
"v3Credentials": [
"API_KEY_ALPHAVANTAGE"
],
"deprecatedCredentials": {
hjoaquim marked this conversation as resolved.
Show resolved Hide resolved
"API_KEY_ALPHAVANTAGE": "alpha_vantage_api_key"
},
"website": "https://www.alphavantage.co",
"instructions": "Go to: https://www.alphavantage.co/support/#api-key\n\n![AlphaVantage](https://user-images.githubusercontent.com/46355364/207820936-46c2ba00-81ff-4cd3-98a4-4fa44412996f.png)\n\nFill out the form, pass Captcha, and click on, \"GET FREE API KEY\"."
},
Expand All @@ -31,9 +31,9 @@
"credentials": [
"biztoc_api_key"
],
"v3Credentials": [
"API_BIZTOC_TOKEN"
],
"deprecatedCredentials": {
"API_BIZTOC_TOKEN": "biztoc_api_key"
},
"website": "https://api.biztoc.com",
"instructions": "The BizToc API is hosted on RapidAPI. To set up, go to: https://rapidapi.com/thma/api/biztoc.\n\n![biztoc0](https://github.com/marban/OpenBBTerminal/assets/18151143/04cdd423-f65e-4ad8-ad5a-4a59b0f5ddda)\n\nIn the top right, select 'Sign Up'. After answering some questions, you will be prompted to select one of their plans.\n\n![biztoc1](https://github.com/marban/OpenBBTerminal/assets/18151143/9f3b72ea-ded7-48c5-aa33-bec5c0de8422)\n\nAfter signing up, navigate back to https://rapidapi.com/thma/api/biztoc. If you are logged in, you will see a header called X-RapidAPI-Key.\n\n![biztoc2](https://github.com/marban/OpenBBTerminal/assets/18151143/0f3b6c91-07e0-447a-90cd-a9e23522929f)"
},
Expand Down Expand Up @@ -95,9 +95,9 @@
"credentials": [
"fmp_api_key"
],
"v3Credentials": [
"API_KEY_FINANCIALMODELINGPREP"
],
"deprecatedCredentials": {
"API_KEY_FINANCIALMODELINGPREP": "fmp_api_key"
},
"website": "https://financialmodelingprep.com",
"instructions": "Go to: https://site.financialmodelingprep.com/developer/docs\n\n![FinancialModelingPrep](https://user-images.githubusercontent.com/46355364/207821920-64553d05-d461-4984-b0fe-be0368c71186.png)\n\nClick on, \"Get my API KEY here\", and sign up for a free account.\n\n![FinancialModelingPrep](https://user-images.githubusercontent.com/46355364/207822184-a723092e-ef42-4f87-8c55-db150f09741b.png)\n\nWith an account created, sign in and navigate to the Dashboard, which shows the assigned token. by pressing the \"Dashboard\" button which will show the API key.\n\n![FinancialModelingPrep](https://user-images.githubusercontent.com/46355364/207823170-dd8191db-e125-44e5-b4f3-2df0e115c91d.png)"
},
Expand All @@ -109,9 +109,9 @@
"credentials": [
"fred_api_key"
],
"v3Credentials": [
"API_FRED_KEY"
],
"deprecatedCredentials": {
"API_FRED_KEY": "fred_api_key"
},
"website": "https://fred.stlouisfed.org",
"instructions": "Go to: https://fred.stlouisfed.org\n\n![FRED](https://user-images.githubusercontent.com/46355364/207827137-d143ba4c-72cb-467d-a7f4-5cc27c597aec.png)\n\nClick on, \"My Account\", create a new account or sign in with Google:\n\n![FRED](https://user-images.githubusercontent.com/46355364/207827011-65cdd501-27e3-436f-bd9d-b0d8381d46a7.png)\n\nAfter completing the sign-up, go to \"My Account\", and select \"API Keys\". Then, click on, \"Request API Key\".\n\n![FRED](https://user-images.githubusercontent.com/46355364/207827577-c869f989-4ef4-4949-ab57-6f3931f2ae9d.png)\n\nFill in the box for information about the use-case for FRED, and by clicking, \"Request API key\", at the bottom of the page, the API key will be issued.\n\n![FRED](https://user-images.githubusercontent.com/46355364/207828032-0a32d3b8-1378-4db2-9064-aa1eb2111632.png)"
},
Expand All @@ -131,9 +131,9 @@
"credentials": [
"intrinio_api_key"
],
"v3Credentials": [
"API_INTRINIO_KEY"
],
"deprecatedCredentials": {
"API_INTRINIO_KEY": "intrinio_api_key"
},
"website": "https://intrinio.com",
"instructions": "Go to: https://intrinio.com/starter-plan\n\n![Intrinio](https://user-images.githubusercontent.com/85772166/219207556-fcfee614-59f1-46ae-bff4-c63dd2f6991d.png)\n\nAn API key will be issued with a subscription. Find the token value within the account dashboard."
},
Expand All @@ -145,9 +145,9 @@
"credentials": [
"nasdaq_api_key"
],
"v3Credentials": [
"API_KEY_QUANDL"
],
"deprecatedCredentials": {
"API_KEY_QUANDL": "nasdaq_api_key"
},
"website": "https://data.nasdaq.com",
"instructions": "Go to: https://www.quandl.com\n\n![Quandl](https://user-images.githubusercontent.com/46355364/207823899-208a3952-f557-4b73-aee6-64ac00faedb7.png)\n\nClick on, \"Sign Up\", and register a new account.\n\n![Quandl](https://user-images.githubusercontent.com/46355364/207824214-4b6b2b74-e709-4ed4-adf2-14803e6f3568.png)\n\nFollow the sign-up instructions, and upon completion the API key will be assigned.\n\n![Quandl](https://user-images.githubusercontent.com/46355364/207824664-3c82befb-9c69-42df-8a82-510d85c19a97.png)"
},
Expand All @@ -167,9 +167,9 @@
"credentials": [
"polygon_api_key"
],
"v3Credentials": [
"API_POLYGON_KEY"
],
"deprecatedCredentials": {
"API_POLYGON_KEY": "polygon_api_key"
},
"website": "https://polygon.io",
"instructions": "Go to: https://polygon.io\n\n![Polygon](https://user-images.githubusercontent.com/46355364/207825623-fcd7f0a3-131a-4294-808c-754c13e38e2a.png)\n\nClick on, \"Get your Free API Key\".\n\n![Polygon](https://user-images.githubusercontent.com/46355364/207825952-ca5540ec-6ed2-4cef-a0ed-bb50b813932c.png)\n\nAfter signing up, the API Key is found at the bottom of the account dashboard page.\n\n![Polygon](https://user-images.githubusercontent.com/46355364/207826258-b1f318fa-fd9c-41d9-bf5c-fe16722e6601.png)"
},
Expand Down Expand Up @@ -224,9 +224,9 @@
"tradier_api_key",
"tradier_account_type"
],
"v3Credentials": [
"API_TRADIER_TOKEN"
],
"deprecatedCredentials": {
"API_TRADIER_TOKEN": "tradier_api_key"
},
"website": "https://tradier.com",
"instructions": "Go to: https://documentation.tradier.com\n\n![Tradier](https://user-images.githubusercontent.com/46355364/207829178-a8bba770-f2ea-4480-b28e-efd81cf30980.png)\n\nClick on, \"Open Account\", to start the sign-up process. After the account has been setup, navigate to [Tradier Broker Dash](https://dash.tradier.com/login?redirect=settings.api) and create the application. Request a sandbox access token."
},
Expand Down
10 changes: 5 additions & 5 deletions assets/scripts/generate_extension_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def to_camel(string: str):
return components[0] + "".join(x.title() for x in components[1:])


def createItem(package_name: str, obj: object, obj_attrs: List[str]) -> Dict[str, Any]:
def create_item(package_name: str, obj: object, obj_attrs: List[str]) -> Dict[str, Any]:
"""Create dictionary item from object attributes."""
pkg_spec = OPENBB_PLATFORM_TOML.data["tool"]["poetry"]["dependencies"].get(
package_name
Expand All @@ -68,7 +68,7 @@ def generate_provider_extensions() -> None:
"repr_name",
"description",
"credentials",
"v3_credentials",
"deprecated_credentials",
"website",
"instructions",
]
Expand All @@ -80,7 +80,7 @@ def generate_provider_extensions() -> None:
file, obj = file_obj[0], file_obj[1]
module = import_module(file)
provider_obj = getattr(module, obj)
data.append(createItem(pkg_name, provider_obj, obj_attrs))
data.append(create_item(pkg_name, provider_obj, obj_attrs))
write("provider", data)


Expand All @@ -96,7 +96,7 @@ def generate_router_extensions() -> None:
file, obj = file_obj[0], file_obj[1]
module = import_module(file)
router_obj = getattr(module, obj)
data.append(createItem(pkg_name, router_obj, obj_attrs))
data.append(create_item(pkg_name, router_obj, obj_attrs))
write("router", data)


Expand All @@ -112,7 +112,7 @@ def generate_obbject_extensions() -> None:
file, obj = file_obj[0], file_obj[1]
module = import_module(file)
ext_obj = getattr(module, obj)
data.append(createItem(pkg_name, ext_obj, obj_attrs))
data.append(create_item(pkg_name, ext_obj, obj_attrs))
write("obbject", data)


Expand Down
5 changes: 5 additions & 0 deletions openbb_platform/core/openbb_core/app/model/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,8 @@ def validate_before(cls, values: dict) -> dict:
v["provider"] = [provider]
new_values["commands"][clean_k] = v
return new_values

def update(self, incoming: "Defaults"):
"""Update current defaults."""
incoming_commands = incoming.model_dump(exclude_none=True).get("commands", {})
self.__dict__["commands"].update(incoming_commands)
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
"""Hub user settings model."""

from typing import Dict, Optional
from typing import Any, Dict, Optional

from pydantic import BaseModel, ConfigDict, Field


class HubUserSettings(BaseModel):
"""Hub user settings model."""

# features_settings: Dict[str, str]
features_settings: Dict[str, Any] = Field(default_factory=dict)
features_keys: Dict[str, Optional[str]] = Field(default_factory=dict)
# features_sources: Dict[str, List[str]]
# features_sources: Dict[str, Any]
# features_terminal_style: Dict[str, Union[str, Dict[str, str]]]

model_config = ConfigDict(validate_assignment=True)
24 changes: 17 additions & 7 deletions openbb_platform/core/openbb_core/app/service/hub_service.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
"""Hub manager class."""

from typing import Optional
from typing import Optional, Tuple
from warnings import warn

from fastapi import HTTPException
from jwt import ExpiredSignatureError, PyJWTError, decode, get_unverified_header
from openbb_core.app.model.abstract.error import OpenBBError
from openbb_core.app.model.credentials import Credentials
from openbb_core.app.model.defaults import Defaults
from openbb_core.app.model.hub.hub_session import HubSession
from openbb_core.app.model.hub.hub_user_settings import HubUserSettings
from openbb_core.app.model.profile import Profile
Expand Down Expand Up @@ -81,7 +82,9 @@ def push(self, user_settings: UserSettings) -> bool:
"""Push user settings to Hub."""
if self._session:
if user_settings.credentials:
hub_user_settings = self.platform2hub(user_settings.credentials)
hub_user_settings = self.platform2hub(
user_settings.credentials, user_settings.defaults
)
return self._put_user_settings(self._session, hub_user_settings)
return False
raise OpenBBError(
Expand All @@ -93,8 +96,10 @@ def pull(self) -> UserSettings:
if self._session:
self._hub_user_settings = self._get_user_settings(self._session)
profile = Profile(hub_session=self._session)
credentials = self.hub2platform(self._hub_user_settings)
return UserSettings(profile=profile, credentials=credentials)
credentials, defaults = self.hub2platform(self._hub_user_settings)
return UserSettings(
profile=profile, credentials=credentials, defaults=defaults
)
raise OpenBBError(
"No session found. Login or provide a 'HubSession' on initialization."
)
Expand Down Expand Up @@ -223,7 +228,7 @@ def _put_user_settings(
detail = response.json().get("detail", None)
raise HTTPException(status_code, detail)

def hub2platform(self, settings: HubUserSettings) -> Credentials:
def hub2platform(self, settings: HubUserSettings) -> Tuple[Credentials, Defaults]:
"""Convert Hub user settings to Platform models."""
if any(k in settings.features_keys for k in self.V3TOV4):
deprecated = {
Expand All @@ -242,9 +247,12 @@ def hub2platform(self, settings: HubUserSettings) -> Credentials:
self.V3TOV4.get(k, k): settings.features_keys.get(self.V3TOV4.get(k, k), v)
for k, v in settings.features_keys.items()
}
return Credentials(**hub_credentials)
defaults = settings.features_settings.get("defaults", {})
return Credentials(**hub_credentials), Defaults(**defaults)

def platform2hub(self, credentials: Credentials) -> HubUserSettings:
def platform2hub(
self, credentials: Credentials, defaults: Defaults
) -> HubUserSettings:
"""Convert Platform models to Hub user settings."""
# Dump mode json ensures SecretStr values are serialized as strings
credentials = credentials.model_dump(mode="json", exclude_none=True)
Expand All @@ -254,6 +262,8 @@ def platform2hub(self, credentials: Credentials) -> HubUserSettings:
# If v3 key was in the hub already, we keep it
k = v3_k if v3_k in settings.features_keys else v4_k
settings.features_keys[k] = v
defaults_ = defaults.model_dump(mode="json", exclude_none=True)
settings.features_settings.update({"defaults": defaults_})
return settings

@staticmethod
Expand Down
2 changes: 2 additions & 0 deletions openbb_platform/core/openbb_core/app/static/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ def login(
incoming = self._hub_service.pull()
self._base_app.user.profile = incoming.profile
self._base_app.user.credentials.update(incoming.credentials)
self._base_app.user.defaults.update(incoming.defaults)
if remember_me:
Path(self._openbb_directory).mkdir(parents=False, exist_ok=True)
session_file = Path(self._openbb_directory, self.SESSION_FILE)
Expand Down Expand Up @@ -187,6 +188,7 @@ def refresh(self, return_settings: bool = False) -> Optional[UserSettings]:
incoming = self._hub_service.pull()
self._base_app.user.profile = incoming.profile
self._base_app.user.credentials.update(incoming.credentials)
self._base_app.user.defaults.update(incoming.defaults)
if return_settings:
return self._base_app._command_runner.user_settings
return None
Expand Down
4 changes: 2 additions & 2 deletions openbb_platform/core/openbb_core/app/static/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,14 @@ def _get_provider(
result = self._check_credentials(p)
if result:
return p
elif result is False:
if result is False:
tries.append((p, "missing credentials"))
else:
tries.append((p, "not found"))

msg = "\n ".join([f"* '{pair[0]}' -> {pair[1]}" for pair in tries])
raise OpenBBError(
f"Provider fallback failed, please specify the provider or update credentials.\n"
f"Provider fallback failed, please specify one of the available providers or update credentials.\n"
f"[Providers]\n {msg}"
)
return choice
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def __init__(
credentials: Optional[List[str]] = None,
fetcher_dict: Optional[Dict[str, Type[Fetcher]]] = None,
repr_name: Optional[str] = None,
v3_credentials: Optional[List[str]] = None,
deprecated_credentials: Optional[Dict[str, Optional[str]]] = None,
instructions: Optional[str] = None,
) -> None:
"""Initialize the provider.
Expand All @@ -36,8 +36,8 @@ def __init__(
Dictionary of fetchers, by default None.
repr_name: Optional[str]
Full name of the provider, by default None.
v3_credentials: Optional[List[str]]
List of corresponding v3 credentials, by default None.
deprecated_credentials: Optional[Dict[str, Optional[str]]]
Map of deprecated credentials to its current name, by default None.
instructions: Optional[str]
Instructions on how to setup the provider. For example, how to get an API key.
"""
Expand All @@ -52,5 +52,5 @@ def __init__(
for c in credentials:
self.credentials.append(f"{self.name.lower()}_{c}")
self.repr_name = repr_name
self.v3_credentials = v3_credentials
self.deprecated_credentials = deprecated_credentials
self.instructions = instructions
15 changes: 11 additions & 4 deletions openbb_platform/core/tests/app/service/test_hub_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import pytest
from jwt import encode
from openbb_core.app.model.defaults import Defaults
from openbb_core.app.service.hub_service import (
Credentials,
HubService,
Expand Down Expand Up @@ -242,8 +243,9 @@ def test_hub2platform_v4_only():
"polygon_api_key": "def",
"fred_api_key": "ghi",
}
mock_user_settings.features_settings = {}

credentials = HubService().hub2platform(mock_user_settings)
credentials, _ = HubService().hub2platform(mock_user_settings)
assert isinstance(credentials, Credentials)
assert credentials.fmp_api_key.get_secret_value() == "abc"
assert credentials.polygon_api_key.get_secret_value() == "def"
Expand All @@ -258,8 +260,9 @@ def test_hub2platform_v3_only():
"API_POLYGON_KEY": "def",
"API_FRED_KEY": "ghi",
}
mock_user_settings.features_settings = {}

credentials = HubService().hub2platform(mock_user_settings)
credentials, _ = HubService().hub2platform(mock_user_settings)
assert isinstance(credentials, Credentials)
assert credentials.fmp_api_key.get_secret_value() == "abc"
assert credentials.polygon_api_key.get_secret_value() == "def"
Expand All @@ -275,8 +278,9 @@ def test_hub2platform_v3v4():
"API_POLYGON_KEY": "def",
"API_FRED_KEY": "ghi",
}
mock_user_settings.features_settings = {}

credentials = HubService().hub2platform(mock_user_settings)
credentials, _ = HubService().hub2platform(mock_user_settings)
assert isinstance(credentials, Credentials)
assert credentials.fmp_api_key.get_secret_value() == "other_key"
assert credentials.polygon_api_key.get_secret_value() == "def"
Expand All @@ -291,6 +295,7 @@ def test_platform2hub():
"fmp_api_key": "other_key",
"API_FRED_KEY": "ghi",
}
mock_user_settings.features_settings = {}
mock_hub_service = HubService()
mock_hub_service._hub_user_settings = mock_user_settings
mock_credentials = Credentials( # Current credentials
Expand All @@ -300,7 +305,8 @@ def test_platform2hub():
benzinga_api_key=SecretStr("benzinga"),
some_api_key=SecretStr("some"),
)
user_settings = mock_hub_service.platform2hub(mock_credentials)
mock_defaults = Defaults()
user_settings = mock_hub_service.platform2hub(mock_credentials, mock_defaults)

assert isinstance(user_settings, HubUserSettings)
assert user_settings.features_keys["API_KEY_FINANCIALMODELINGPREP"] == "fmp"
Expand All @@ -309,6 +315,7 @@ def test_platform2hub():
assert user_settings.features_keys["API_FRED_KEY"] == "fred"
assert user_settings.features_keys["benzinga_api_key"] == "benzinga"
assert "some_api_key" not in user_settings.features_keys
assert "defaults" in user_settings.features_settings


@pytest.mark.parametrize(
Expand Down
2 changes: 1 addition & 1 deletion openbb_platform/core/tests/app/static/test_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def test_container__check_credentials(container):
("provider_1", "provider_2"),
OpenBBError,
escape(
"Provider fallback failed, please specify the provider or update credentials."
"Provider fallback failed, please specify one of the available providers or update credentials."
"\n[Providers]\n * 'x' -> not found\n * 'y' -> not found\n * 'z' -> not found"
),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@
"EtfHistorical": AVEquityHistoricalFetcher,
},
repr_name="Alpha Vantage",
v3_credentials=["API_KEY_ALPHAVANTAGE"],
deprecated_credentials={"API_KEY_ALPHAVANTAGE": "alpha_vantage_api_key"},
instructions='Go to: https://www.alphavantage.co/support/#api-key\n\n![AlphaVantage](https://user-images.githubusercontent.com/46355364/207820936-46c2ba00-81ff-4cd3-98a4-4fa44412996f.png)\n\nFill out the form, pass Captcha, and click on, "GET FREE API KEY".', # noqa: E501 pylint: disable=line-too-long
)
Loading
Loading