mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-11-02 07:23:25 +00:00
[BugFix] Add Exception Handling For Unauthorized API Key Error. (#6800)
* add exception handling for unauthorized api call request. * linting * more linting * more linting * missed files * biztoc cassette * more linting * crypto interval choices as 7d and 30d instead of 5 and 21 * update the default message and a few Intrinio endpoints to use the unauthorized error. * mypy * no-else-raise --------- Co-authored-by: Theodore Aptekarev <aptekarev@gmail.com>
This commit is contained in:
@@ -6,7 +6,7 @@ from fastapi import APIRouter, FastAPI
|
||||
from openbb_core.api.exception_handlers import ExceptionHandlers
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
from openbb_core.app.router import RouterLoader
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from openbb_core.provider.utils.errors import EmptyDataError, UnauthorizedError
|
||||
from pydantic import ValidationError
|
||||
|
||||
|
||||
@@ -40,3 +40,4 @@ class AppLoader:
|
||||
app.exception_handlers[ValidationError] = ExceptionHandlers.validation
|
||||
app.exception_handlers[OpenBBError] = ExceptionHandlers.openbb
|
||||
app.exception_handlers[EmptyDataError] = ExceptionHandlers.empty_data
|
||||
app.exception_handlers[UnauthorizedError] = ExceptionHandlers.unauthorized
|
||||
|
||||
@@ -10,7 +10,7 @@ from fastapi import Request
|
||||
from fastapi.responses import JSONResponse, Response
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
from openbb_core.env import Env
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from openbb_core.provider.utils.errors import EmptyDataError, UnauthorizedError
|
||||
from pydantic import ValidationError
|
||||
|
||||
logger = logging.getLogger("uvicorn.error")
|
||||
@@ -55,7 +55,7 @@ class ExceptionHandlers:
|
||||
return await ExceptionHandlers._handle(
|
||||
exception=error,
|
||||
status_code=500,
|
||||
detail="Unexpected error.",
|
||||
detail=f"Unexpected Error -> {error.__class__.__name__} -> {str(error.args[0] or error.args)}",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@@ -99,3 +99,12 @@ class ExceptionHandlers:
|
||||
async def empty_data(_: Request, error: EmptyDataError):
|
||||
"""Exception handler for EmptyDataError."""
|
||||
return Response(status_code=204)
|
||||
|
||||
@staticmethod
|
||||
async def unauthorized(_: Request, error: UnauthorizedError):
|
||||
"""Exception handler for OpenBBError."""
|
||||
return await ExceptionHandlers._handle(
|
||||
exception=error,
|
||||
status_code=502,
|
||||
detail=str(error.original),
|
||||
)
|
||||
|
||||
@@ -293,6 +293,10 @@ class Router:
|
||||
"model": OpenBBErrorResponse,
|
||||
"description": "Internal Error",
|
||||
},
|
||||
502: {
|
||||
"model": OpenBBErrorResponse,
|
||||
"description": "Unauthorized",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ from typing import Any, Callable, Optional, TypeVar, overload
|
||||
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
from openbb_core.env import Env
|
||||
from openbb_core.provider.utils.errors import EmptyDataError, UnauthorizedError
|
||||
from pydantic import ValidationError, validate_call
|
||||
from typing_extensions import ParamSpec
|
||||
|
||||
@@ -85,10 +86,21 @@ def exception_handler(func: Callable[P, R]) -> Callable[P, R]:
|
||||
raise OpenBBError(f"\n[Error] -> {error_str}").with_traceback(
|
||||
tb
|
||||
) from None
|
||||
if isinstance(e, UnauthorizedError):
|
||||
raise UnauthorizedError(f"\n[Error] -> {str(e)}").with_traceback(
|
||||
tb
|
||||
) from None
|
||||
if isinstance(e, EmptyDataError):
|
||||
raise EmptyDataError(f"\n[Empty] -> {str(e)}").with_traceback(
|
||||
tb
|
||||
) from None
|
||||
if isinstance(e, OpenBBError):
|
||||
raise OpenBBError(f"\n[Error] -> {str(e)}").with_traceback(tb) from None
|
||||
raise OpenBBError("\n[Error] -> Unexpected error.").with_traceback(
|
||||
tb
|
||||
) from None
|
||||
if isinstance(e, Exception):
|
||||
raise OpenBBError(
|
||||
f"\n[Unexpected Error] -> {e.__class__.__name__} -> {str(e.args[0] or e.args)}"
|
||||
).with_traceback(tb) from None
|
||||
|
||||
return None
|
||||
|
||||
return wrapper
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Custom exceptions for the provider."""
|
||||
|
||||
from typing import Union
|
||||
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
|
||||
|
||||
@@ -12,3 +14,26 @@ class EmptyDataError(OpenBBError):
|
||||
"""Initialize the exception."""
|
||||
self.message = message
|
||||
super().__init__(self.message)
|
||||
|
||||
|
||||
class UnauthorizedError(OpenBBError):
|
||||
"""Exception raised for an unauthorized provider request response."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: Union[str, tuple[str]] = (
|
||||
"Unauthorized <provider name> API request."
|
||||
" Please check your <provider name> credentials and subscription access.",
|
||||
),
|
||||
provider_name: str = "<provider name>",
|
||||
):
|
||||
"""Initialize the exception."""
|
||||
if provider_name and provider_name != "<provider name>":
|
||||
msg = message
|
||||
if isinstance(msg, tuple):
|
||||
msg = msg[0].replace("<provider name>", provider_name)
|
||||
elif isinstance(msg, str):
|
||||
msg = msg.replace("<provider name>", provider_name)
|
||||
message = msg
|
||||
self.message = message
|
||||
super().__init__(self.message)
|
||||
|
||||
@@ -8,17 +8,13 @@ from datetime import (
|
||||
)
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.analyst_search import (
|
||||
AnalystSearchData,
|
||||
AnalystSearchQueryParams,
|
||||
)
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from openbb_core.provider.utils.helpers import (
|
||||
amake_request,
|
||||
get_querystring,
|
||||
safe_fromtimestamp,
|
||||
)
|
||||
from pydantic import Field, field_validator, model_validator
|
||||
|
||||
|
||||
@@ -367,6 +363,9 @@ class BenzingaAnalystSearchData(AnalystSearchData):
|
||||
@classmethod
|
||||
def validate_date(cls, v: float) -> Optional[dateType]:
|
||||
"""Validate last_updated."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from openbb_core.provider.utils.helpers import safe_fromtimestamp
|
||||
|
||||
if v:
|
||||
dt = safe_fromtimestamp(v, tz=timezone.utc)
|
||||
return dt.date() if dt.time() == dt.min.time() else dt
|
||||
@@ -414,15 +413,31 @@ class BenzingaAnalystSearchFetcher(
|
||||
**kwargs: Any,
|
||||
) -> List[Dict]:
|
||||
"""Extract the raw data."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from openbb_benzinga.utils.helpers import response_callback
|
||||
from openbb_core.provider.utils.helpers import amake_request, get_querystring
|
||||
|
||||
token = credentials.get("benzinga_api_key") if credentials else ""
|
||||
querystring = get_querystring(query.model_dump(), [])
|
||||
querystring = get_querystring(query.model_dump(by_alias=True), [])
|
||||
url = f"https://api.benzinga.com/api/v2.1/calendar/ratings/analysts?{querystring}&token={token}"
|
||||
response = await amake_request(url, **kwargs)
|
||||
data = await amake_request(url, response_callback=response_callback, **kwargs)
|
||||
|
||||
if not response:
|
||||
raise EmptyDataError()
|
||||
if (isinstance(data, list) and not data) or (
|
||||
isinstance(data, dict) and not data.get("analyst_ratings_analyst")
|
||||
):
|
||||
raise EmptyDataError("No ratings data returned.")
|
||||
|
||||
return response.get("analyst_ratings_analyst") # type: ignore
|
||||
if isinstance(data, dict) and "analyst_ratings_analyst" not in data:
|
||||
raise OpenBBError(
|
||||
f"Unexpected data format. Expected 'analyst_ratings_analyst' key, got: {list(data.keys())}"
|
||||
)
|
||||
|
||||
if not isinstance(data, dict):
|
||||
raise OpenBBError(
|
||||
f"Unexpected data format. Expected dict, got: {type(data).__name__}"
|
||||
)
|
||||
|
||||
return data["analyst_ratings_analyst"]
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
"""Benzinga Company News Model."""
|
||||
|
||||
import math
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
from datetime import (
|
||||
date as dateType,
|
||||
datetime,
|
||||
)
|
||||
from typing import Any, Dict, List, Literal, Optional
|
||||
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.company_news import (
|
||||
CompanyNewsData,
|
||||
CompanyNewsQueryParams,
|
||||
)
|
||||
from openbb_core.provider.utils.descriptions import QUERY_DESCRIPTIONS
|
||||
from openbb_core.provider.utils.helpers import amake_requests, get_querystring
|
||||
from openbb_core.provider.utils.errors import EmptyDataError, UnauthorizedError
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
|
||||
@@ -148,6 +150,12 @@ class BenzingaCompanyNewsFetcher(
|
||||
**kwargs: Any,
|
||||
) -> List[Dict]:
|
||||
"""Extract data."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
import asyncio # noqa
|
||||
import math
|
||||
from openbb_core.provider.utils.helpers import amake_request, get_querystring
|
||||
from openbb_benzinga.utils.helpers import response_callback
|
||||
|
||||
token = credentials.get("benzinga_api_key") if credentials else ""
|
||||
|
||||
base_url = "https://api.benzinga.com/api/v2/news"
|
||||
@@ -165,11 +173,28 @@ class BenzingaCompanyNewsFetcher(
|
||||
for page in range(pages)
|
||||
]
|
||||
|
||||
data = await amake_requests(urls, **kwargs)
|
||||
results: list = []
|
||||
|
||||
return data[: query.limit]
|
||||
async def get_one(url):
|
||||
"""Get data for one url."""
|
||||
try:
|
||||
response = await amake_request(
|
||||
url, response_callback=response_callback, **kwargs
|
||||
)
|
||||
if response:
|
||||
results.extend(response)
|
||||
except (OpenBBError, UnauthorizedError) as e:
|
||||
raise e from e
|
||||
|
||||
await asyncio.gather(*[get_one(url) for url in urls])
|
||||
|
||||
if not results:
|
||||
raise EmptyDataError("The request was returned empty.")
|
||||
|
||||
return sorted(
|
||||
results, key=lambda x: x.get("created"), reverse=query.order == "desc"
|
||||
)[: query.limit if query.limit else len(results)]
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: BenzingaCompanyNewsQueryParams,
|
||||
|
||||
@@ -10,6 +10,7 @@ from datetime import (
|
||||
)
|
||||
from typing import Any, Dict, List, Literal, Optional, Union
|
||||
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.price_target import (
|
||||
PriceTargetData,
|
||||
@@ -17,11 +18,6 @@ from openbb_core.provider.standard_models.price_target import (
|
||||
)
|
||||
from openbb_core.provider.utils.descriptions import QUERY_DESCRIPTIONS
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from openbb_core.provider.utils.helpers import (
|
||||
amake_requests,
|
||||
get_querystring,
|
||||
safe_fromtimestamp,
|
||||
)
|
||||
from pydantic import Field, field_validator, model_validator
|
||||
|
||||
COVERAGE_DICT = {
|
||||
@@ -58,10 +54,26 @@ class BenzingaPriceTargetQueryParams(PriceTargetQueryParams):
|
||||
"firm_ids": "parameters[firm_id]",
|
||||
}
|
||||
__json_schema_extra__ = {
|
||||
"symbol": ["multiple_items_allowed"],
|
||||
"analyst_ids": ["multiple_items_allowed"],
|
||||
"firm_ids": ["multiple_items_allowed"],
|
||||
"fields": ["multiple_items_allowed"],
|
||||
"symbol": {"multiple_items_allowed": True},
|
||||
"analyst_ids": {"multiple_items_allowed": True},
|
||||
"firm_ids": {"multiple_items_allowed": True},
|
||||
"fields": {"multiple_items_allowed": True},
|
||||
"action": {
|
||||
"multiple_items_allowed": False,
|
||||
"choices": [
|
||||
"downgrades",
|
||||
"maintains",
|
||||
"reinstates",
|
||||
"reiterates",
|
||||
"upgrades",
|
||||
"assumes",
|
||||
"initiates",
|
||||
"terminates",
|
||||
"removes",
|
||||
"suspends",
|
||||
"firm_dissolved",
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
page: Optional[int] = Field(
|
||||
@@ -231,6 +243,9 @@ class BenzingaPriceTargetData(PriceTargetData):
|
||||
@classmethod
|
||||
def validate_date(cls, v: float) -> Optional[dateType]:
|
||||
"""Convert the Unix timestamp to a datetime object."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from openbb_core.provider.utils.helpers import safe_fromtimestamp
|
||||
|
||||
if v:
|
||||
dt = safe_fromtimestamp(v, tz=timezone.utc)
|
||||
return dt.date() if dt.time() == dt.min.time() else dt
|
||||
@@ -263,18 +278,30 @@ class BenzingaPriceTargetFetcher(
|
||||
**kwargs: Any,
|
||||
) -> List[Dict]:
|
||||
"""Return the raw data from the Benzinga endpoint."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from openbb_benzinga.utils.helpers import response_callback
|
||||
from openbb_core.provider.utils.helpers import amake_request, get_querystring
|
||||
|
||||
token = credentials.get("benzinga_api_key") if credentials else ""
|
||||
|
||||
base_url = "https://api.benzinga.com/api/v2.1/calendar/ratings"
|
||||
querystring = get_querystring(query.model_dump(by_alias=True), [])
|
||||
|
||||
url = f"{base_url}?{querystring}&token={token}"
|
||||
data = await amake_requests(url, **kwargs)
|
||||
data = await amake_request(url, response_callback=response_callback, **kwargs)
|
||||
|
||||
if not data:
|
||||
raise EmptyDataError()
|
||||
if isinstance(data, dict) and "ratings" not in data:
|
||||
raise OpenBBError(
|
||||
f"Unexpected data format. Expected 'ratings' key, got: {list(data.keys())}"
|
||||
)
|
||||
if not isinstance(data, dict):
|
||||
raise OpenBBError(
|
||||
f"Unexpected data format. Expected dict, got: {type(data)}"
|
||||
)
|
||||
if isinstance(data, dict) and not data.get("ratings"):
|
||||
raise EmptyDataError("No ratings data returned.")
|
||||
|
||||
return data[0].get("ratings")
|
||||
return data["ratings"]
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
|
||||
@@ -2,20 +2,20 @@
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
import math
|
||||
from datetime import (
|
||||
date as dateType,
|
||||
datetime,
|
||||
)
|
||||
from typing import Any, Dict, List, Literal, Optional
|
||||
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.world_news import (
|
||||
WorldNewsData,
|
||||
WorldNewsQueryParams,
|
||||
)
|
||||
from openbb_core.provider.utils.descriptions import QUERY_DESCRIPTIONS
|
||||
from openbb_core.provider.utils.helpers import amake_requests, get_querystring
|
||||
from openbb_core.provider.utils.errors import EmptyDataError, UnauthorizedError
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
|
||||
@@ -146,6 +146,12 @@ class BenzingaWorldNewsFetcher(
|
||||
**kwargs: Any,
|
||||
) -> List[Dict]:
|
||||
"""Extract the data."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
import asyncio # noqa
|
||||
import math
|
||||
from openbb_core.provider.utils.helpers import amake_request, get_querystring
|
||||
from openbb_benzinga.utils.helpers import response_callback
|
||||
|
||||
token = credentials.get("benzinga_api_key") if credentials else ""
|
||||
base_url = "https://api.benzinga.com/api/v2/news"
|
||||
|
||||
@@ -159,9 +165,27 @@ class BenzingaWorldNewsFetcher(
|
||||
for page in range(pages)
|
||||
]
|
||||
|
||||
data = await amake_requests(urls, **kwargs)
|
||||
results: list = []
|
||||
|
||||
return data[: query.limit]
|
||||
async def get_one(url):
|
||||
"""Get data for one url."""
|
||||
try:
|
||||
response = await amake_request(
|
||||
url, response_callback=response_callback, **kwargs
|
||||
)
|
||||
if response:
|
||||
results.extend(response)
|
||||
except (OpenBBError, UnauthorizedError) as e:
|
||||
raise e from e
|
||||
|
||||
await asyncio.gather(*[get_one(url) for url in urls])
|
||||
|
||||
if not results:
|
||||
raise EmptyDataError("The request was returned empty.")
|
||||
|
||||
return sorted(
|
||||
results, key=lambda x: x.get("created"), reverse=query.order == "desc"
|
||||
)[: query.limit if query.limit else len(results)]
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
"""Benzinga Helpers."""
|
||||
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
from openbb_core.provider.utils.errors import EmptyDataError, UnauthorizedError
|
||||
|
||||
|
||||
async def response_callback(response, _):
|
||||
"""Response callback."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
results = await response.json()
|
||||
if (
|
||||
results
|
||||
and isinstance(results, list)
|
||||
and len(results) == 1
|
||||
and isinstance(results[0], str)
|
||||
):
|
||||
if "access denied" in results[0].lower():
|
||||
raise UnauthorizedError(f"Unauthorized Benzinga request -> {results[0]}")
|
||||
raise OpenBBError(results[0])
|
||||
|
||||
if isinstance(results, list) and not results:
|
||||
raise EmptyDataError("The request was returned empty.")
|
||||
|
||||
return results
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
from typing import Any, Optional, Union
|
||||
from warnings import warn
|
||||
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
@@ -11,6 +11,7 @@ from openbb_core.provider.standard_models.world_news import (
|
||||
WorldNewsData,
|
||||
WorldNewsQueryParams,
|
||||
)
|
||||
from openbb_core.provider.utils.errors import UnauthorizedError
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
|
||||
@@ -36,10 +37,10 @@ class BiztocWorldNewsData(WorldNewsData):
|
||||
"images": "img",
|
||||
}
|
||||
|
||||
images: Optional[List[Dict[str, str]]] = Field(
|
||||
images: Optional[list[dict[str, str]]] = Field(
|
||||
description="Images for the article.", default=None
|
||||
)
|
||||
tags: Optional[List[str]] = Field(description="Tags for the article.", default=None)
|
||||
tags: Optional[list[str]] = Field(description="Tags for the article.", default=None)
|
||||
score: Optional[float] = Field(
|
||||
description="Search relevance score for the article.", default=None
|
||||
)
|
||||
@@ -63,27 +64,38 @@ class BiztocWorldNewsData(WorldNewsData):
|
||||
class BiztocWorldNewsFetcher(
|
||||
Fetcher[
|
||||
BiztocWorldNewsQueryParams,
|
||||
List[BiztocWorldNewsData],
|
||||
list[BiztocWorldNewsData],
|
||||
]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the Biztoc endpoints."""
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: Dict[str, Any]) -> BiztocWorldNewsQueryParams:
|
||||
def transform_query(params: dict[str, Any]) -> BiztocWorldNewsQueryParams:
|
||||
"""Transform the query."""
|
||||
if params.get("start_date") or params.get("end_date"):
|
||||
warn("start_date and end_date are not supported for this endpoint.")
|
||||
return BiztocWorldNewsQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
def extract_data(
|
||||
async def aextract_data(
|
||||
query: BiztocWorldNewsQueryParams,
|
||||
credentials: Optional[Dict[str, str]],
|
||||
credentials: Optional[dict[str, str]],
|
||||
**kwargs: Any,
|
||||
) -> List[Dict]:
|
||||
) -> list[dict]:
|
||||
"""Extract the data from the Biztoc endpoint."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from openbb_core.provider.utils.helpers import make_request
|
||||
from openbb_core.provider.utils.helpers import amake_request, make_request
|
||||
|
||||
async def response_callback(response, _):
|
||||
res = await response.json()
|
||||
if isinstance(res, dict) and "message" in res:
|
||||
if "subscribed" in res["message"].lower():
|
||||
raise UnauthorizedError(
|
||||
f"Unauthorized Biztoc request -> {res['message']}"
|
||||
)
|
||||
raise OpenBBError(res["message"])
|
||||
|
||||
return await response.json()
|
||||
|
||||
api_key = credentials.get("biztoc_api_key") if credentials else ""
|
||||
headers = {
|
||||
@@ -94,11 +106,13 @@ class BiztocWorldNewsFetcher(
|
||||
}
|
||||
base_url = "https://biztoc.p.rapidapi.com/"
|
||||
url = ""
|
||||
response: List = []
|
||||
response: Union[list, dict] = []
|
||||
if query.term:
|
||||
query.term = query.term.replace(" ", "%20")
|
||||
url = base_url + f"search?q={query.term}"
|
||||
response = make_request(url, headers=headers).json()
|
||||
response = await amake_request(
|
||||
url, headers=headers, response_callback=response_callback
|
||||
)
|
||||
elif query.source is not None:
|
||||
sources_response = make_request(
|
||||
"https://biztoc.p.rapidapi.com/sources",
|
||||
@@ -110,17 +124,23 @@ class BiztocWorldNewsFetcher(
|
||||
f"{query.source} not a valid source. Valid sources: {sources}"
|
||||
)
|
||||
url = base_url + f"news/source/{query.source.lower()}"
|
||||
response = make_request(url, headers=headers).json()
|
||||
response = await amake_request(
|
||||
url, headers=headers, response_callback=response_callback
|
||||
)
|
||||
else:
|
||||
url1 = base_url + "news/latest"
|
||||
url2 = base_url + "news/topics"
|
||||
response = make_request(url1, headers=headers).json()
|
||||
response2 = make_request(url2, headers=headers).json()
|
||||
response = await amake_request(
|
||||
url1, headers=headers, response_callback=response_callback
|
||||
)
|
||||
response2 = await amake_request(
|
||||
url2, headers=headers, response_callback=response_callback
|
||||
)
|
||||
if response2:
|
||||
for topic in response2:
|
||||
stories = topic.get("stories", [])
|
||||
if stories:
|
||||
response.extend(
|
||||
response.extend( # type: ignore
|
||||
{
|
||||
"text" if k == "body_preview" else k: v
|
||||
for k, v in story.items()
|
||||
@@ -128,14 +148,14 @@ class BiztocWorldNewsFetcher(
|
||||
for story in stories
|
||||
)
|
||||
|
||||
return response
|
||||
return response # type: ignore
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: BiztocWorldNewsQueryParams, data: List[Dict], **kwargs: Any
|
||||
) -> List[BiztocWorldNewsData]:
|
||||
query: BiztocWorldNewsQueryParams, data: list[dict], **kwargs: Any
|
||||
) -> list[BiztocWorldNewsData]:
|
||||
"""Transform the data to the standard format."""
|
||||
results: List[BiztocWorldNewsData] = []
|
||||
results: list[BiztocWorldNewsData] = []
|
||||
for item in data:
|
||||
item.pop("id", None)
|
||||
item.pop("uid", None)
|
||||
|
||||
@@ -26,7 +26,7 @@ interactions:
|
||||
Herald","web":"https://www.bostonherald.com/business/"},{"id":"bizjournals","title":"Business
|
||||
Journals","web":"https://www.bizjournals.com/"},{"id":"cbc","title":"CBC","web":"https://www.cbc.ca/s"},{"id":"cbs","title":"CBS","web":"https://www.cbsnews.com"},{"id":"cnbc","title":"CNBC","web":"https://www.cnbc.com/"},{"id":"cnn","title":"CNN","web":"https://edition.cnn.com/business"},{"id":"chicago","title":"Chicago
|
||||
Tribune","web":"https://www.chicagotribune.com/business/"},{"id":"coindesk","title":"Coindesk","web":"https://www.coindesk.com"},{"id":"dw","title":"DW","web":"https://www.dw.com/en/business/s-1431"},{"id":"dailyupside","title":"Daily
|
||||
Upside","web":"https://www.thedailyupside.com/"},{"id":"dealbreaker","title":"Dealbreaker","web":"https://dealbreaker.com/"},{"id":"entrepreneur","title":"Entrepreneur","web":"https://www.entrepreneur.com/"},{"id":"euronews","title":"Euronews","web":"https://www.euronews.com/"},{"id":"fastcompany","title":"FastCompany","web":"https://www.fastcompany.com"},{"id":"finpost","title":"Financial
|
||||
Upside","web":"https://www.thedailyupside.com/"},{"id":"dealbreaker","title":"Dealbreaker","web":"https://dealbreaker.com/"},{"id":"entrepreneur","title":"Entrepreneur","web":"https://www.entrepreneur.com/"},{"id":"stocktwits","title":"EurActiv","web":"https://www.euractiv.com/"},{"id":"euronews","title":"Euronews","web":"https://www.euronews.com/"},{"id":"fastcompany","title":"FastCompany","web":"https://www.fastcompany.com"},{"id":"finpost","title":"Financial
|
||||
Post","web":"https://financialpost.com/"},{"id":"ft","title":"Financial Times","web":"https://www.ft.com"},{"id":"forbes","title":"Forbes","web":"https://www.forbes.com"},{"id":"fortune","title":"Fortune","web":"https://fortune.com/"},{"id":"fox","title":"Fox
|
||||
Business","web":"https://www.foxbusiness.com"},{"id":"globeandmail","title":"Globe
|
||||
And Mail","web":"https://www.theglobeandmail.com"},{"id":"google_business","title":"Google
|
||||
@@ -63,45 +63,47 @@ interactions:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 89a4a7f40c219f15-FRA
|
||||
- 8d46253b4f35c990-IAD
|
||||
Cache-Control:
|
||||
- no-store, no-cache, must-revalidate, max-age=0
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Encoding:
|
||||
- gzip
|
||||
Content-Length:
|
||||
- '1800'
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Thu, 27 Jun 2024 10:11:58 GMT
|
||||
- Fri, 18 Oct 2024 05:32:01 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=qqd5%2FZT%2FpjnF59niBWwAAMKoNSHaARhHE%2BCScAOamvVMXueCOGrH6Cyf6yENE0qdAIp4u8QJXurzuHxBuuFVmpVCkBu%2BqOoko4Gt%2BezOVomoB2E5UoYXIZXwGcg%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=6AJ37sdn9g018QqveznvFOUcc06J0MlsSB7CDEFKhN7XOhnexO7oUWGOx9%2BTeTH%2BUfbmxCFfUB9dRstLZUvq6koiunLgudzqseaUyrLMRwcL9G4TTGHbblRtT88%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- RapidAPI-1.2.8
|
||||
Strict-Transport-Security:
|
||||
- max-age=31556926; includeSubDomains
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
Vary:
|
||||
- Accept-Encoding, Cookie
|
||||
X-RapidAPI-Region:
|
||||
- AWS - eu-central-1
|
||||
- AWS - us-east-1
|
||||
X-RapidAPI-Request-Id:
|
||||
- bb52b953b619ebdbae3d20f65f30c5041993d53bfcb4bd646a484856e7ad1bad
|
||||
X-RapidAPI-Version:
|
||||
- 1.2.8
|
||||
X-RateLimit-Requests-Limit:
|
||||
- '2000'
|
||||
X-RateLimit-Requests-Remaining:
|
||||
- '1995'
|
||||
- '1914'
|
||||
X-RateLimit-Requests-Reset:
|
||||
- '1996936'
|
||||
- '1620568'
|
||||
X-RateLimit-rapid-free-plans-hard-limit-Limit:
|
||||
- '500000'
|
||||
X-RateLimit-rapid-free-plans-hard-limit-Remaining:
|
||||
- '499995'
|
||||
- '499961'
|
||||
X-RateLimit-rapid-free-plans-hard-limit-Reset:
|
||||
- '1996936'
|
||||
- '1620568'
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
permissions-policy:
|
||||
@@ -109,7 +111,7 @@ interactions:
|
||||
referrer-policy:
|
||||
- strict-origin-when-cross-origin
|
||||
rndr-id:
|
||||
- 78f5e9bd-116f-4106
|
||||
- 96769602-78bc-4d9d
|
||||
x-content-type-options:
|
||||
- nosniff
|
||||
x-render-origin-server:
|
||||
@@ -124,8 +126,6 @@ interactions:
|
||||
- application/json
|
||||
Accept-Encoding:
|
||||
- gzip
|
||||
Connection:
|
||||
- keep-alive
|
||||
X-RapidAPI-Host:
|
||||
- biztoc.p.rapidapi.com
|
||||
X-RapidAPI-Key:
|
||||
@@ -134,171 +134,153 @@ interactions:
|
||||
uri: https://biztoc.p.rapidapi.com/news/source/bloomberg
|
||||
response:
|
||||
body:
|
||||
string: '[{"body_preview":"","img":{"s":"https://cdn.biztoc.com/671d384ea549859a_s.webp","sq":"https://cdn.biztoc.com/671d384ea549859a_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 10:00:32 GMT","title":"The Secret to a Long Life Is in Your Garage","url":"https://www.bloomberg.com/news/newsletters/2024-06-27/biking-is-the-secret-to-longevity?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/7a092becc648f312_s.webp","sq":"https://cdn.biztoc.com/7a092becc648f312_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 10:00:32 GMT","title":"RTO Mandates Are Killing the Euphoric Work-Life
|
||||
Balance Some Moms Found","url":"https://www.bloomberg.com/news/features/2024-06-27/return-to-office-is-killing-work-life-balance-dream-for-moms?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/ce50045cff5cc694_s.webp","sq":"https://cdn.biztoc.com/ce50045cff5cc694_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 10:00:29 GMT","title":"Game-Changing HIV Shot Can\u2019t Get to
|
||||
High-Risk Groups Fast Enough","url":"https://www.bloomberg.com/opinion/articles/2024-06-27/gilead-hiv-shot-lenacapavir-can-t-get-to-high-risk-groups-fast-enough?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/05c5bdea66614330_s.webp","sq":"https://cdn.biztoc.com/05c5bdea66614330_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 10:00:31 GMT","title":"What to Watch in Biden and Trump\u2019s
|
||||
First Presidential Debate of 2024","url":"https://www.bloomberg.com/news/articles/2024-06-27/trump-vs-biden-1st-debate-what-to-expect-polls-how-to-watch?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/e3ca73bdd000e5c0_s.webp","sq":"https://cdn.biztoc.com/e3ca73bdd000e5c0_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 09:57:49 GMT","title":"Hedge Funds Sell Tech Stocks \u2018Aggressively,\u2019
|
||||
Goldman Says","url":"https://www.bloomberg.com/news/articles/2024-06-27/hedge-funds-are-selling-tech-stocks-aggressively-goldman-says?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/2cdcc4f31a5bc8a7_s.webp","sq":"https://cdn.biztoc.com/2cdcc4f31a5bc8a7_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 09:30:01 GMT","title":"BOE Warns on \u2018Struggling\u2019 Private
|
||||
Equity-Backed Firms","url":"https://www.bloomberg.com/news/articles/2024-06-27/boe-warns-on-struggling-private-equity-backed-firms?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/4c381d961d53cf72_s.webp","sq":"https://cdn.biztoc.com/4c381d961d53cf72_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 09:59:15 GMT","title":"Economic Insecurity Roils Politics From
|
||||
Kenya to France","url":"https://www.bloomberg.com/news/newsletters/2024-06-27/economic-insecurity-is-agitating-politics-from-kenya-to-france?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/e3225d824ef43d63_s.webp","sq":"https://cdn.biztoc.com/e3225d824ef43d63_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 09:21:46 GMT","title":"China\u2019s Offshore Wind Power Prices
|
||||
to Undercut Coal This Year","url":"https://www.bloomberg.com/news/articles/2024-06-27/china-s-offshore-wind-power-prices-to-undercut-coal-this-year?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/f273766c417f6d3c_s.webp","sq":"https://cdn.biztoc.com/f273766c417f6d3c_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 09:33:49 GMT","title":"Largest Thai Mobile Operator True Debuts
|
||||
Yen Sustainability Loan","url":"https://www.bloomberg.com/news/articles/2024-06-27/largest-thai-mobile-operator-true-debuts-yen-sustainability-loan?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/e088074911846227_s.webp","sq":"https://cdn.biztoc.com/e088074911846227_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 09:41:28 GMT","title":"India Credit Seen Benefiting From Index-Related
|
||||
Bond Flows","url":"https://www.bloomberg.com/news/articles/2024-06-27/india-credit-seen-benefiting-from-index-related-bond-flows?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/460acda0d69de5b1_s.webp","sq":"https://cdn.biztoc.com/460acda0d69de5b1_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 09:30:43 GMT","title":"Zambia Inflation Hits 29-Month High on
|
||||
Worst Drought in Decades","url":"https://www.bloomberg.com/news/articles/2024-06-27/zambia-inflation-hits-29-month-high-on-worst-drought-in-decades?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/72e6e5767c04fe2d_s.webp","sq":"https://cdn.biztoc.com/72e6e5767c04fe2d_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 09:30:00 GMT","title":"RBA\u2019s Hauser Says Current Account
|
||||
Surpluses May Not Persist","url":"https://www.bloomberg.com/news/articles/2024-06-27/rba-s-hauser-says-current-account-surpluses-may-not-persist?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/961c40ef2d2996a6_s.webp","sq":"https://cdn.biztoc.com/961c40ef2d2996a6_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 09:44:02 GMT","title":"UK Mortgage Costs Could Fall For 1.5 Million
|
||||
This Year, BOE Says","url":"https://www.bloomberg.com/news/articles/2024-06-27/uk-mortgage-costs-could-fall-for-1-5-million-this-year-boe-says?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/916efa0233eddfa8_s.webp","sq":"https://cdn.biztoc.com/916efa0233eddfa8_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 07:49:43 GMT","title":"Housing Crises Take a Toll on the World\u2019s
|
||||
Most Liveable Cities","url":"https://www.bloomberg.com/news/articles/2024-06-27/housing-crises-take-a-toll-on-the-world-s-most-liveable-cities?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/0c8d68a121623d42_s.webp","sq":"https://cdn.biztoc.com/0c8d68a121623d42_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 09:27:11 GMT","title":"Boeing Sees Fewer Fuselage Defects, Helping
|
||||
Lift Productivity","url":"https://www.bloomberg.com/news/articles/2024-06-27/boeing-sees-lower-fuselage-defects-helping-lift-productivity?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/bbd12d7a7df1d29f_s.webp","sq":"https://cdn.biztoc.com/bbd12d7a7df1d29f_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 09:15:26 GMT","title":"Rand Resumes Losses as South Africa Cabinet
|
||||
Delay Fuels Angst","url":"https://www.bloomberg.com/news/articles/2024-06-27/rand-resumes-losses-as-south-africa-cabinet-delay-fuels-angst?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/c2798367d9d8a2ea_s.webp","sq":"https://cdn.biztoc.com/c2798367d9d8a2ea_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 09:20:00 GMT","title":"Egypt\u2019s $10 Billion Wind Farm Is Poised
|
||||
to Break Ground in 2026","url":"https://www.bloomberg.com/news/articles/2024-06-27/egypt-s-10-billion-wind-farm-is-poised-to-break-ground-in-2026?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/86425a91d3933c5a_s.webp","sq":"https://cdn.biztoc.com/86425a91d3933c5a_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 09:15:01 GMT","title":"Boeing Said Cleared to Resume Wide-Body
|
||||
Jet Deliveries to China","url":"https://www.bloomberg.com/news/articles/2024-06-27/boeing-said-cleared-to-resume-wide-body-jet-deliveries-to-china?ref=biztoc.com"},{"body_preview":"","img":null,"published":"Thu,
|
||||
27 Jun 2024 08:48:39 GMT","title":"ECB May Only Cut Rates Once More This Year,
|
||||
Kazimir Says","url":"https://www.bloomberg.com/news/articles/2024-06-27/ecb-may-only-cut-rates-once-more-this-year-kazimir-says?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/ae4962855a7a53f4_s.webp","sq":"https://cdn.biztoc.com/ae4962855a7a53f4_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 09:15:20 GMT","title":"Macron? Le Pen? A Guide to France\u2019s
|
||||
Crucial Election","url":"https://www.bloomberg.com/news/articles/2024-06-27/france-election-2024-macron-le-pen-and-a-political-system-under-strain?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/86d725dd5c6e6ff1_s.webp","sq":"https://cdn.biztoc.com/86d725dd5c6e6ff1_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 09:00:31 GMT","title":"Democrats Keep Feuding in Philly, Imperiling
|
||||
Biden''s Reelection","url":"https://www.bloomberg.com/news/features/2024-06-27/election-2024-to-beat-trump-biden-needs-philly-democrats-to-get-along?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/78394a8d264dc840_s.webp","sq":"https://cdn.biztoc.com/78394a8d264dc840_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 09:00:29 GMT","title":"Trump Can\u2019t Handle the Foreign Policy
|
||||
Debate America Needs","url":"https://www.bloomberg.com/opinion/articles/2024-06-27/trump-can-t-handle-the-foreign-policy-debate-america-needs?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/b4b892b60b395257_s.webp","sq":"https://cdn.biztoc.com/b4b892b60b395257_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 08:56:15 GMT","title":"Crypto Exchange Bybit Grabs Global Spotlight
|
||||
in Void Left by FTX","url":"https://www.bloomberg.com/news/articles/2024-06-27/crypto-exchange-bybit-grabs-global-spotlight-in-void-left-by-ftx?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/9ab810dfd969627b_s.webp","sq":"https://cdn.biztoc.com/9ab810dfd969627b_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 08:12:24 GMT","title":"Suu Kyi\u2019s Family Mansion Will Be Up
|
||||
for Bidding Again in August","url":"https://www.bloomberg.com/news/articles/2024-06-27/suu-kyi-s-family-mansion-will-be-up-for-bidding-again-in-august?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/a93b0f0f54e85b83_s.webp","sq":"https://cdn.biztoc.com/a93b0f0f54e85b83_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 08:43:10 GMT","title":"Japan\u2019s Jet Fuel Crunch Hits Narita,
|
||||
One of Its Biggest Airports","url":"https://www.bloomberg.com/news/articles/2024-06-27/japan-s-jet-fuel-crunch-hits-narita-one-of-its-biggest-airports?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/e2f7dbdee28becd4_s.webp","sq":"https://cdn.biztoc.com/e2f7dbdee28becd4_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 08:05:22 GMT","title":"Thai Energy Billionaire Steps Up Data Center
|
||||
Push to Tap AI Boom","url":"https://www.bloomberg.com/news/articles/2024-06-27/thai-energy-billionaire-steps-up-data-center-push-to-tap-ai-boom?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/71fdba8a4bfc9639_s.webp","sq":"https://cdn.biztoc.com/71fdba8a4bfc9639_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 08:24:40 GMT","title":"ECB Will Be Agile in Deciding on Interest-Rate
|
||||
Path, Lane Says","url":"https://www.bloomberg.com/news/articles/2024-06-27/ecb-will-be-agile-in-deciding-on-interest-rate-path-lane-says?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/a41d31526e60312e_s.webp","sq":"https://cdn.biztoc.com/a41d31526e60312e_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 08:25:24 GMT","title":"Activist Fund in Japan Wins Daidoh Board
|
||||
Seats in Rare Victory","url":"https://www.bloomberg.com/news/articles/2024-06-27/activist-fund-in-japan-wins-daidoh-board-seats-in-rare-victory?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/2da6f0fcc289cdb1_s.webp","sq":"https://cdn.biztoc.com/2da6f0fcc289cdb1_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 08:23:15 GMT","title":"Zimbabwe Holds Key Interest Rate at 20%
|
||||
With Eye on Inflation","url":"https://www.bloomberg.com/news/articles/2024-06-27/-zwg-usd-zimbabwe-leaves-benchmark-interest-rate-unchanged-at-20?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/d3aba268bc340b4b_s.webp","sq":"https://cdn.biztoc.com/d3aba268bc340b4b_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 08:00:00 GMT","title":"South Korea Asks Banks for Help When Won\u2019s
|
||||
Trading Hours Extend","url":"https://www.bloomberg.com/news/articles/2024-06-27/south-korea-asks-banks-for-help-when-won-s-trading-hours-extend?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/7cdd1a163283880b_s.webp","sq":"https://cdn.biztoc.com/7cdd1a163283880b_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 08:01:09 GMT","title":"Chinese Builder Sino-Ocean Group Gets Wind-Up
|
||||
Petition in HK","url":"https://www.bloomberg.com/news/articles/2024-06-27/chinese-builder-sino-ocean-group-gets-wind-up-petition-in-hk-lxwz6hu3?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/b9fab14c1ca609f1_s.webp","sq":"https://cdn.biztoc.com/b9fab14c1ca609f1_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 07:44:58 GMT","title":"Bloomberg French Poll of Polls: Le Pen\u2019s
|
||||
Group Makes More Gains","url":"https://www.bloomberg.com/news/articles/2024-06-27/bloomberg-french-poll-of-polls-le-pen-s-group-makes-more-gains?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/228308de25690dd0_s.webp","sq":"https://cdn.biztoc.com/228308de25690dd0_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 07:50:58 GMT","title":"China Says Two Pandas Are Heading to US
|
||||
in Show of \u2018Friendship\u2019","url":"https://www.bloomberg.com/news/articles/2024-06-27/china-says-two-pandas-are-heading-to-us-in-show-of-friendship?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/69a6870fe063ddec_s.webp","sq":"https://cdn.biztoc.com/69a6870fe063ddec_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 08:00:00 GMT","title":"South Africa Achieves First Primary Budget
|
||||
Surplus in 15 Years","url":"https://www.bloomberg.com/news/articles/2024-06-27/south-africa-achieves-first-primary-budget-surplus-in-15-years?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/3be9366671dc10b4_s.webp","sq":"https://cdn.biztoc.com/3be9366671dc10b4_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 08:01:42 GMT","title":"CVC Agrees to Buy UK Infrastructure Contractor
|
||||
M Group Services","url":"https://www.bloomberg.com/news/articles/2024-06-27/cvc-agrees-to-buy-uk-infrastructure-contractor-m-group-services?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/e2926786e44722b4_s.webp","sq":"https://cdn.biztoc.com/e2926786e44722b4_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 08:01:30 GMT","title":"Riksbank Signals as Many as Three More
|
||||
Rate Cuts This Year","url":"https://www.bloomberg.com/news/articles/2024-06-27/riksbank-signals-as-many-as-three-more-rate-cuts-this-year?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/34fec51cafcaf87c_s.webp","sq":"https://cdn.biztoc.com/34fec51cafcaf87c_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 07:36:36 GMT","title":"UBS Creates New Wealth Unit in Khan, Karofsky
|
||||
Strategy Reboot","url":"https://www.bloomberg.com/news/articles/2024-06-27/ubs-unveils-new-wealth-strategy-under-khan-karofsky?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/4ea2d73048f23c34_s.webp","sq":"https://cdn.biztoc.com/4ea2d73048f23c34_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 07:49:43 GMT","title":"Russia Has Sent Around 10,000 Immigrants
|
||||
to Front in Ukraine","url":"https://www.bloomberg.com/news/articles/2024-06-27/russia-has-sent-around-10-000-immigrants-to-front-in-ukraine?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/79f4306688fef94c_s.webp","sq":"https://cdn.biztoc.com/79f4306688fef94c_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 08:00:29 GMT","title":"Bonus Clawback: The Bloomberg Close, Asia
|
||||
Edition","url":"https://www.bloomberg.com/news/newsletters/2024-06-27/bonus-clawback-the-bloomberg-close-asia-edition?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/8151aef0dd4fa159_s.webp","sq":"https://cdn.biztoc.com/8151aef0dd4fa159_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 08:00:31 GMT","title":"The American Entrepreneurs Who First Opened
|
||||
The Chinese Market","url":"https://www.bloomberg.com/news/articles/2024-06-27/the-american-entrepreneurs-who-first-opened-the-chinese-market?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/06997e3c080c6a47_s.webp","sq":"https://cdn.biztoc.com/06997e3c080c6a47_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 06:52:18 GMT","title":"Foreign Funds Sell Japan\u2019s Stocks,
|
||||
Longest Run Since March 2023","url":"https://www.bloomberg.com/news/articles/2024-06-27/foreign-funds-sell-japan-s-stocks-longest-run-since-march-2023?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/c8d8bd948e0f19a8_s.webp","sq":"https://cdn.biztoc.com/c8d8bd948e0f19a8_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 07:12:26 GMT","title":"India Plans \u2018Historic Steps\u2019
|
||||
in Budget Under New Government","url":"https://www.bloomberg.com/news/articles/2024-06-27/india-plans-historic-steps-in-budget-under-new-government?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/a023e218ff87db3a_s.webp","sq":"https://cdn.biztoc.com/a023e218ff87db3a_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 06:47:27 GMT","title":"Quant Mutual\u2019s Clients Pull $168 Million
|
||||
in Days After Probe","url":"https://www.bloomberg.com/news/articles/2024-06-27/quant-mutual-s-clients-pull-168-million-in-days-after-probe?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/6d4cac6984c2b656_s.webp","sq":"https://cdn.biztoc.com/6d4cac6984c2b656_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 05:38:33 GMT","title":"Japan Inflation Outlook Jumps, Backing
|
||||
BOJ Case for Rate Hike","url":"https://www.bloomberg.com/news/articles/2024-06-27/japan-inflation-outlook-jumps-backing-boj-case-for-rate-hike?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/db178aca5d2e7f0a_s.webp","sq":"https://cdn.biztoc.com/db178aca5d2e7f0a_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 05:49:13 GMT","title":"Macquarie Foresees Risk of Wider Yuan Trading
|
||||
Band in China","url":"https://www.bloomberg.com/news/articles/2024-06-27/macquarie-foresees-risk-of-wider-yuan-trading-band-in-china?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/e83fb6bce870ccf7_s.webp","sq":"https://cdn.biztoc.com/e83fb6bce870ccf7_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 04:01:31 GMT","title":"India\u2019s Busy IPO Queue for Second
|
||||
Half Goes Beyond Hyundai Unit","url":"https://www.bloomberg.com/news/articles/2024-06-27/india-s-busy-ipo-queue-for-second-half-goes-beyond-hyundai-unit?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/b9dabf7b272601a8_s.webp","sq":"https://cdn.biztoc.com/b9dabf7b272601a8_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 07:17:26 GMT","title":"GSK Drops as RSV Shot Suffers Fresh Blow
|
||||
With US Recommendation","url":"https://www.bloomberg.com/news/articles/2024-06-27/gsk-drops-as-rsv-shot-suffers-fresh-blow-with-us-recommendation?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/e0ab66f4e886d30a_s.webp","sq":"https://cdn.biztoc.com/e0ab66f4e886d30a_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 07:08:51 GMT","title":"North Korea\u2019s Trash Balloons Can Endanger
|
||||
Airplanes, Aviation Official Warns","url":"https://www.bloomberg.com/news/articles/2024-06-27/north-korea-s-trash-balloons-can-endanger-airplanes-aviation-official-warns?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/575d48ea58ba7100_s.webp","sq":"https://cdn.biztoc.com/575d48ea58ba7100_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 07:05:19 GMT","title":"Toyota to Offload Shares in Parts Maker
|
||||
Aisin in $1 Billion Deal","url":"https://www.bloomberg.com/news/articles/2024-06-27/toyota-to-offload-shares-in-parts-maker-aisin-in-1-billion-deal?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/c8a7619792ea7414_s.webp","sq":"https://cdn.biztoc.com/c8a7619792ea7414_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 07:13:52 GMT","title":"China Miscalculated With Europe in Backing
|
||||
Russia, US Envoy Says","url":"https://www.bloomberg.com/news/articles/2024-06-27/china-miscalculated-with-europe-in-backing-russia-us-envoy-say?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/32227393ba658fb1_s.webp","sq":"https://cdn.biztoc.com/32227393ba658fb1_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 07:00:06 GMT","title":"Weak Yen\u2019s Sway Over Japanese Stock
|
||||
Market Is Unravelling","url":"https://www.bloomberg.com/news/articles/2024-06-27/weak-yen-s-sway-over-japanese-stock-market-is-unravelling?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/b32d08a7bf5b7ca2_s.webp","sq":"https://cdn.biztoc.com/b32d08a7bf5b7ca2_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 07:01:49 GMT","title":"Philippines Keeps Key Rate Steady as Peso
|
||||
Hovers Near Record Low","url":"https://www.bloomberg.com/news/articles/2024-06-27/philippines-keeps-key-rate-steady-as-peso-hovers-near-record-low?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/399ef6b97458c6de_s.webp","sq":"https://cdn.biztoc.com/399ef6b97458c6de_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 07:11:42 GMT","title":"Campari Owner Rejects Claims After Reports
|
||||
of \u20ac1 Billion Unpaid Taxes","url":"https://www.bloomberg.com/news/articles/2024-06-27/campari-owner-rejects-claims-after-reports-of-1b-unpaid-taxes?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/3d1ef51d324ca86d_s.webp","sq":"https://cdn.biztoc.com/3d1ef51d324ca86d_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 05:53:50 GMT","title":"Australia\u2019s No. 2 Pension Fund Says
|
||||
RBA Won\u2019t Hike in August","url":"https://www.bloomberg.com/news/articles/2024-06-27/australia-s-no-2-pension-fund-reckons-rba-won-t-cut-in-august?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/939468f0d35ff041_s.webp","sq":"https://cdn.biztoc.com/939468f0d35ff041_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 06:30:10 GMT","title":"Rahul Gandhi Shares the Stage With Modi
|
||||
After Years in His Shadow","url":"https://www.bloomberg.com/news/articles/2024-06-27/rahul-gandhi-finally-shares-stage-with-india-s-modi-bjp-after-years-in-shadows?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/98e137541f0e753e_s.webp","sq":"https://cdn.biztoc.com/98e137541f0e753e_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 06:45:25 GMT","title":"Top Rolex Seller Sees Signs of Stability
|
||||
in UK Watch Market","url":"https://www.bloomberg.com/news/articles/2024-06-27/top-rolex-seller-sees-signs-of-stability-in-uk-watch-market?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/f8fce111e1d36341_s.webp","sq":"https://cdn.biztoc.com/f8fce111e1d36341_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 06:24:47 GMT","title":"Le Maire Says France Pulled in Extra \u20ac3
|
||||
billion in Tax Receipts","url":"https://www.bloomberg.com/news/articles/2024-06-27/le-maire-says-france-pulled-in-extra-eu3-billion-in-tax-receipts?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/15450a902a4d0722_s.webp","sq":"https://cdn.biztoc.com/15450a902a4d0722_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 03:16:49 GMT","title":"New World Tells Investors It Will Try to
|
||||
Pay Perp Bond Next Year","url":"https://www.bloomberg.com/news/articles/2024-06-27/new-world-tells-investors-it-will-try-to-pay-perp-bond-next-year?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/0dfb5779d1e86302_s.webp","sq":"https://cdn.biztoc.com/0dfb5779d1e86302_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 06:26:29 GMT","title":"H&M Profit Misses as Summer Collections
|
||||
Fail to Lure Shoppers","url":"https://www.bloomberg.com/news/articles/2024-06-27/h-m-profit-misses-as-summer-collections-fail-to-lure-shoppers?ref=biztoc.com"},{"body_preview":"","img":{"s":"https://cdn.biztoc.com/6b892da2567303db_s.webp","sq":"https://cdn.biztoc.com/6b892da2567303db_sq.webp"},"published":"Thu,
|
||||
27 Jun 2024 05:34:36 GMT","title":"Australian PM\u2019s Assange Welcome Sparks
|
||||
Concern Over US Relations","url":"https://www.bloomberg.com/news/articles/2024-06-27/australian-pm-s-assange-welcome-sparks-concern-over-us-relations?ref=biztoc.com"}]
|
||||
|
||||
'
|
||||
string: !!binary |
|
||||
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
|
||||
E83837/9PwAAAP//AwCUFTALXFsAAA==
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 89a4a7f75ec918de-FRA
|
||||
- 8d46253e7fd558ba-IAD
|
||||
Cache-Control:
|
||||
- no-store, no-cache, must-revalidate, max-age=0
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Encoding:
|
||||
- gzip
|
||||
Content-Length:
|
||||
- '5904'
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Thu, 27 Jun 2024 10:11:59 GMT
|
||||
- Fri, 18 Oct 2024 05:32:01 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=yNZDg0%2BZ2k%2FVgkUiTSGQCmqLRboOxdmahSxGhMzK2yxlnLsMIPrriLCq5vOBmzsVz5jCzYKR0LYKlBNcIaW05p5i55ZwAAjhWP77VyyyNwQUe393E1bdGzZetho%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=1jncDmrg1XfuP0IcLzSdFqxnDMUXcq%2FYLEq7Y47JCzrfHGKMIYKTdVDqO5wAJ1CDVG8lCorUMgO5Aw5pGQ4cBY8eGoB%2FI0iTPcN%2FHHf9%2Bz3zMUu9I%2FdQYPbfHmI%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- RapidAPI-1.2.8
|
||||
Strict-Transport-Security:
|
||||
- max-age=31556926; includeSubDomains
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
Vary:
|
||||
- Accept-Encoding, Cookie
|
||||
X-RapidAPI-Region:
|
||||
- AWS - eu-central-1
|
||||
- AWS - us-east-1
|
||||
X-RapidAPI-Request-Id:
|
||||
- c9f0a6fb613c5c0a2361b3f48bba6dcc7bc83feaa827675cb22eb492b1669872
|
||||
X-RapidAPI-Version:
|
||||
- 1.2.8
|
||||
X-RateLimit-Requests-Limit:
|
||||
- '2000'
|
||||
X-RateLimit-Requests-Remaining:
|
||||
- '1994'
|
||||
- '1913'
|
||||
X-RateLimit-Requests-Reset:
|
||||
- '1996935'
|
||||
- '1620568'
|
||||
X-RateLimit-rapid-free-plans-hard-limit-Limit:
|
||||
- '500000'
|
||||
X-RateLimit-rapid-free-plans-hard-limit-Remaining:
|
||||
- '499994'
|
||||
- '499960'
|
||||
X-RateLimit-rapid-free-plans-hard-limit-Reset:
|
||||
- '1996935'
|
||||
- '1620568'
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
permissions-policy:
|
||||
@@ -306,7 +288,7 @@ interactions:
|
||||
referrer-policy:
|
||||
- strict-origin-when-cross-origin
|
||||
rndr-id:
|
||||
- 6526935a-5651-4667
|
||||
- 8911030f-1e15-4bb9
|
||||
x-content-type-options:
|
||||
- nosniff
|
||||
x-render-origin-server:
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
import asyncio
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Dict, List, Optional
|
||||
from warnings import warn
|
||||
@@ -13,7 +12,7 @@ from openbb_core.provider.standard_models.economic_calendar import (
|
||||
EconomicCalendarData,
|
||||
EconomicCalendarQueryParams,
|
||||
)
|
||||
from openbb_core.provider.utils.helpers import amake_request
|
||||
from openbb_core.provider.utils.errors import EmptyDataError, UnauthorizedError
|
||||
from pydantic import Field, field_validator, model_validator
|
||||
|
||||
|
||||
@@ -97,6 +96,11 @@ class FMPEconomicCalendarFetcher(
|
||||
**kwargs: Any,
|
||||
) -> List[Dict]:
|
||||
"""Return the data from the FMP endpoint."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
import asyncio # noqa
|
||||
from openbb_core.provider.utils.helpers import amake_request
|
||||
from openbb_fmp.utils.helpers import response_callback
|
||||
|
||||
api_key = credentials.get("fmp_api_key") if credentials else ""
|
||||
|
||||
base_url = "https://financialmodelingprep.com/api/v3/economic_calendar?"
|
||||
@@ -123,12 +127,12 @@ class FMPEconomicCalendarFetcher(
|
||||
"""Get data for one URL."""
|
||||
n_urls = 1
|
||||
try:
|
||||
result = await amake_request(url, **kwargs)
|
||||
result = await amake_request(url, response_callback=response_callback)
|
||||
if result:
|
||||
if "Error Message" in result:
|
||||
raise OpenBBError(result["Error Message"])
|
||||
results.extend(result)
|
||||
except Exception as e:
|
||||
except UnauthorizedError as e:
|
||||
raise e from e
|
||||
except OpenBBError as e:
|
||||
if len(urls) == 1 or (len(urls) > 1 and n_urls == len(urls)):
|
||||
raise e from e
|
||||
warn(f"Error in fetching part of the data from FMP -> {e}")
|
||||
@@ -136,6 +140,9 @@ class FMPEconomicCalendarFetcher(
|
||||
|
||||
await asyncio.gather(*[get_one(url) for url in urls])
|
||||
|
||||
if not results:
|
||||
raise EmptyDataError("The request was returned empty.")
|
||||
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -4,19 +4,27 @@ from datetime import date
|
||||
from typing import Any, List, Optional, Union
|
||||
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from openbb_core.provider.utils.errors import EmptyDataError, UnauthorizedError
|
||||
from openbb_core.provider.utils.helpers import get_querystring
|
||||
|
||||
|
||||
async def response_callback(response, _):
|
||||
"""Use callback for make_request."""
|
||||
data = await response.json()
|
||||
if isinstance(data, dict) and "Error Message" in data:
|
||||
raise OpenBBError(f"FMP Error Message -> {data['Error Message']}")
|
||||
if isinstance(data, dict) and "error" in data:
|
||||
raise OpenBBError(
|
||||
f"FMP Error Message -> {data['error']}. Status code: {response.status}"
|
||||
)
|
||||
if isinstance(data, dict):
|
||||
error_message = data.get("Error Message", data.get("error"))
|
||||
if error_message is not None:
|
||||
conditions = (
|
||||
"upgrade" in error_message.lower()
|
||||
or "exclusive endpoint" in error_message.lower()
|
||||
or "subscription" in error_message.lower()
|
||||
or "unauthorized" in error_message.lower()
|
||||
)
|
||||
if conditions:
|
||||
raise UnauthorizedError(f"Unauthorized FMP request -> {error_message}")
|
||||
raise OpenBBError(
|
||||
f"FMP Error Message -> Status code: {response.status} -> {error_message}"
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ from openbb_core.provider.standard_models.company_news import (
|
||||
CompanyNewsData,
|
||||
CompanyNewsQueryParams,
|
||||
)
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from openbb_core.provider.utils.errors import EmptyDataError, UnauthorizedError
|
||||
from openbb_core.provider.utils.helpers import (
|
||||
amake_request,
|
||||
get_querystring,
|
||||
@@ -219,8 +219,14 @@ class IntrinioCompanyNewsFetcher(
|
||||
async def callback(response, session):
|
||||
"""Response callback."""
|
||||
result = await response.json()
|
||||
if "error" in result:
|
||||
raise OpenBBError(f"Intrinio Error Message -> {result['error']}")
|
||||
|
||||
if isinstance(result, dict) and "error" in result:
|
||||
if "api key" in result.get("message", "").lower():
|
||||
raise UnauthorizedError(
|
||||
f"Unauthorized Intrinio request -> {result.get('message')}"
|
||||
)
|
||||
raise OpenBBError(f"Error in Intrinio request -> {result}")
|
||||
|
||||
symbol = response.url.parts[-2]
|
||||
_data = result.get("news", [])
|
||||
data = []
|
||||
|
||||
@@ -12,7 +12,7 @@ from openbb_core.provider.standard_models.forward_ebitda_estimates import (
|
||||
ForwardEbitdaEstimatesData,
|
||||
ForwardEbitdaEstimatesQueryParams,
|
||||
)
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from openbb_core.provider.utils.errors import EmptyDataError, UnauthorizedError
|
||||
from openbb_core.provider.utils.helpers import (
|
||||
amake_request,
|
||||
get_querystring,
|
||||
@@ -135,9 +135,15 @@ class IntrinioForwardEbitdaEstimatesFetcher(
|
||||
async def fetch_callback(response, session):
|
||||
"""Use callback for pagination."""
|
||||
data = await response.json()
|
||||
messages = data.get("messages")
|
||||
if messages:
|
||||
raise OpenBBError(str(messages))
|
||||
error = data.get("error", None)
|
||||
if error:
|
||||
message = data.get("message", "")
|
||||
if "api key" in message.lower():
|
||||
raise UnauthorizedError(
|
||||
f"Unauthorized Intrinio request -> {message}"
|
||||
)
|
||||
raise OpenBBError(f"Error: {error} -> {message}")
|
||||
|
||||
estimates = data.get("ebitda_consensus", []) # type: ignore
|
||||
if estimates and len(estimates) > 0:
|
||||
results.extend(estimates)
|
||||
|
||||
@@ -13,7 +13,7 @@ from openbb_core.provider.standard_models.forward_eps_estimates import (
|
||||
ForwardEpsEstimatesData,
|
||||
ForwardEpsEstimatesQueryParams,
|
||||
)
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from openbb_core.provider.utils.errors import EmptyDataError, UnauthorizedError
|
||||
from openbb_core.provider.utils.helpers import (
|
||||
amake_request,
|
||||
get_querystring,
|
||||
@@ -165,9 +165,15 @@ class IntrinioForwardEpsEstimatesFetcher(
|
||||
async def fetch_callback(response, session):
|
||||
"""Use callback for pagination."""
|
||||
data = await response.json()
|
||||
messages = data.get("messages", None)
|
||||
if messages:
|
||||
raise OpenBBError(str(messages))
|
||||
error = data.get("error", None)
|
||||
if error:
|
||||
message = data.get("message", "")
|
||||
if "api key" in message.lower():
|
||||
raise UnauthorizedError(
|
||||
f"Unauthorized Intrinio request -> {message}"
|
||||
)
|
||||
raise OpenBBError(f"Error: {error} -> {message}")
|
||||
|
||||
if data.get("estimates") and len(data.get("estimates")) > 0: # type: ignore
|
||||
results.extend(data.get("estimates")) # type: ignore
|
||||
while data.get("next_page"): # type: ignore
|
||||
|
||||
@@ -13,7 +13,7 @@ from openbb_core.provider.standard_models.forward_pe_estimates import (
|
||||
ForwardPeEstimatesData,
|
||||
ForwardPeEstimatesQueryParams,
|
||||
)
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from openbb_core.provider.utils.errors import EmptyDataError, UnauthorizedError
|
||||
from openbb_core.provider.utils.helpers import amake_request
|
||||
from openbb_intrinio.utils.helpers import response_callback
|
||||
from pydantic import Field
|
||||
@@ -108,7 +108,11 @@ class IntrinioForwardPeEstimatesFetcher(
|
||||
data = await response.json()
|
||||
error = data.get("error", None)
|
||||
if error:
|
||||
message = data.get("message", None)
|
||||
message = data.get("message", "")
|
||||
if "api key" in message.lower():
|
||||
raise UnauthorizedError(
|
||||
f"Unauthorized Intrinio request -> {message}"
|
||||
)
|
||||
raise OpenBBError(f"Error: {error} -> {message}")
|
||||
forward_pe = data.get("forward_pe")
|
||||
if forward_pe and len(forward_pe) > 0: # type: ignore
|
||||
|
||||
@@ -13,7 +13,7 @@ from openbb_core.provider.standard_models.forward_sales_estimates import (
|
||||
ForwardSalesEstimatesData,
|
||||
ForwardSalesEstimatesQueryParams,
|
||||
)
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from openbb_core.provider.utils.errors import EmptyDataError, UnauthorizedError
|
||||
from openbb_core.provider.utils.helpers import (
|
||||
amake_request,
|
||||
get_querystring,
|
||||
@@ -184,9 +184,14 @@ class IntrinioForwardSalesEstimatesFetcher(
|
||||
async def fetch_callback(response, session):
|
||||
"""Use callback for pagination."""
|
||||
data = await response.json()
|
||||
messages = data.get("messages")
|
||||
if messages:
|
||||
raise OpenBBError(str(messages))
|
||||
error = data.get("error", None)
|
||||
if error:
|
||||
message = data.get("message", "")
|
||||
if "api key" in message.lower():
|
||||
raise UnauthorizedError(
|
||||
f"Unauthorized Intrinio request -> {message}"
|
||||
)
|
||||
raise OpenBBError(f"Error: {error} -> {message}")
|
||||
if data.get("estimates") and len(data.get("estimates")) > 0: # type: ignore
|
||||
results.extend(data.get("estimates")) # type: ignore
|
||||
while data.get("next_page"): # type: ignore
|
||||
|
||||
@@ -5,14 +5,13 @@
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Dict, List, Literal, Optional, Union
|
||||
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.world_news import (
|
||||
WorldNewsData,
|
||||
WorldNewsQueryParams,
|
||||
)
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from openbb_core.provider.utils.helpers import amake_request, get_querystring
|
||||
from openbb_intrinio.utils.helpers import get_data
|
||||
from openbb_core.provider.utils.errors import EmptyDataError, UnauthorizedError
|
||||
from openbb_intrinio.utils.references import IntrinioCompany, IntrinioSecurity
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
@@ -185,6 +184,10 @@ class IntrinioWorldNewsFetcher(
|
||||
**kwargs: Any,
|
||||
) -> List[Dict]:
|
||||
"""Return the raw data from the Intrinio endpoint."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from openbb_core.provider.utils.helpers import amake_request, get_querystring
|
||||
from openbb_intrinio.utils.helpers import get_data
|
||||
|
||||
api_key = credentials.get("intrinio_api_key") if credentials else ""
|
||||
|
||||
base_url = "https://api-v2.intrinio.com/companies"
|
||||
@@ -200,6 +203,14 @@ class IntrinioWorldNewsFetcher(
|
||||
async def callback(response, session):
|
||||
"""Response callback."""
|
||||
result = await response.json()
|
||||
|
||||
if isinstance(result, dict) and "error" in result:
|
||||
if "api key" in result.get("message", "").lower():
|
||||
raise UnauthorizedError(
|
||||
f"Unauthorized Intrinio request -> {result.get('message')}"
|
||||
)
|
||||
raise OpenBBError(f"Error in Intrinio request -> {result}")
|
||||
|
||||
_data = result.get("news", [])
|
||||
data = []
|
||||
data.extend(
|
||||
|
||||
@@ -10,7 +10,7 @@ from io import StringIO
|
||||
from typing import Any, Dict, List, Optional, TypeVar, Union
|
||||
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from openbb_core.provider.utils.errors import EmptyDataError, UnauthorizedError
|
||||
from openbb_core.provider.utils.helpers import (
|
||||
ClientResponse,
|
||||
ClientSession,
|
||||
@@ -63,8 +63,10 @@ async def response_callback(
|
||||
"""Use callback for async_request."""
|
||||
data = await response.json()
|
||||
|
||||
if isinstance(data, dict) and response.status != 200:
|
||||
if isinstance(data, dict) and (response.status != 200 or data.get("error")):
|
||||
if message := data.get("error", None) or data.get("message", None):
|
||||
if "api key" in message.lower():
|
||||
raise UnauthorizedError(f"Unauthorized Intrinio request -> {message}")
|
||||
raise OpenBBError(f"Error in Intrinio request -> {message}")
|
||||
|
||||
if error := data.get("Error Message", None):
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
import math
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
@@ -12,7 +11,7 @@ from openbb_core.provider.standard_models.company_news import (
|
||||
CompanyNewsData,
|
||||
CompanyNewsQueryParams,
|
||||
)
|
||||
from openbb_core.provider.utils.helpers import amake_requests, get_querystring
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
|
||||
@@ -27,7 +26,10 @@ class TiingoCompanyNewsQueryParams(CompanyNewsQueryParams):
|
||||
"start_date": "startDate",
|
||||
"end_date": "endDate",
|
||||
}
|
||||
__json_schema_extra__ = {"symbol": {"multiple_items_allowed": True}}
|
||||
__json_schema_extra__ = {
|
||||
"symbol": {"multiple_items_allowed": True},
|
||||
"source": {"multiple_items_allowed": True},
|
||||
}
|
||||
|
||||
offset: Optional[int] = Field(
|
||||
default=0, description="Page offset, used in conjunction with limit."
|
||||
@@ -87,6 +89,12 @@ class TiingoCompanyNewsFetcher(
|
||||
**kwargs: Any,
|
||||
) -> List[Dict]:
|
||||
"""Return the raw data from the tiingo endpoint."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
import asyncio # noqa
|
||||
import math
|
||||
from openbb_core.provider.utils.helpers import get_querystring
|
||||
from openbb_tiingo.utils.helpers import get_data
|
||||
|
||||
api_key = credentials.get("tiingo_token") if credentials else ""
|
||||
|
||||
base_url = "https://api.tiingo.com/tiingo/news"
|
||||
@@ -106,7 +114,22 @@ class TiingoCompanyNewsFetcher(
|
||||
else:
|
||||
urls = [f"{base_url}?{query_str}&token={api_key}&limit={limit}"]
|
||||
|
||||
return await amake_requests(urls)
|
||||
results: list = []
|
||||
|
||||
async def get_one(url):
|
||||
"""Get data for one URL and append results to list."""
|
||||
response = await get_data(url)
|
||||
if isinstance(response, list):
|
||||
results.extend(response)
|
||||
elif isinstance(response, dict):
|
||||
results.append(response)
|
||||
|
||||
await asyncio.gather(*[get_one(url) for url in urls])
|
||||
|
||||
if not results:
|
||||
raise EmptyDataError()
|
||||
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
|
||||
@@ -3,71 +3,74 @@
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Literal, Optional
|
||||
from typing import Any, Literal, Optional, Union
|
||||
from warnings import warn
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.crypto_historical import (
|
||||
CryptoHistoricalData,
|
||||
CryptoHistoricalQueryParams,
|
||||
)
|
||||
from openbb_core.provider.utils.descriptions import QUERY_DESCRIPTIONS
|
||||
from openbb_core.provider.utils.helpers import (
|
||||
ClientResponse,
|
||||
amake_requests,
|
||||
get_querystring,
|
||||
)
|
||||
from pydantic import Field, PrivateAttr, model_validator
|
||||
from openbb_core.provider.utils.errors import EmptyDataError, UnauthorizedError
|
||||
from pydantic import Field
|
||||
|
||||
|
||||
class TiingoCryptoHistoricalQueryParams(CryptoHistoricalQueryParams):
|
||||
"""Tiingo Crypto Historical Price Query.
|
||||
|
||||
Source: https://www.tiingo.com/documentation/end-of-day
|
||||
Source: https://www.tiingo.com/documentation/crypto
|
||||
"""
|
||||
|
||||
__alias_dict__ = {
|
||||
"symbol": "tickers",
|
||||
"start_date": "startDate",
|
||||
"end_date": "endDate",
|
||||
"interval": "resampleFreq",
|
||||
"symbol": "tickers",
|
||||
}
|
||||
__json_schema_extra__ = {
|
||||
"symbol": {"multiple_items_allowed": True},
|
||||
"interval": {"choices": ["1m", "5m", "15m", "30m", "1h", "4h", "1d"]},
|
||||
"exchanges": {"multiple_items_allowed": True},
|
||||
"interval": {
|
||||
"choices": [
|
||||
"1m",
|
||||
"5m",
|
||||
"15m",
|
||||
"30m",
|
||||
"90m",
|
||||
"1h",
|
||||
"2h",
|
||||
"4h",
|
||||
"1d",
|
||||
"7d",
|
||||
"30d",
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
interval: Literal["1m", "5m", "15m", "30m", "1h", "4h", "1d"] = Field(
|
||||
default="1d", description=QUERY_DESCRIPTIONS.get("interval", "")
|
||||
)
|
||||
exchanges: Optional[List[str]] = Field(
|
||||
interval: Union[
|
||||
Literal[
|
||||
"1m",
|
||||
"5m",
|
||||
"15m",
|
||||
"30m",
|
||||
"90m",
|
||||
"1h",
|
||||
"2h",
|
||||
"4h",
|
||||
"1d",
|
||||
"7d",
|
||||
"30d",
|
||||
],
|
||||
str,
|
||||
] = Field(default="1d", description=QUERY_DESCRIPTIONS.get("interval", ""))
|
||||
|
||||
exchanges: Optional[Union[list[str], str]] = Field(
|
||||
default=None,
|
||||
description=(
|
||||
"To limit the query to a subset of exchanges e.g. ['POLONIEX', 'GDAX']"
|
||||
),
|
||||
)
|
||||
_frequency: Literal["daily", "weekly", "monthly", "annually"] = PrivateAttr(
|
||||
default=None
|
||||
)
|
||||
|
||||
# pylint: disable=protected-access
|
||||
@model_validator(mode="after") # type: ignore[arg-type]
|
||||
@classmethod
|
||||
def set_time_params(cls, values: "TiingoCryptoHistoricalQueryParams"):
|
||||
"""Set the default start & end date and time params for Tiingo API."""
|
||||
frequency_dict = {
|
||||
"1d": "1Day",
|
||||
"1m": "1Min",
|
||||
"5m": "5Min",
|
||||
"15m": "15Min",
|
||||
"30m": "30Min",
|
||||
"1h": "1Hour",
|
||||
"4h": "4Hour",
|
||||
}
|
||||
values._frequency = frequency_dict[values.interval] # type: ignore[assignment]
|
||||
|
||||
return values
|
||||
|
||||
|
||||
class TiingoCryptoHistoricalData(CryptoHistoricalData):
|
||||
@@ -96,14 +99,17 @@ class TiingoCryptoHistoricalData(CryptoHistoricalData):
|
||||
class TiingoCryptoHistoricalFetcher(
|
||||
Fetcher[
|
||||
TiingoCryptoHistoricalQueryParams,
|
||||
List[TiingoCryptoHistoricalData],
|
||||
list[TiingoCryptoHistoricalData],
|
||||
]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the Tiingo endpoints."""
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: Dict[str, Any]) -> TiingoCryptoHistoricalQueryParams:
|
||||
def transform_query(params: dict[str, Any]) -> TiingoCryptoHistoricalQueryParams:
|
||||
"""Transform the query params."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
transformed_params = params
|
||||
|
||||
now = datetime.now().date()
|
||||
@@ -115,51 +121,74 @@ class TiingoCryptoHistoricalFetcher(
|
||||
|
||||
return TiingoCryptoHistoricalQueryParams(**transformed_params)
|
||||
|
||||
# pylint: disable=protected-access
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: TiingoCryptoHistoricalQueryParams,
|
||||
credentials: Optional[Dict[str, str]],
|
||||
credentials: Optional[dict[str, str]],
|
||||
**kwargs: Optional[Any],
|
||||
) -> List[Dict]:
|
||||
) -> list[dict]:
|
||||
"""Return the raw data from the Tiingo endpoint."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from openbb_core.provider.utils.helpers import get_querystring
|
||||
from openbb_tiingo.utils.helpers import get_data
|
||||
|
||||
api_key = credentials.get("tiingo_token") if credentials else ""
|
||||
|
||||
base_url = "https://api.tiingo.com/tiingo/crypto/prices"
|
||||
query_str = get_querystring(
|
||||
query.model_dump(by_alias=False), ["tickers", "resampleFreq"]
|
||||
)
|
||||
results: List[dict] = []
|
||||
query_str = get_querystring(query.model_dump(by_alias=True), ["interval"])
|
||||
frequency = query.interval
|
||||
|
||||
async def callback(response: ClientResponse, _: Any) -> List[Dict]:
|
||||
result = await response.json()
|
||||
symbol = response.url.query.get("tickers", "")
|
||||
if not result:
|
||||
warn(f"No data found the the symbol: {symbol}")
|
||||
return results
|
||||
data = result[0].get("priceData")
|
||||
if "," in query.symbol:
|
||||
for d in data:
|
||||
d["symbol"] = symbol.upper() if symbol else None
|
||||
if len(data) > 0:
|
||||
results.extend(data)
|
||||
return results
|
||||
if frequency.endswith("m"):
|
||||
frequency = f"{frequency[:-1]}min"
|
||||
elif frequency == "h":
|
||||
frequency = f"{frequency[:-1]}hour"
|
||||
elif frequency.endswith("d"):
|
||||
frequency = f"{frequency[:-1]}day"
|
||||
|
||||
urls = [
|
||||
f"{base_url}?tickers={symbol}&{query_str}&resampleFreq={query._frequency}&token={api_key}"
|
||||
for symbol in query.symbol.split(",")
|
||||
]
|
||||
results: list = []
|
||||
url = f"{base_url}?{query_str}&resampleFreq={frequency}&token={api_key}"
|
||||
|
||||
return await amake_requests(urls, callback, **kwargs)
|
||||
try:
|
||||
results = await get_data(url)
|
||||
except (EmptyDataError, OpenBBError, UnauthorizedError) as e:
|
||||
raise e from e
|
||||
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: TiingoCryptoHistoricalQueryParams,
|
||||
data: List[Dict],
|
||||
data: list[dict],
|
||||
**kwargs: Any,
|
||||
) -> List[TiingoCryptoHistoricalData]:
|
||||
) -> list[TiingoCryptoHistoricalData]:
|
||||
"""Return the transformed data."""
|
||||
return [
|
||||
TiingoCryptoHistoricalData.model_validate(d)
|
||||
for d in sorted(data, key=lambda x: x["date"], reverse=True)
|
||||
]
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from pandas import to_datetime
|
||||
|
||||
results: list[TiingoCryptoHistoricalData] = []
|
||||
symbols = query.symbol.split(",")
|
||||
returned_symbols = [item.get("ticker", "").upper() for item in data]
|
||||
|
||||
for symbol in symbols:
|
||||
if symbol not in returned_symbols:
|
||||
warn(f"No data found for {symbol}")
|
||||
|
||||
for item in data:
|
||||
symbol = item.get("ticker", "").upper()
|
||||
price_data = item.get("priceData", [])
|
||||
|
||||
if not price_data:
|
||||
warn(f"No data found for {symbol}")
|
||||
continue
|
||||
|
||||
for row in price_data:
|
||||
if len(returned_symbols) > 1:
|
||||
row["symbol"] = symbol
|
||||
if query.interval.endswith("d"):
|
||||
row["date"] = to_datetime(row["date"]).date()
|
||||
else:
|
||||
row["date"] = to_datetime(row["date"], utc=True)
|
||||
|
||||
results.append(TiingoCryptoHistoricalData.model_validate(row))
|
||||
|
||||
return sorted(results, key=lambda x: x.date)
|
||||
|
||||
@@ -3,65 +3,65 @@
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Literal, Optional
|
||||
from typing import Any, Literal, Optional, Union
|
||||
from warnings import warn
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.currency_historical import (
|
||||
CurrencyHistoricalData,
|
||||
CurrencyHistoricalQueryParams,
|
||||
)
|
||||
from openbb_core.provider.utils.descriptions import QUERY_DESCRIPTIONS
|
||||
from openbb_core.provider.utils.helpers import (
|
||||
ClientResponse,
|
||||
amake_requests,
|
||||
get_querystring,
|
||||
)
|
||||
from pydantic import Field, PrivateAttr, model_validator
|
||||
from openbb_core.provider.utils.errors import EmptyDataError, UnauthorizedError
|
||||
from pydantic import Field
|
||||
|
||||
|
||||
class TiingoCurrencyHistoricalQueryParams(CurrencyHistoricalQueryParams):
|
||||
"""Tiingo Currency Historical Price Query.
|
||||
|
||||
Source: https://www.tiingo.com/documentation/end-of-day
|
||||
Source: https://www.tiingo.com/documentation/forex
|
||||
"""
|
||||
|
||||
__alias_dict__ = {
|
||||
"symbol": "tickers",
|
||||
"start_date": "startDate",
|
||||
"end_date": "endDate",
|
||||
"interval": "resampleFreq",
|
||||
}
|
||||
__json_schema_extra__ = {
|
||||
"symbol": {"multiple_items_allowed": True},
|
||||
"interval": {"choices": ["1m", "5m", "15m", "30m", "1h", "4h", "1d"]},
|
||||
"interval": {
|
||||
"choices": [
|
||||
"1m",
|
||||
"5m",
|
||||
"15m",
|
||||
"30m",
|
||||
"90m",
|
||||
"1h",
|
||||
"2h",
|
||||
"4h",
|
||||
"1d",
|
||||
"5d",
|
||||
"21d",
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
interval: Literal["1m", "5m", "15m", "30m", "1h", "4h", "1d"] = Field(
|
||||
default="1d", description=QUERY_DESCRIPTIONS.get("interval", "")
|
||||
)
|
||||
_frequency: Literal["daily", "weekly", "monthly", "annually"] = PrivateAttr(
|
||||
default=None
|
||||
)
|
||||
|
||||
# pylint: disable=protected-access
|
||||
@model_validator(mode="after") # type: ignore[arg-type]
|
||||
@classmethod
|
||||
def set_time_params(cls, values: "TiingoCurrencyHistoricalQueryParams"):
|
||||
"""Set the default start & end date and time params for Tiingo API."""
|
||||
frequency_dict = {
|
||||
"1d": "1Day",
|
||||
"1m": "1Min",
|
||||
"5m": "5Min",
|
||||
"15m": "15Min",
|
||||
"30m": "30Min",
|
||||
"1h": "1Hour",
|
||||
"4h": "4Hour",
|
||||
}
|
||||
values._frequency = frequency_dict[values.interval] # type: ignore[assignment]
|
||||
|
||||
return values
|
||||
interval: Union[
|
||||
Literal[
|
||||
"1m",
|
||||
"5m",
|
||||
"15m",
|
||||
"30m",
|
||||
"90m",
|
||||
"1h",
|
||||
"2h",
|
||||
"4h",
|
||||
"1d",
|
||||
"5d",
|
||||
"21d",
|
||||
],
|
||||
str,
|
||||
] = Field(default="1d", description=QUERY_DESCRIPTIONS.get("interval", ""))
|
||||
|
||||
|
||||
class TiingoCurrencyHistoricalData(CurrencyHistoricalData):
|
||||
@@ -73,14 +73,17 @@ class TiingoCurrencyHistoricalData(CurrencyHistoricalData):
|
||||
class TiingoCurrencyHistoricalFetcher(
|
||||
Fetcher[
|
||||
TiingoCurrencyHistoricalQueryParams,
|
||||
List[TiingoCurrencyHistoricalData],
|
||||
list[TiingoCurrencyHistoricalData],
|
||||
]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the Tiingo endpoints."""
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: Dict[str, Any]) -> TiingoCurrencyHistoricalQueryParams:
|
||||
def transform_query(params: dict[str, Any]) -> TiingoCurrencyHistoricalQueryParams:
|
||||
"""Transform the query params."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
transformed_params = params
|
||||
|
||||
now = datetime.now().date()
|
||||
@@ -92,51 +95,92 @@ class TiingoCurrencyHistoricalFetcher(
|
||||
|
||||
return TiingoCurrencyHistoricalQueryParams(**transformed_params)
|
||||
|
||||
# pylint: disable=protected-access,unused-argument
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: TiingoCurrencyHistoricalQueryParams,
|
||||
credentials: Optional[Dict[str, str]],
|
||||
credentials: Optional[dict[str, str]],
|
||||
**kwargs: Any,
|
||||
) -> List[Dict]:
|
||||
) -> list[dict]:
|
||||
"""Return the raw data from the Tiingo endpoint."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
import asyncio # noqa
|
||||
from openbb_core.provider.utils.helpers import get_querystring
|
||||
from openbb_tiingo.utils.helpers import get_data
|
||||
from pandas import to_datetime
|
||||
|
||||
api_key = credentials.get("tiingo_token") if credentials else ""
|
||||
|
||||
base_url = "https://api.tiingo.com/tiingo/fx/prices"
|
||||
base_url = "https://api.tiingo.com/tiingo/fx"
|
||||
query_str = get_querystring(
|
||||
query.model_dump(by_alias=False), ["tickers", "resampleFreq"]
|
||||
)
|
||||
results: List[dict] = []
|
||||
|
||||
async def callback(response: ClientResponse, _: Any) -> List[Dict]:
|
||||
result = await response.json()
|
||||
symbol = response.url.query.get("tickers", "")
|
||||
if not result:
|
||||
warn(f"No data found the the symbol: {symbol}")
|
||||
return results
|
||||
data = result
|
||||
if len(data) > 0:
|
||||
if "," not in query.symbol:
|
||||
_ = [d.pop("ticker") for d in data]
|
||||
query_str = get_querystring(
|
||||
query.model_dump(by_alias=True), ["symbol", "interval"]
|
||||
)
|
||||
frequency = query.interval
|
||||
|
||||
if frequency.endswith("m"):
|
||||
frequency = f"{frequency[:-1]}min"
|
||||
elif frequency == "h":
|
||||
frequency = f"{frequency[:-1]}hour"
|
||||
elif frequency.endswith("d"):
|
||||
frequency = f"{frequency[:-1]}day"
|
||||
|
||||
results: list = []
|
||||
messages: list = []
|
||||
|
||||
async def get_one(symbol):
|
||||
"""Get data for one symbol."""
|
||||
url = f"{base_url}/{symbol}/prices?{query_str}&resampleFreq={frequency}&token={api_key}"
|
||||
data = None
|
||||
try:
|
||||
data = await get_data(url)
|
||||
except UnauthorizedError as e:
|
||||
raise e from e
|
||||
except OpenBBError as e:
|
||||
if (
|
||||
e.original
|
||||
and isinstance(e.original, str)
|
||||
and "ticker" in e.original.lower()
|
||||
):
|
||||
messages.append(e.original)
|
||||
else:
|
||||
for d in data:
|
||||
d["ticker"] = d["ticker"].upper()
|
||||
messages.append(f"{symbol}: {e.original}")
|
||||
|
||||
if isinstance(data, list):
|
||||
for d in data:
|
||||
if "," in query.symbol:
|
||||
d["symbol"] = symbol
|
||||
if query.interval.endswith("d"):
|
||||
d["date"] = to_datetime(d["date"]).date()
|
||||
else:
|
||||
d["date"] = to_datetime(d["date"], utc=True)
|
||||
|
||||
results.extend(data)
|
||||
return results
|
||||
|
||||
urls = [
|
||||
f"{base_url}?tickers={symbol}&{query_str}&resampleFreq={query._frequency}&token={api_key}"
|
||||
for symbol in query.symbol.split(",")
|
||||
]
|
||||
symbols = query.symbol.split(",")
|
||||
await asyncio.gather(*[get_one(symbol) for symbol in symbols])
|
||||
|
||||
return await amake_requests(urls, callback, **kwargs)
|
||||
if not results and messages:
|
||||
raise OpenBBError(f"{messages}")
|
||||
|
||||
if not results and not messages:
|
||||
raise EmptyDataError("The request was returned empty.")
|
||||
|
||||
if results and messages:
|
||||
for message in messages:
|
||||
warn(message)
|
||||
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: TiingoCurrencyHistoricalQueryParams,
|
||||
data: List[Dict],
|
||||
data: list[dict],
|
||||
**kwargs: Any,
|
||||
) -> List[TiingoCurrencyHistoricalData]:
|
||||
) -> list[TiingoCurrencyHistoricalData]:
|
||||
"""Return the transformed data."""
|
||||
return [TiingoCurrencyHistoricalData.model_validate(d) for d in data]
|
||||
return sorted(
|
||||
[TiingoCurrencyHistoricalData.model_validate(d) for d in data],
|
||||
key=lambda x: x.date,
|
||||
)
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Literal, Optional
|
||||
from typing import Any, Literal, Optional, Union
|
||||
from warnings import warn
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.equity_historical import (
|
||||
EquityHistoricalData,
|
||||
@@ -16,12 +16,8 @@ from openbb_core.provider.utils.descriptions import (
|
||||
DATA_DESCRIPTIONS,
|
||||
QUERY_DESCRIPTIONS,
|
||||
)
|
||||
from openbb_core.provider.utils.helpers import (
|
||||
ClientResponse,
|
||||
amake_requests,
|
||||
get_querystring,
|
||||
)
|
||||
from pydantic import Field, PrivateAttr, model_validator
|
||||
from openbb_core.provider.utils.errors import EmptyDataError, UnauthorizedError
|
||||
from pydantic import Field
|
||||
|
||||
|
||||
class TiingoEquityHistoricalQueryParams(EquityHistoricalQueryParams):
|
||||
@@ -36,31 +32,30 @@ class TiingoEquityHistoricalQueryParams(EquityHistoricalQueryParams):
|
||||
}
|
||||
__json_schema_extra__ = {
|
||||
"symbol": {"multiple_items_allowed": True},
|
||||
"interval": {"choices": ["1m", "5m", "15m", "30m", "1h", "4h", "1d"]},
|
||||
"interval": {
|
||||
"choices": [
|
||||
"1m",
|
||||
"5m",
|
||||
"15m",
|
||||
"30m",
|
||||
"90m",
|
||||
"1h",
|
||||
"2h",
|
||||
"4h",
|
||||
"1d",
|
||||
"1W",
|
||||
"1M",
|
||||
"1Y",
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
interval: Literal["1d", "1W", "1M", "1Y"] = Field(
|
||||
default="1d", description=QUERY_DESCRIPTIONS.get("interval", "")
|
||||
)
|
||||
_frequency: Literal["daily", "weekly", "monthly", "annually"] = PrivateAttr(
|
||||
default=None
|
||||
)
|
||||
|
||||
# pylint: disable=protected-access
|
||||
@model_validator(mode="after") # type: ignore[arg-type]
|
||||
@classmethod
|
||||
def set_time_params(cls, values: "TiingoEquityHistoricalQueryParams"):
|
||||
"""Set the default start & end date and time params for Tiingo API."""
|
||||
frequency_dict = {
|
||||
"1d": "daily",
|
||||
"1W": "weekly",
|
||||
"1M": "monthly",
|
||||
"1Y": "annually",
|
||||
}
|
||||
|
||||
values._frequency = frequency_dict[values.interval] # type: ignore[assignment]
|
||||
|
||||
return values
|
||||
interval: Union[
|
||||
Literal[
|
||||
"1m", "5m", "15m", "30m", "90m", "1h", "2h", "4h", "1d", "1W", "1M", "1Y"
|
||||
],
|
||||
str,
|
||||
] = Field(default="1d", description=QUERY_DESCRIPTIONS.get("interval", ""))
|
||||
|
||||
|
||||
class TiingoEquityHistoricalData(EquityHistoricalData):
|
||||
@@ -109,14 +104,17 @@ class TiingoEquityHistoricalData(EquityHistoricalData):
|
||||
class TiingoEquityHistoricalFetcher(
|
||||
Fetcher[
|
||||
TiingoEquityHistoricalQueryParams,
|
||||
List[TiingoEquityHistoricalData],
|
||||
list[TiingoEquityHistoricalData],
|
||||
]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the Tiingo endpoints."""
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: Dict[str, Any]) -> TiingoEquityHistoricalQueryParams:
|
||||
def transform_query(params: dict[str, Any]) -> TiingoEquityHistoricalQueryParams:
|
||||
"""Transform the query params."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
transformed_params = params
|
||||
|
||||
now = datetime.now().date()
|
||||
@@ -132,45 +130,108 @@ class TiingoEquityHistoricalFetcher(
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: TiingoEquityHistoricalQueryParams,
|
||||
credentials: Optional[Dict[str, str]],
|
||||
credentials: Optional[dict[str, str]],
|
||||
**kwargs: Any,
|
||||
) -> List[Dict]:
|
||||
) -> list[dict]:
|
||||
"""Return the raw data from the Tiingo endpoint."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
import asyncio # noqa
|
||||
from openbb_core.provider.utils.helpers import get_querystring
|
||||
from openbb_tiingo.utils.helpers import get_data
|
||||
|
||||
api_key = credentials.get("tiingo_token") if credentials else ""
|
||||
|
||||
base_url = "https://api.tiingo.com/tiingo/daily"
|
||||
base_url = (
|
||||
"https://api.tiingo.com/tiingo/daily"
|
||||
if query.interval in ["1d", "1W", "1M", "1Y"]
|
||||
else "https://api.tiingo.com/iex"
|
||||
)
|
||||
query_str = get_querystring(
|
||||
query.model_dump(by_alias=True), ["symbol", "interval"]
|
||||
)
|
||||
frequency_dict = {
|
||||
"1d": "daily",
|
||||
"1W": "weekly",
|
||||
"1M": "monthly",
|
||||
"1Y": "annually",
|
||||
}
|
||||
frequency = (
|
||||
frequency_dict.get(query.interval, "")
|
||||
if query.interval in frequency_dict
|
||||
else query.interval
|
||||
)
|
||||
cols_str = "&columns=open,high,low,close,volume"
|
||||
|
||||
async def callback(response: ClientResponse, _: Any) -> List[Dict]:
|
||||
data = await response.json()
|
||||
symbol = response.url.parts[-2]
|
||||
results: List[dict] = []
|
||||
if not data:
|
||||
warn(f"No data found the the symbol: {symbol}")
|
||||
return results
|
||||
if frequency.endswith("m"):
|
||||
frequency = f"{frequency[:-1]}min"
|
||||
query_str = query_str + cols_str
|
||||
elif frequency == "h":
|
||||
frequency = f"{frequency[:-1]}hour"
|
||||
query_str = query_str + cols_str
|
||||
|
||||
if isinstance(data, List):
|
||||
results = data
|
||||
results: list = []
|
||||
messages: list = []
|
||||
|
||||
if "," in query.symbol:
|
||||
for d in results:
|
||||
d["symbol"] = symbol
|
||||
return results
|
||||
async def get_one(symbol):
|
||||
"""Get data for one symbol."""
|
||||
url = f"{base_url}/{symbol}/prices?{query_str}&resampleFreq={frequency}&token={api_key}"
|
||||
data = None
|
||||
try:
|
||||
data = await get_data(url)
|
||||
except UnauthorizedError as e:
|
||||
raise e from e
|
||||
except OpenBBError as e:
|
||||
if (
|
||||
e.original
|
||||
and isinstance(e.original, str)
|
||||
and "ticker" in e.original.lower()
|
||||
):
|
||||
messages.append(e.original)
|
||||
else:
|
||||
messages.append(f"{symbol}: {e.original}")
|
||||
|
||||
urls = [
|
||||
f"{base_url}/{symbol}/prices?{query_str}&resampleFreq={query._frequency}&token={api_key}"
|
||||
for symbol in query.symbol.split(",")
|
||||
]
|
||||
if isinstance(data, list):
|
||||
if "," in query.symbol:
|
||||
for d in data:
|
||||
d["symbol"] = symbol
|
||||
results.extend(data)
|
||||
|
||||
return await amake_requests(urls, callback, **kwargs)
|
||||
symbols = query.symbol.split(",")
|
||||
await asyncio.gather(*[get_one(symbol) for symbol in symbols])
|
||||
|
||||
if not results and messages:
|
||||
raise OpenBBError(f"{messages}")
|
||||
|
||||
if not results and not messages:
|
||||
raise EmptyDataError("The request was returned empty.")
|
||||
|
||||
if results and messages:
|
||||
for message in messages:
|
||||
warn(message)
|
||||
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: TiingoEquityHistoricalQueryParams,
|
||||
data: List[Dict],
|
||||
data: list[dict],
|
||||
**kwargs: Any,
|
||||
) -> List[TiingoEquityHistoricalData]:
|
||||
) -> list[TiingoEquityHistoricalData]:
|
||||
"""Return the transformed data."""
|
||||
return [TiingoEquityHistoricalData.model_validate(d) for d in data]
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from pandas import to_datetime
|
||||
from pytz import timezone
|
||||
|
||||
results: list[TiingoEquityHistoricalData] = []
|
||||
for item in data:
|
||||
# If the data is intraday, it comes from IEX and the TZ should be localized to America/New_York
|
||||
if query.interval in ["1d", "1W", "1M", "1Y"]:
|
||||
item["date"] = to_datetime(item["date"]).date()
|
||||
else:
|
||||
item["date"] = to_datetime(item["date"], utc=True).tz_convert(
|
||||
timezone("America/New_York")
|
||||
)
|
||||
|
||||
results.append(TiingoEquityHistoricalData.model_validate(item))
|
||||
|
||||
return results
|
||||
|
||||
@@ -39,23 +39,22 @@ class TiingoTrailingDivYieldFetcher(
|
||||
return TiingoTrailingDivYieldQueryParams(**transformed_params)
|
||||
|
||||
@staticmethod
|
||||
def extract_data(
|
||||
async def aextract_data(
|
||||
query: TiingoTrailingDivYieldQueryParams,
|
||||
credentials: Optional[Dict[str, str]],
|
||||
**kwargs: Any,
|
||||
) -> List[Dict]:
|
||||
"""Return the raw data from the Tiingo endpoint."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from openbb_core.provider.utils.helpers import make_request
|
||||
from openbb_tiingo.utils.helpers import get_data
|
||||
|
||||
api_key = credentials.get("tiingo_token") if credentials else ""
|
||||
url = (
|
||||
f"https://api.tiingo.com/tiingo/corporate-actions/{query.symbol}/distribution-yield?"
|
||||
f"token={api_key}"
|
||||
)
|
||||
data = make_request(url)
|
||||
|
||||
return data.json()
|
||||
return await get_data(url) # type: ignore
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
import math
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
@@ -12,7 +11,7 @@ from openbb_core.provider.standard_models.world_news import (
|
||||
WorldNewsData,
|
||||
WorldNewsQueryParams,
|
||||
)
|
||||
from openbb_core.provider.utils.helpers import amake_requests, get_querystring
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
|
||||
@@ -26,6 +25,8 @@ class TiingoWorldNewsQueryParams(WorldNewsQueryParams):
|
||||
"start_date": "startDate",
|
||||
"end_date": "endDate",
|
||||
}
|
||||
__json_schema_extra__ = {"source": {"multiple_items_allowed": True}}
|
||||
|
||||
offset: Optional[int] = Field(
|
||||
default=0, description="Page offset, used in conjunction with limit."
|
||||
)
|
||||
@@ -90,6 +91,12 @@ class TiingoWorldNewsFetcher(
|
||||
**kwargs: Any,
|
||||
) -> List[Dict]:
|
||||
"""Return the raw data from the tiingo endpoint."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
import asyncio # noqa
|
||||
import math
|
||||
from openbb_core.provider.utils.helpers import get_querystring
|
||||
from openbb_tiingo.utils.helpers import get_data
|
||||
|
||||
api_key = credentials.get("tiingo_token") if credentials else ""
|
||||
|
||||
base_url = "https://api.tiingo.com/tiingo/news"
|
||||
@@ -110,7 +117,22 @@ class TiingoWorldNewsFetcher(
|
||||
else:
|
||||
urls = [f"{base_url}?{query_str}&token={api_key}&limit={limit}"]
|
||||
|
||||
return await amake_requests(urls)
|
||||
results: list = []
|
||||
|
||||
async def get_one(url):
|
||||
"""Get data for one URL and append results to list."""
|
||||
response = await get_data(url)
|
||||
if isinstance(response, list):
|
||||
results.extend(response)
|
||||
elif isinstance(response, dict):
|
||||
results.append(response)
|
||||
|
||||
await asyncio.gather(*[get_one(url) for url in urls])
|
||||
|
||||
if not results:
|
||||
raise EmptyDataError()
|
||||
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
|
||||
@@ -1,109 +1,41 @@
|
||||
"""Tiingo Helpers Module."""
|
||||
|
||||
import json
|
||||
from io import StringIO
|
||||
from typing import Any, List, Optional, TypeVar, Union
|
||||
|
||||
import requests
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
from openbb_core.provider import helpers
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from pydantic import BaseModel
|
||||
from requests.exceptions import SSLError
|
||||
|
||||
T = TypeVar("T", bound=BaseModel)
|
||||
from typing import Optional, Union
|
||||
|
||||
|
||||
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
|
||||
"""
|
||||
async def get_data(url: str) -> Union[dict, list]:
|
||||
"""Get data from Tiingo endpoint and parse the JSON response."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from pyodide.http import open_url # type: ignore
|
||||
from json import JSONDecodeError # noqa
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
from openbb_core.provider.utils.errors import EmptyDataError, UnauthorizedError
|
||||
from openbb_core.provider.utils.helpers import amake_request
|
||||
|
||||
response = open_url(url)
|
||||
return BasicResponse(response)
|
||||
response: Optional[Union[dict, list]] = None
|
||||
|
||||
|
||||
def get_data(url: str, **kwargs: Any) -> Union[list, dict]:
|
||||
"""Get data from Tiingo endpoint."""
|
||||
try:
|
||||
r: Union[requests.Response, BasicResponse] = helpers.make_request(url, **kwargs)
|
||||
except SSLError:
|
||||
r = request(url)
|
||||
if r.status_code == 404:
|
||||
raise OpenBBError("Tiingo endpoint doesn't exist.")
|
||||
response = await amake_request(url)
|
||||
if (
|
||||
response
|
||||
and isinstance(response, dict)
|
||||
and len(list(response.keys())) == 1
|
||||
and response.get("detail")
|
||||
):
|
||||
if (
|
||||
"token" in response["detail"]
|
||||
or "access" in response["detail"]
|
||||
or "permission" in response["detail"]
|
||||
or "authorized" in response["detail"]
|
||||
):
|
||||
raise UnauthorizedError(
|
||||
f"Unauthorized Tiingo request -> {response['detail']}"
|
||||
)
|
||||
raise OpenBBError(response["detail"])
|
||||
|
||||
data = r.json()
|
||||
if r.status_code != 200:
|
||||
error = data.get("detail")
|
||||
raise OpenBBError(f"Error in Tiingo request -> {error}")
|
||||
if not response:
|
||||
raise EmptyDataError("The response is empty")
|
||||
|
||||
if len(data) == 0:
|
||||
raise EmptyDataError()
|
||||
return response
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_data_many(
|
||||
url: str, sub_dict: Optional[str] = None, **kwargs: Any
|
||||
) -> List[dict]:
|
||||
"""Get data from Tiingo 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 = 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")
|
||||
return data
|
||||
|
||||
|
||||
def get_data_one(url: str, **kwargs: Any) -> dict:
|
||||
"""Get data from Tiingo endpoint and convert to schema."""
|
||||
data = 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
|
||||
except JSONDecodeError as e:
|
||||
raise OpenBBError(f"Failed to parse JSON response -> {e}") from e
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -6,7 +6,7 @@ from datetime import (
|
||||
date as dateType,
|
||||
datetime,
|
||||
)
|
||||
from typing import Any, Dict, List, Literal, Optional, Union
|
||||
from typing import Any, Literal, Optional, Union
|
||||
from warnings import warn
|
||||
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
@@ -104,7 +104,7 @@ class TEEconomicCalendarQueryParams(EconomicCalendarQueryParams):
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from openbb_core.provider.utils.helpers import check_item
|
||||
|
||||
result: List = []
|
||||
result: list = []
|
||||
values = c.replace(" ", "_").split(",")
|
||||
for v in values:
|
||||
check_item(v.lower(), COUNTRIES)
|
||||
@@ -200,7 +200,7 @@ class TEEconomicCalendarData(EconomicCalendarData):
|
||||
|
||||
@field_validator("reference_date", mode="before", check_fields=False)
|
||||
@classmethod
|
||||
def validate_date(cls, v: str) -> dateType:
|
||||
def validate_date(cls, v):
|
||||
"""Validate the date."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from pandas import to_datetime
|
||||
@@ -224,51 +224,47 @@ class TEEconomicCalendarData(EconomicCalendarData):
|
||||
class TEEconomicCalendarFetcher(
|
||||
Fetcher[
|
||||
TEEconomicCalendarQueryParams,
|
||||
List[TEEconomicCalendarData],
|
||||
list[TEEconomicCalendarData],
|
||||
]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the Trading Economics endpoints."""
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: Dict[str, Any]) -> TEEconomicCalendarQueryParams:
|
||||
def transform_query(params: dict[str, Any]) -> TEEconomicCalendarQueryParams:
|
||||
"""Transform the query params."""
|
||||
return TEEconomicCalendarQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: TEEconomicCalendarQueryParams,
|
||||
credentials: Optional[Dict[str, str]],
|
||||
credentials: Optional[dict[str, str]],
|
||||
**kwargs: Any,
|
||||
) -> Union[dict, List[dict]]:
|
||||
) -> Union[dict, list[dict]]:
|
||||
"""Return the raw data from the TE endpoint."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from openbb_core.provider.utils.helpers import ClientResponse, amake_request
|
||||
from openbb_core.provider.utils.helpers import amake_request
|
||||
from openbb_tradingeconomics.utils import url_generator
|
||||
from openbb_tradingeconomics.utils.helpers import response_callback
|
||||
|
||||
api_key = credentials.get("tradingeconomics_api_key") if credentials else ""
|
||||
|
||||
if query.group is not None:
|
||||
query.group = query.group.replace("_", " ") # type: ignore
|
||||
|
||||
url = url_generator.generate_url(query)
|
||||
|
||||
if not url:
|
||||
raise OpenBBError(
|
||||
"No url generated. Check combination of input parameters."
|
||||
)
|
||||
|
||||
url = f"{url}{api_key}"
|
||||
|
||||
async def callback(response: ClientResponse, _: Any) -> Union[dict, List[dict]]:
|
||||
"""Return the response."""
|
||||
if response.status != 200:
|
||||
raise OpenBBError(
|
||||
f"Error in TE request: \n{await response.text()}"
|
||||
f"\nInfo -> TE API tend to fail if the number of countries is above {TE_COUNTRY_LIMIT}."
|
||||
)
|
||||
return await response.json()
|
||||
|
||||
return await amake_request(url, response_callback=callback, **kwargs)
|
||||
return await amake_request(url, response_callback=response_callback, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: TEEconomicCalendarQueryParams, data: List[Dict], **kwargs: Any
|
||||
) -> List[TEEconomicCalendarData]:
|
||||
query: TEEconomicCalendarQueryParams, data: list[dict], **kwargs: Any
|
||||
) -> list[TEEconomicCalendarData]:
|
||||
"""Return the transformed data."""
|
||||
return [TEEconomicCalendarData.model_validate(d) for d in data]
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
"""TradingEconomics Helpers."""
|
||||
|
||||
from typing import Union
|
||||
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
from openbb_core.provider.utils.errors import EmptyDataError, UnauthorizedError
|
||||
|
||||
|
||||
async def response_callback(response, _) -> Union[dict, list[dict]]:
|
||||
"""Return the response."""
|
||||
if response.status != 200:
|
||||
message = await response.text()
|
||||
|
||||
if "credentials" in message or "unauthorized" in message.lower():
|
||||
raise UnauthorizedError(
|
||||
f"Unauthorized TradingEconomics request -> {message}"
|
||||
)
|
||||
|
||||
raise OpenBBError(f"{response.status} -> {message}")
|
||||
|
||||
results = await response.json()
|
||||
|
||||
if not results:
|
||||
raise EmptyDataError("The request was returned empty.")
|
||||
|
||||
return results
|
||||
Reference in New Issue
Block a user