1+ import os
2+ import asyncio
13from dataclasses import dataclass , field
2- from urllib . parse import urljoin
4+ from typing import Any
35import httpx
4- from typing import Any , Optional
5- from pydantic import BaseModel
6+ import utils
7+ from stock_valuation_app .models .stock import CombinedModel
8+
9+
10+ def get_endpoint (url : str ) -> str :
11+ """Get endpoints from the configuration."""
12+ api_config = utils .get_section_config ("api" )
13+ return api_config .get (f"{ url } " )
14+
15+
16+ def get_base_url () -> str :
17+ """Get the base URL from the configuration."""
18+ return get_endpoint ("base_url" )
19+
20+
21+ def get_api_key () -> str :
22+ """Get the API key from the .env file."""
23+ return os .getenv ("FMP_API_KEY" , "" )
624
725
826@dataclass
927class FMPClient :
1028 """A client for interacting with the Financial Modeling Prep API."""
11- base_url : str = field (default_factory = utils .get_base_url )
12- api_key : str = field (default_factory = utils .get_api_key )
29+ base_url : str = field (default_factory = get_base_url )
30+ api_key : str = field (default_factory = get_api_key )
31+ metric_types : list [str ] = field (default_factory = lambda : ["profile" , "rating" , "ratios" , "key-metrics" , "financial-growth" ,])
1332
33+ async def get_data (self , client : httpx .Client , url : str ) -> dict [str , Any ]:
34+ """Call API endpoint asynchronously"""
35+ response = await client .get (url )
36+ data = response .json ()
37+ return data
1438
15- async def fetch_data (self , endpoint : str , symbol : str , period : Optional [str ] = "annual" ): # params: dict[str, Any]
16- """Fetch data from the Financial Modeling Prep API."""
17- base_endpoint = urljoin (self .base_url , endpoint )
18- if period is None :
19- url = f"{ base_endpoint } /{ symbol } ?apikey={ self .api_key } "
20- else :
21- url = f"{ base_endpoint } /{ symbol } ?period={ period } &apikey={ self .api_key } "
39+ async def fetch_data (self , ticker : str ) -> dict [str , list [dict [str , Any ]]]:
40+ urls = []
41+ for metric in self .metric_types :
42+ if metric in ["profile" , "rating" ]:
43+ endpoint = f"{ self .base_url } /{ metric } /{ ticker } ?apikey={ self .api_key } "
44+ else :
45+ endpoint = f"{ self .base_url } /{ metric } /{ ticker } ?period=annual&apikey={ self .api_key } "
46+ urls .append (endpoint )
2247
2348 async with httpx .AsyncClient () as client :
24- response = await client .get (url )
25- response .raise_for_status ()
26- data = response .json ()
27- return data
49+ tasks = []
50+ for url in urls :
51+ tasks .append (asyncio .create_task (self .get_data (client , url )))
52+
53+ results = await asyncio .gather (* tasks )
54+
55+ # Rename some metric types to match with fields defined in the CombinedModel
56+ replace_metric_types = {"key-metrics" : "key_metrics" , "financial-growth" : "growth" ,}
57+ new_metric_types = [replace_metric_types .get (item , item ) for item in self .metric_types ]
58+
59+ # Create combined records dict for validation
60+ records = dict (zip (new_metric_types , results ))
61+
62+ # Validate the combined records
63+ return CombinedModel (** records ).model_dump ()
0 commit comments