Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BugFix] Add Exception Handling For Unauthorized API Key Error. #6800

Merged
merged 22 commits into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
655896d
add exception handling for unauthorized api call request.
deeleeramone Oct 18, 2024
c7b58b4
Merge branch 'develop' into bugfix/unauthorized-error
deeleeramone Oct 18, 2024
aecdc21
linting
deeleeramone Oct 18, 2024
dc67312
Merge branch 'bugfix/unauthorized-error' of https://github.com/OpenBB…
deeleeramone Oct 18, 2024
b2f93fb
more linting
deeleeramone Oct 18, 2024
a5da4b0
more linting
deeleeramone Oct 18, 2024
36182c7
missed files
deeleeramone Oct 18, 2024
6bd744b
biztoc cassette
deeleeramone Oct 18, 2024
dda5faa
more linting
deeleeramone Oct 18, 2024
ece1044
crypto interval choices as 7d and 30d instead of 5 and 21
deeleeramone Oct 18, 2024
798f8cb
Merge branch 'develop' into bugfix/unauthorized-error
deeleeramone Oct 18, 2024
3995ca0
Merge branch 'develop' into bugfix/unauthorized-error
deeleeramone Oct 18, 2024
34c9d79
Merge branch 'develop' into bugfix/unauthorized-error
piiq Oct 19, 2024
8d7a476
Merge branch 'develop' into bugfix/unauthorized-error
deeleeramone Oct 21, 2024
3c7b9c3
Merge branch 'develop' into bugfix/unauthorized-error
deeleeramone Oct 21, 2024
5881012
Merge branch 'develop' into bugfix/unauthorized-error
deeleeramone Oct 21, 2024
b3f56c2
Merge branch 'develop' into bugfix/unauthorized-error
deeleeramone Oct 22, 2024
1b2f88e
Merge branch 'develop' into bugfix/unauthorized-error
deeleeramone Oct 22, 2024
26db502
update the default message and a few Intrinio endpoints to use the un…
deeleeramone Oct 22, 2024
c108c83
mypy
deeleeramone Oct 22, 2024
19dc4df
no-else-raise
deeleeramone Oct 22, 2024
d350b17
Merge branch 'develop' into bugfix/unauthorized-error
piiq Oct 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion openbb_platform/core/openbb_core/api/app_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
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


Expand Down Expand Up @@ -40,3 +40,4 @@ def add_exception_handlers(app: FastAPI):
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
13 changes: 11 additions & 2 deletions openbb_platform/core/openbb_core/api/exception_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
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")
Expand Down Expand Up @@ -55,7 +55,7 @@ async def exception(_: Request, error: Exception) -> JSONResponse:
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
Expand Down Expand Up @@ -99,3 +99,12 @@ async def openbb(_: Request, error: OpenBBError):
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),
)
4 changes: 4 additions & 0 deletions openbb_platform/core/openbb_core/app/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,10 @@ def command(
"model": OpenBBErrorResponse,
"description": "Internal Error",
},
502: {
"model": OpenBBErrorResponse,
"description": "Unauthorized",
},
},
)

Expand Down
18 changes: 15 additions & 3 deletions openbb_platform/core/openbb_core/app/static/utils/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

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

Expand Down Expand Up @@ -85,10 +86,21 @@ def wrapper(*f_args, **f_kwargs):
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
12 changes: 12 additions & 0 deletions openbb_platform/core/openbb_core/provider/utils/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,15 @@ def __init__(
"""Initialize the exception."""
self.message = message
super().__init__(self.message)


class UnauthorizedError(OpenBBError):
"""Exception raised for an unauthorized request response."""

def __init__(
self,
message: str = "Unauthorized request. Please check your credentials and subscription access from the provider.",
deeleeramone marked this conversation as resolved.
Show resolved Hide resolved
):
"""Initialize the exception."""
self.message = message
super().__init__(self.message)
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,13 @@
)
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


Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -414,15 +413,31 @@ async def aextract_data(
**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 (isinstance(data, list) and not data) or (
isinstance(data, dict) and not data.get("analyst_ratings_analyst")
):
raise EmptyDataError("No ratings data returned.")

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 response:
raise EmptyDataError()
if not isinstance(data, dict):
raise OpenBBError(
f"Unexpected data format. Expected dict, got: {type(data).__name__}"
)

return response.get("analyst_ratings_analyst") # type: ignore
return data["analyst_ratings_analyst"]

@staticmethod
def transform_data(
Expand Down
Original file line number Diff line number Diff line change
@@ -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


Expand Down Expand Up @@ -148,6 +150,12 @@ async def aextract_data(
**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"
Expand All @@ -165,11 +173,28 @@ async def aextract_data(
for page in range(pages)
]

data = await amake_requests(urls, **kwargs)
results: list = []

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 data[: query.limit]
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,14 @@
)
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,
PriceTargetQueryParams,
)
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 = {
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -231,6 +243,9 @@ def parse_date(cls, v: str):
@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
Expand Down Expand Up @@ -263,18 +278,30 @@ async def aextract_data(
**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(
Expand Down
Loading
Loading