Files
OpenBB/openbb_platform/providers/fmp/openbb_fmp/utils/helpers.py
Henrique Joaquim 31d03eb5f1 equity.estimates.price_target - Integrate with Benzinga (#5946)
* deprecation message

* fix lints

* adding equity.estimates.ratings and fmp as its provider

* unit tests for fmp

* removing unused file

* typos

* add benzinga to AnalystRatings

* revert changes to estimates router

* moving it to price target instead

* reverting changes

* fmp support for multiple ticker

* benzinga price target

* ruffy

* unused argument

* fix type hints

* tests for the new fetcher

* new integration tests

* fixing tests

* missing std arg

---------

Co-authored-by: Igor Radovanovic <74266147+IgorWounds@users.noreply.github.com>
2024-01-17 18:39:40 +00:00

191 lines
5.1 KiB
Python

"""FMP Helpers Module."""
import json
from datetime import date as dateType
from io import StringIO
from typing import Any, Dict, List, Optional, TypeVar, Union
from openbb_core.provider.utils.client import ClientSession
from openbb_core.provider.utils.errors import EmptyDataError
from openbb_core.provider.utils.helpers import (
ClientResponse,
amake_request,
amake_requests,
get_querystring,
)
from pydantic import BaseModel
T = TypeVar("T", bound=BaseModel)
class BasicResponse:
"""Basic Response class."""
def __init__(self, response: StringIO):
"""Initialize the BasicResponse class."""
# Find a way to get the status code
self.status_code = 200
response.seek(0)
self.text = response.read()
def json(self) -> dict:
"""Return the response as a dictionary."""
return json.loads(self.text)
def request(url: str) -> BasicResponse:
"""Request function for PyScript.
Pass in Method and make sure to await!
Parameters
----------
url: str
URL to make request to
Return
------
response: BasicRequest
BasicRequest object with status_code and text attributes
"""
# pylint: disable=import-outside-toplevel
from pyodide.http import open_url # type: ignore
response = open_url(url)
return BasicResponse(response)
async def response_callback(
response: ClientResponse, _: ClientSession
) -> Union[dict, List[dict]]:
"""Callback for make_request."""
data = await response.json()
if response.status != 200:
message = data.get("message", None) or data.get("error", "unknown error")
raise RuntimeError(f"Error in FMP request -> {message}")
if "Error Message" in data:
raise RuntimeError(f"FMP Error Message -> {data['Error Message']}")
if len(data) == 0:
raise EmptyDataError()
return data
async def get_data(url: str, **kwargs: Any) -> Union[list, dict]:
"""Get data from FMP endpoint."""
return await amake_request(url, response_callback=response_callback, **kwargs)
async def get_data_urls(urls: str, **kwargs: Any) -> Union[list, dict]:
"""Get data from FMP for several urls."""
return await amake_requests(urls, response_callback=response_callback, **kwargs)
def create_url(
version: int,
endpoint: str,
api_key: Optional[str],
query: Optional[Union[BaseModel, Dict]] = None,
exclude: Optional[List[str]] = None,
) -> str:
"""Return a URL for the FMP API.
Parameters
----------
version: int
The version of the API to use.
endpoint: str
The endpoint to use.
api_key: str
The API key to use.
query: Optional[BaseModel]
The dictionary to be turned into a querystring.
exclude: List[str]
The keys to be excluded from the querystring.
Returns
-------
str
The querystring.
"""
the_dict = {}
if query:
the_dict = query.model_dump() if isinstance(query, BaseModel) else query
query_string = get_querystring(the_dict, exclude or [])
base_url = f"https://financialmodelingprep.com/api/v{version}/"
return f"{base_url}{endpoint}?{query_string}&apikey={api_key}"
async def get_data_many(
url: str, sub_dict: Optional[str] = None, **kwargs: Any
) -> List[dict]:
"""Get data from FMP endpoint and convert to list of schemas.
Parameters
----------
url: str
The URL to get the data from.
sub_dict: Optional[str]
The sub-dictionary to use.
Returns
-------
List[dict]
Dictionary of data.
"""
data = await get_data(url, **kwargs)
if sub_dict and isinstance(data, dict):
data = data.get(sub_dict, [])
if isinstance(data, dict):
raise ValueError("Expected list of dicts, got dict")
if len(data) == 0:
raise EmptyDataError()
return data
async def get_data_one(url: str, **kwargs: Any) -> dict:
"""Get data from FMP endpoint and convert to schema."""
data = await get_data(url, **kwargs)
if isinstance(data, list):
if len(data) == 0:
raise ValueError("Expected dict, got empty list")
try:
data = {i: data[i] for i in range(len(data))} if len(data) > 1 else data[0]
except TypeError as e:
raise ValueError("Expected dict, got list of dicts") from e
return data
def most_recent_quarter(base: dateType = dateType.today()) -> dateType:
"""Get the most recent quarter date."""
base = min(base, dateType.today()) # This prevents dates from being in the future
exacts = [(3, 31), (6, 30), (9, 30), (12, 31)]
for exact in exacts:
if base.month == exact[0] and base.day == exact[1]:
return base
if base.month < 4:
return dateType(base.year - 1, 12, 31)
if base.month < 7:
return dateType(base.year, 3, 31)
if base.month < 10:
return dateType(base.year, 6, 30)
return dateType(base.year, 9, 30)
def get_interval(value: str) -> str:
"""Get the intervals for the FMP API."""
intervals = {
"m": "min",
"h": "hour",
"d": "day",
}
return f"{value[:-1]}{intervals[value[-1]]}"