from __future__ import annotations from abc import ABC, abstractmethod from typing import Any, Generic, MutableMapping, Optional, TypeVar, cast from marshmallow import Schema from src.api_client.dsp import BaseDSPApiClient from src.api_client.errors import DSPApiError from src.enums import ServiceType, get_music_service from src.logger import BoundLogger from src.utils import get_dsp_api_client from ..models import ServiceAccount from .error import ServiceAuthorizationError __all__ = ["BaseDSPAuthorizer"] T = TypeVar("T", bound=BaseDSPApiClient) class BaseDSPAuthorizer(Generic[T], ABC): service_type: ServiceType callback_schema: Schema def __init__(self, *, logger: BoundLogger): self.__callback_host: str | None = None self._logger = logger self._api_client = self._get_api_client() def _get_api_client(self) -> T: return cast(T, get_dsp_api_client(self.service_type, self._logger)) def set_callback_host(self, url: str): self.__callback_host = url @property def _api_url(self): if self.__callback_host is None: raise AttributeError("'set_callback_host' need to be called") return self.__callback_host def get_callback_uri(self) -> str: return f"{self._api_url}/_serviceaccounts/callback/{self.__class__.service_type.name}" @abstractmethod def get_auth_url(self) -> str: pass def _get_service_account_data(self, code: str, redirect_uri: Optional[str], **extra): data: MutableMapping[str, Any] = {} try: data.update( self._api_client.get_token_from_code( code, redirect_uri=redirect_uri or self.get_callback_uri(), **extra ).to_dict() ) user_info = self._api_client.get_user_info(**extra).to_dict() user_info.pop("image_url") # TODO: if image_url will be added to table this line can be removed data.update(user_info) except DSPApiError as e: raise ServiceAuthorizationError("Can't get info from code") from e return data def complete_auth(self, app_id: int, code: str | None = None, error: str | None = None, **extra) -> ServiceAccount: if error: raise ServiceAuthorizationError(error) if code is None: raise ServiceAuthorizationError("No code provided") redirect_uri = extra.pop("redirect_uri", None) return ServiceAccount( application_id=app_id, service_type=self.__class__.service_type, music_service=get_music_service(self.__class__.service_type), redirect_uri=redirect_uri, **self._get_service_account_data(code, redirect_uri=redirect_uri, **extra), )