From 121fcdcdf6baf8c30bf2ffd9682de5d37424c0e7 Mon Sep 17 00:00:00 2001 From: Ritiek Malhotra Date: Thu, 2 Apr 2020 04:14:07 +0530 Subject: [PATCH] Authenticating services --- setup.py | 7 ++- spotdl/authorize/__init__.py | 6 ++ spotdl/authorize/authorize_base.py | 19 +++++++ spotdl/authorize/exceptions.py | 20 +++++++ spotdl/authorize/services/__init__.py | 2 + spotdl/authorize/services/spotify.py | 55 +++++++++++++++++++ .../authorize/services/tests/test_spotify.py | 19 +++++++ spotdl/authorize/tests/test_authorize_base.py | 16 ++++++ .../tests/test_authorize_exceptions.py | 15 +++++ ...xceptions.py => test_encode_exceptions.py} | 0 ...xceptions.py => test_lyrics_exceptions.py} | 0 ...eptions.py => test_metadata_exceptions.py} | 0 spotdl/spotify_tools.py | 10 ---- 13 files changed, 157 insertions(+), 12 deletions(-) create mode 100644 spotdl/authorize/__init__.py create mode 100644 spotdl/authorize/authorize_base.py create mode 100644 spotdl/authorize/exceptions.py create mode 100644 spotdl/authorize/services/__init__.py create mode 100644 spotdl/authorize/services/spotify.py create mode 100644 spotdl/authorize/services/tests/test_spotify.py create mode 100644 spotdl/authorize/tests/test_authorize_base.py create mode 100644 spotdl/authorize/tests/test_authorize_exceptions.py rename spotdl/encode/tests/{test_exceptions.py => test_encode_exceptions.py} (100%) rename spotdl/lyrics/tests/{test_exceptions.py => test_lyrics_exceptions.py} (100%) rename spotdl/metadata/tests/{test_exceptions.py => test_metadata_exceptions.py} (100%) diff --git a/setup.py b/setup.py index 0f96c0c..5c87106 100644 --- a/setup.py +++ b/setup.py @@ -16,8 +16,11 @@ setup( "spotdl.lyrics.providers", "spotdl.encode", "spotdl.encode.encoders", - "spotdl.downloaders", - "spotdl.patch", + "spotdl.metadata", + "spotdl.metadata.embedders", + "spotdl.metadata.providers", + "spotdl.authorize", + "spotdl.authorize.services", ], version=spotdl.__version__, install_requires=[ diff --git a/spotdl/authorize/__init__.py b/spotdl/authorize/__init__.py new file mode 100644 index 0000000..72fdc72 --- /dev/null +++ b/spotdl/authorize/__init__.py @@ -0,0 +1,6 @@ +from spotdl.authorize.authorize_base import AuthorizeBase + +from spotdl.authorize.exceptions import AuthorizationError +from spotdl.authorize.exceptions import SpotifyAuthorizationError +from spotdl.authorize.exceptions import YouTubeAuthorizationError + diff --git a/spotdl/authorize/authorize_base.py b/spotdl/authorize/authorize_base.py new file mode 100644 index 0000000..2e87ce9 --- /dev/null +++ b/spotdl/authorize/authorize_base.py @@ -0,0 +1,19 @@ +from abc import ABC +from abc import abstractmethod + +class AuthorizeBase(ABC): + """ + Defined service authenticators must inherit from this abstract + base class and implement their own functionality for the below + defined methods. + """ + + @abstractmethod + def authorize(self): + """ + This method must authorize with the corresponding service + and return an object that can be utilized in making + authenticated requests. + """ + pass + diff --git a/spotdl/authorize/exceptions.py b/spotdl/authorize/exceptions.py new file mode 100644 index 0000000..65bcbde --- /dev/null +++ b/spotdl/authorize/exceptions.py @@ -0,0 +1,20 @@ +class AuthorizationError(Exception): + __module__ = Exception.__module__ + + def __init__(self, message=None): + super().__init__(message) + + +class SpotifyAuthorizationError(AuthorizationError): + __module__ = Exception.__module__ + + def __init__(self, message=None): + super().__init__(message) + + +class YouTubeAuthorizationError(AuthorizationError): + __module__ = Exception.__module__ + + def __init__(self, message=None): + super().__init__(message) + diff --git a/spotdl/authorize/services/__init__.py b/spotdl/authorize/services/__init__.py new file mode 100644 index 0000000..87cda5f --- /dev/null +++ b/spotdl/authorize/services/__init__.py @@ -0,0 +1,2 @@ +from spotdl.authorize.services.spotify import AuthorizeSpotify + diff --git a/spotdl/authorize/services/spotify.py b/spotdl/authorize/services/spotify.py new file mode 100644 index 0000000..9c94bf0 --- /dev/null +++ b/spotdl/authorize/services/spotify.py @@ -0,0 +1,55 @@ +from spotdl.authorize import AuthorizeBase +from spotdl.authorize.exceptions import SpotifyAuthorizationError + +import spotipy +import spotipy.oauth2 as oauth2 + +# This global_client is used to keep the last logged-in client +# object in memory for for persistence. If credentials aren't +# provided when creating further objects, the last authenticated +# client object with correct credentials is returned when +# `AuthorizeSpotify().authorize()` is called. +global_client = None + +class AuthorizeSpotify(AuthorizeBase): + def __init__(self): + global global_client + self._client = global_client + + def _generate_token(self, client_id, client_secret): + """ Generate the token. """ + credentials = oauth2.SpotifyClientCredentials( + client_id=client_id, + client_secret=client_secret, + ) + token = credentials.get_access_token() + return token + + def authorize(self, client_id=None, client_secret=None): + no_credentials_provided = client_id is None and client_secret is None + not_valid_input = no_credentials_provided and self._client is None + + if not_valid_input: + raise SpotifyAuthorizationError( + "You must pass in client_id and client_secret to this method " + "when authenticating for the first time." + ) + + if no_credentials_provided: + return self._client + + try: + token = self._generate_token(client_id, client_secret) + except spotipy.SpotifyOauthError: + raise SpotifyAuthorizeError( + "Failed to retrieve token. Perhaps you provided invalid credentials?" + ) + + spotify = spotipy.Spotify(auth=token) + + self._client = spotify + global global_client + global_client = spotify + + return spotify + diff --git a/spotdl/authorize/services/tests/test_spotify.py b/spotdl/authorize/services/tests/test_spotify.py new file mode 100644 index 0000000..6e55b80 --- /dev/null +++ b/spotdl/authorize/services/tests/test_spotify.py @@ -0,0 +1,19 @@ +from spotdl.authorize.services import AuthorizeSpotify + +import pytest + +class TestSpotifyAuthorize: + # TODO: Test these once we a have config.py + # storing pre-defined default credentials. + # + # We'll use these credentials to create + # a spotipy object via below tests + + @pytest.mark.xfail + def test_generate_token(self): + raise NotImplementedError + + @pytest.mark.xfail + def test_authorize(self): + raise NotImplementedError + diff --git a/spotdl/authorize/tests/test_authorize_base.py b/spotdl/authorize/tests/test_authorize_base.py new file mode 100644 index 0000000..f7c453b --- /dev/null +++ b/spotdl/authorize/tests/test_authorize_base.py @@ -0,0 +1,16 @@ +from spotdl.authorize import AuthorizeBase + +import pytest + +class TestAbstractBaseClass: + def test_error_abstract_base_class_authorizebase(self): + with pytest.raises(TypeError): + AuthorizeBase() + + def test_inherit_abstract_base_class_authorizebase(self): + class AuthorizeKid(AuthorizeBase): + def authorize(self): + pass + + AuthorizeKid() + diff --git a/spotdl/authorize/tests/test_authorize_exceptions.py b/spotdl/authorize/tests/test_authorize_exceptions.py new file mode 100644 index 0000000..17fc50e --- /dev/null +++ b/spotdl/authorize/tests/test_authorize_exceptions.py @@ -0,0 +1,15 @@ +from spotdl.authorize.exceptions import AuthorizationError +from spotdl.authorize.exceptions import SpotifyAuthorizationError +from spotdl.authorize.exceptions import YouTubeAuthorizationError + + +class TestEncoderNotFoundSubclass: + def test_authozation_error_subclass(self): + assert issubclass(AuthorizationError, Exception) + + def test_spotify_authorization_error_subclass(self): + assert issubclass(SpotifyAuthorizationError, AuthorizationError) + + def test_youtube_authorization_error_subclass(self): + assert issubclass(YouTubeAuthorizationError, AuthorizationError) + diff --git a/spotdl/encode/tests/test_exceptions.py b/spotdl/encode/tests/test_encode_exceptions.py similarity index 100% rename from spotdl/encode/tests/test_exceptions.py rename to spotdl/encode/tests/test_encode_exceptions.py diff --git a/spotdl/lyrics/tests/test_exceptions.py b/spotdl/lyrics/tests/test_lyrics_exceptions.py similarity index 100% rename from spotdl/lyrics/tests/test_exceptions.py rename to spotdl/lyrics/tests/test_lyrics_exceptions.py diff --git a/spotdl/metadata/tests/test_exceptions.py b/spotdl/metadata/tests/test_metadata_exceptions.py similarity index 100% rename from spotdl/metadata/tests/test_exceptions.py rename to spotdl/metadata/tests/test_metadata_exceptions.py diff --git a/spotdl/spotify_tools.py b/spotdl/spotify_tools.py index cb92787..045ec5b 100644 --- a/spotdl/spotify_tools.py +++ b/spotdl/spotify_tools.py @@ -17,16 +17,6 @@ from spotdl.lyrics.exceptions import LyricsNotFoundError spotify = None -def generate_token(): - """ Generate the token. """ - credentials = oauth2.SpotifyClientCredentials( - client_id=const.args.spotify_client_id, - client_secret=const.args.spotify_client_secret, - ) - token = credentials.get_access_token() - return token - - def must_be_authorized(func, spotify=spotify): def wrapper(*args, **kwargs): global spotify