From 1440ae05a83b8dc6190c329f46491d637457acdc Mon Sep 17 00:00:00 2001 From: aan Date: Mon, 2 Dec 2024 22:17:24 +0100 Subject: [PATCH] feat; API keys --- bearish/__init__.py | 4 +-- bearish/database/alembic/env.py | 4 +-- bearish/database/crud.py | 2 ++ bearish/main.py | 32 +++++++++++++++++--- bearish/models/api_keys/api_keys.py | 6 ++-- bearish/models/assets/assets.py | 6 ++++ bearish/models/query/query.py | 3 ++ bearish/sources/financial_modelling_prep.py | 5 ++- tests/sources/test_finance_modelling_prep.py | 7 +++++ 9 files changed, 56 insertions(+), 13 deletions(-) diff --git a/bearish/__init__.py b/bearish/__init__.py index e3217d0..e90ec5a 100644 --- a/bearish/__init__.py +++ b/bearish/__init__.py @@ -1,10 +1,9 @@ -from dotenv import load_dotenv import logging.config LOGGING_CONFIG = { "version": 1, - "disable_existing_loggers": True, + "disable_existing_loggers": False, "formatters": { "standard": { "format": "%(asctime)s [%(levelname)s] %(name)s: [%(funcName)s] %(message)s" @@ -35,4 +34,3 @@ logging.config.dictConfig(LOGGING_CONFIG) -load_dotenv() diff --git a/bearish/database/alembic/env.py b/bearish/database/alembic/env.py index d369fdf..1a385d0 100644 --- a/bearish/database/alembic/env.py +++ b/bearish/database/alembic/env.py @@ -16,8 +16,8 @@ # Interpret the config file for Python logging. # This line sets up loggers basically. -if config.config_file_name is not None: - fileConfig(config.config_file_name) +# if config.config_file_name is not None: +# fileConfig(config.config_file_name) # add your model's MetaData object here # for 'autogenerate' support diff --git a/bearish/database/crud.py b/bearish/database/crud.py index 028f17f..5fdd2af 100644 --- a/bearish/database/crud.py +++ b/bearish/database/crud.py @@ -156,6 +156,8 @@ def _read_asset_type( query_ = select(orm_table) if query.countries: query_ = query_.where(orm_table.country.in_(query.countries)) # type: ignore + if query.exchanges: + query_ = query_.where(orm_table.exchange.in_(query.exchanges)) # type: ignore assets = session.exec(query_).all() return [table.model_validate(asset) for asset in assets] diff --git a/bearish/main.py b/bearish/main.py index f45b071..bcafc6e 100644 --- a/bearish/main.py +++ b/bearish/main.py @@ -74,7 +74,7 @@ def write_assets(self, query: Optional[AssetQuery] = None) -> None: ] for source in asset_sources + self.sources: if query: - cached_assets = self.read_assets(AssetQuery(countries=query.countries)) + cached_assets = self.read_assets(AssetQuery.model_validate(query)) query.update_symbols(cached_assets) logger.info(f"Fetching assets from source {type(source).__name__}") assets_ = source.read_assets(query) @@ -135,12 +135,34 @@ def read_sources(self) -> List[str]: @app.command() -def assets(path: Path, countries: List[str]) -> None: +def tickers(path: Path, exchanges: List[str], api_keys: Optional[Path] = None) -> None: + logger.info( - f"Writing assets to database for countries: {countries}", + f"Writing assets to database for countries: {exchanges}", ) - bearish = Bearish(path=path) - bearish.write_assets(AssetQuery(countries=countries)) + source_api_keys = SourceApiKeys.from_file(api_keys) + bearish = Bearish(path=path, api_keys=source_api_keys) + bearish.write_assets(AssetQuery(exchanges=exchanges, countries=[])) + + +@app.command() +def financials( + path: Path, exchanges: List[str], api_keys: Optional[Path] = None +) -> None: + source_api_keys = SourceApiKeys.from_file(api_keys) + bearish = Bearish(path=path, api_keys=source_api_keys) + asset_query = AssetQuery(exchanges=exchanges, countries=[]) + assets = bearish.read_assets(asset_query) + bearish.write_many_financials(assets.symbols()) + + +@app.command() +def series(path: Path, exchanges: List[str], api_keys: Optional[Path] = None) -> None: + source_api_keys = SourceApiKeys.from_file(api_keys) + bearish = Bearish(path=path, api_keys=source_api_keys) + asset_query = AssetQuery(exchanges=exchanges, countries=[]) + assets = bearish.read_assets(asset_query) + bearish.write_many_series(assets.symbols(), "full") if __name__ == "__main__": diff --git a/bearish/models/api_keys/api_keys.py b/bearish/models/api_keys/api_keys.py index e309da5..264ec24 100644 --- a/bearish/models/api_keys/api_keys.py +++ b/bearish/models/api_keys/api_keys.py @@ -1,6 +1,6 @@ import json from pathlib import Path -from typing import Dict +from typing import Dict, Optional from pydantic import BaseModel, Field @@ -9,7 +9,9 @@ class SourceApiKeys(BaseModel): keys: Dict[str, str] = Field(default_factory=dict) @classmethod - def from_file(cls, api_keys_path: Path) -> "SourceApiKeys": + def from_file(cls, api_keys_path: Optional[Path] = None) -> "SourceApiKeys": + if api_keys_path is None: + return cls() if not api_keys_path.exists(): raise FileNotFoundError(f"File not found: {api_keys_path}") try: diff --git a/bearish/models/assets/assets.py b/bearish/models/assets/assets.py index 94cada4..ff5de9b 100644 --- a/bearish/models/assets/assets.py +++ b/bearish/models/assets/assets.py @@ -30,3 +30,9 @@ def add(self, assets: "Assets") -> None: self.cryptos.extend(assets.cryptos) self.etfs.extend(assets.etfs) self.currencies.extend(assets.currencies) + + def symbols(self) -> List[str]: + return [ + asset.symbol + for asset in self.equities + self.cryptos + self.etfs + self.currencies + ] diff --git a/bearish/models/query/query.py b/bearish/models/query/query.py index d9b9282..bbb8f47 100644 --- a/bearish/models/query/query.py +++ b/bearish/models/query/query.py @@ -47,6 +47,9 @@ class AssetQuery(BaseAssetQuery): countries: Annotated[ List[str], BeforeValidator(remove_duplicates), Field(default_factory=list) ] + exchanges: Annotated[ + List[str], BeforeValidator(remove_duplicates), Field(default_factory=list) + ] symbols: Symbols = Field(default=Symbols()) # type: ignore def update_symbols(self, assets: Assets) -> None: diff --git a/bearish/sources/financial_modelling_prep.py b/bearish/sources/financial_modelling_prep.py index 92cac72..2dbdd9a 100644 --- a/bearish/sources/financial_modelling_prep.py +++ b/bearish/sources/financial_modelling_prep.py @@ -103,6 +103,9 @@ def from_tickers(cls, tickers: List[str]) -> List["FmpEquity"]: for ticker in tickers: try: profile = read_api(API_URL, "profile", cls.__api_key__, ticker) + if profile.get("Error Message"): + logger.error(f"Error reading {ticker}: {profile['Error Message']}") + break quote = read_api(API_URL, "quote", cls.__api_key__, ticker) ratio_ttm = read_api(API_URL, "ratios-ttm", cls.__api_key__, ticker) key_metrics = read_api( @@ -111,7 +114,7 @@ def from_tickers(cls, tickers: List[str]) -> List["FmpEquity"]: datas = [*profile, *quote, *ratio_ttm, *key_metrics] data = {k: v for data in datas for k, v in data.items()} tickers_.append(cls.model_validate(data)) - except Exception as e: # noqa: PERF203 + except Exception as e: logger.error(f"Error reading {ticker}: {e}") continue return tickers_ diff --git a/tests/sources/test_finance_modelling_prep.py b/tests/sources/test_finance_modelling_prep.py index 44e9598..88a83f1 100644 --- a/tests/sources/test_finance_modelling_prep.py +++ b/tests/sources/test_finance_modelling_prep.py @@ -95,6 +95,13 @@ def test_fmp_equity(fmp_api_fixture: requests_mock.Mocker) -> None: assert not assets.is_empty() +@pytest.mark.skip("requires API key") +def test_fmp_equity_integration() -> None: + fmp = FmpSource() + fmp.set_api_key("...") + assets = fmp._read_assets(AssetQuery(symbols=Symbols(equities=["AGFB.BR"]))) + + def test_fmp_financials(fmp_api_fixture: requests_mock.Mocker) -> None: fmp = FmpSource() fmp.set_api_key(API_KEY)