Skip to content

Commit

Permalink
feat; API keys
Browse files Browse the repository at this point in the history
  • Loading branch information
andoludo committed Dec 2, 2024
1 parent bef016f commit 1440ae0
Show file tree
Hide file tree
Showing 9 changed files with 56 additions and 13 deletions.
4 changes: 1 addition & 3 deletions bearish/__init__.py
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -35,4 +34,3 @@


logging.config.dictConfig(LOGGING_CONFIG)
load_dotenv()
4 changes: 2 additions & 2 deletions bearish/database/alembic/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions bearish/database/crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
32 changes: 27 additions & 5 deletions bearish/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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__":
Expand Down
6 changes: 4 additions & 2 deletions bearish/models/api_keys/api_keys.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import json
from pathlib import Path
from typing import Dict
from typing import Dict, Optional

from pydantic import BaseModel, Field

Expand All @@ -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:
Expand Down
6 changes: 6 additions & 0 deletions bearish/models/assets/assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
]
3 changes: 3 additions & 0 deletions bearish/models/query/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
5 changes: 4 additions & 1 deletion bearish/sources/financial_modelling_prep.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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_
Expand Down
7 changes: 7 additions & 0 deletions tests/sources/test_finance_modelling_prep.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 1440ae0

Please sign in to comment.