A request can wait to be served for longer than the access token it was sent with lives, and comes back refused for a token that was valid when it left. Uploads that take minutes make that wait ordinary. - tell an expired token apart from any other refusal by the code the API returns, and retry only that one, once - prefer a token another process left behind over minting a second: it saves a round trip, and a rotating provider would retire one that is still in use. Assign it rather than read it, since the setter is what rewrites the session header the retry will carry - settle the token before the request so the one compared afterwards is the one that was actually sent - report an overloaded server when the second attempt is refused too, and let API errors out of the upload path with their own type
42 lines
1.8 KiB
Python
42 lines
1.8 KiB
Python
from abc import ABC
|
|
|
|
import requests
|
|
from pydantic import TypeAdapter
|
|
from requests import Response
|
|
|
|
from mdrsclient.connection import MDRSConnection
|
|
from mdrsclient.exceptions import (
|
|
BadRequestException,
|
|
ForbiddenException,
|
|
TokenExpiredException,
|
|
UnauthorizedException,
|
|
UnexpectedException,
|
|
)
|
|
from mdrsclient.models.error import DRFStandardizedErrors
|
|
|
|
|
|
class BaseApi(ABC):
|
|
connection: MDRSConnection
|
|
|
|
def __init__(self, connection: MDRSConnection) -> None:
|
|
self.connection = connection
|
|
|
|
def _raise_response_error(self, response: Response) -> None:
|
|
if response.status_code >= 300:
|
|
if response.status_code < 400 or response.status_code >= 500:
|
|
raise UnexpectedException(f"Unexpected status code returned: {response.status_code}.")
|
|
errors = TypeAdapter(DRFStandardizedErrors).validate_python(response.json())
|
|
if response.status_code == requests.codes.bad_request:
|
|
raise BadRequestException(errors.errors[0].detail)
|
|
elif response.status_code == requests.codes.unauthorized:
|
|
# A request can wait in the server's queue for longer than the access
|
|
# token it was sent with lives, so an expired token here does not mean
|
|
# the session is over - it means this one request arrived too late.
|
|
if any(e.code == "token_not_valid" for e in errors.errors):
|
|
raise TokenExpiredException("Access token expired before the request was served.")
|
|
raise UnauthorizedException("Login required.")
|
|
elif response.status_code == requests.codes.forbidden:
|
|
raise ForbiddenException("You do not have enough permissions. Access is denied.")
|
|
else:
|
|
raise UnexpectedException(errors.errors[0].detail)
|