From 3681a9b616212656462efa0a9c78722f90cca442 Mon Sep 17 00:00:00 2001 From: Mike Crute Date: Mon, 8 Jun 2020 18:20:57 +0000 Subject: Use black for formatting I don't particularly like the black formatter (does anybody, really?) but it's guaranteed to be consistent so it should prevent nitpicking style. --- pandora/client.py | 215 ++++++++++++---------- pandora/clientbuilder.py | 42 +++-- pandora/errors.py | 9 +- pandora/models/_base.py | 12 +- pandora/models/ad.py | 5 +- pandora/models/playlist.py | 36 ++-- pandora/models/search.py | 15 +- pandora/models/station.py | 9 +- pandora/transport.py | 35 ++-- pydora/audio_backend.py | 7 +- pydora/configure.py | 15 +- pydora/player.py | 56 ++++-- pydora/utils.py | 5 +- setup.py | 41 +++-- tests/test_pandora/test_client.py | 237 ++++++++++++++---------- tests/test_pandora/test_clientbuilder.py | 112 ++++++------ tests/test_pandora/test_errors.py | 1 - tests/test_pandora/test_models.py | 303 +++++++++++++++++-------------- tests/test_pandora/test_transport.py | 60 +++--- tests/test_pydora/test_utils.py | 27 ++- 20 files changed, 708 insertions(+), 534 deletions(-) diff --git a/pandora/client.py b/pandora/client.py index e0d3da9..a98056c 100644 --- a/pandora/client.py +++ b/pandora/client.py @@ -29,8 +29,14 @@ class BaseAPIClient: ALL_QUALITIES = [LOW_AUDIO_QUALITY, MED_AUDIO_QUALITY, HIGH_AUDIO_QUALITY] - def __init__(self, transport, partner_user, partner_password, device, - default_audio_quality=MED_AUDIO_QUALITY): + def __init__( + self, + transport, + partner_user, + partner_password, + device, + default_audio_quality=MED_AUDIO_QUALITY, + ): self.transport = transport self.partner_user = partner_user self.partner_password = partner_password @@ -40,11 +46,13 @@ class BaseAPIClient: self.password = None def _partner_login(self): - partner = self.transport("auth.partnerLogin", - username=self.partner_user, - password=self.partner_password, - deviceModel=self.device, - version=self.transport.API_VERSION) + partner = self.transport( + "auth.partnerLogin", + username=self.partner_user, + password=self.partner_password, + deviceModel=self.device, + version=self.transport.API_VERSION, + ) self.transport.set_partner(partner) @@ -59,16 +67,18 @@ class BaseAPIClient: self._partner_login() try: - user = self.transport("auth.userLogin", - loginType="user", - username=self.username, - password=self.password, - includePandoraOneInfo=True, - includeSubscriptionExpiration=True, - returnCapped=True, - includeAdAttributes=True, - includeAdvertiserAttributes=True, - xplatformAdCapable=True) + user = self.transport( + "auth.userLogin", + loginType="user", + username=self.username, + password=self.password, + includePandoraOneInfo=True, + includeSubscriptionExpiration=True, + returnCapped=True, + includeAdAttributes=True, + includeAdvertiserAttributes=True, + xplatformAdCapable=True, + ) except errors.InvalidPartnerLogin: raise errors.InvalidUserLogin() @@ -80,7 +90,7 @@ class BaseAPIClient: def get_qualities(cls, start_at, return_all_if_invalid=True): try: idx = cls.ALL_QUALITIES.index(start_at) - return cls.ALL_QUALITIES[:idx + 1] + return cls.ALL_QUALITIES[: idx + 1] except ValueError: if return_all_if_invalid: return cls.ALL_QUALITIES[:] @@ -105,9 +115,9 @@ class APIClient(BaseAPIClient): def get_station_list(self): from .models.station import StationList - return StationList.from_json(self, - self("user.getStationList", - includeStationArtUrl=True)) + return StationList.from_json( + self, self("user.getStationList", includeStationArtUrl=True) + ) def get_station_list_checksum(self): return self("user.getStationListChecksum")["checksum"] @@ -119,19 +129,21 @@ class APIClient(BaseAPIClient): additional_urls = [] if isinstance(additional_urls, str): - raise TypeError('Additional urls should be a list') + raise TypeError("Additional urls should be a list") urls = [getattr(url, "value", url) for url in additional_urls] - resp = self("station.getPlaylist", - stationToken=station_token, - includeTrackLength=True, - xplatformAdCapable=True, - audioAdPodCapable=True, - additionalAudioUrl=','.join(urls)) + resp = self( + "station.getPlaylist", + stationToken=station_token, + includeTrackLength=True, + xplatformAdCapable=True, + audioAdPodCapable=True, + additionalAudioUrl=",".join(urls), + ) - for item in resp['items']: - item['_paramAdditionalUrls'] = additional_urls + for item in resp["items"]: + item["_paramAdditionalUrls"] = additional_urls playlist = Playlist.from_json(self, resp) @@ -145,58 +157,69 @@ class APIClient(BaseAPIClient): def get_bookmarks(self): from .models.bookmark import BookmarkList - return BookmarkList.from_json(self, - self("user.getBookmarks")) + return BookmarkList.from_json(self, self("user.getBookmarks")) def get_station(self, station_token): from .models.station import Station - return Station.from_json(self, - self("station.getStation", - stationToken=station_token, - includeExtendedAttributes=True)) + return Station.from_json( + self, + self( + "station.getStation", + stationToken=station_token, + includeExtendedAttributes=True, + ), + ) def add_artist_bookmark(self, track_token): - return self("bookmark.addArtistBookmark", - trackToken=track_token) + return self("bookmark.addArtistBookmark", trackToken=track_token) def add_song_bookmark(self, track_token): - return self("bookmark.addSongBookmark", - trackToken=track_token) + return self("bookmark.addSongBookmark", trackToken=track_token) def delete_song_bookmark(self, bookmark_token): - return self("bookmark.deleteSongBookmark", - bookmarkToken=bookmark_token) + return self( + "bookmark.deleteSongBookmark", bookmarkToken=bookmark_token + ) def delete_artist_bookmark(self, bookmark_token): - return self("bookmark.deleteArtistBookmark", - bookmarkToken=bookmark_token) + return self( + "bookmark.deleteArtistBookmark", bookmarkToken=bookmark_token + ) - def search(self, search_text, - include_near_matches=False, - include_genre_stations=False): + def search( + self, + search_text, + include_near_matches=False, + include_genre_stations=False, + ): from .models.search import SearchResult return SearchResult.from_json( self, - self("music.search", - searchText=search_text, - includeNearMatches=include_near_matches, - includeGenreStations=include_genre_stations) + self( + "music.search", + searchText=search_text, + includeNearMatches=include_near_matches, + includeGenreStations=include_genre_stations, + ), ) def add_feedback(self, track_token, positive): - return self("station.addFeedback", - trackToken=track_token, - isPositive=positive) + return self( + "station.addFeedback", trackToken=track_token, isPositive=positive + ) def add_music(self, music_token, station_token): - return self("station.addMusic", - musicToken=music_token, - stationToken=station_token) + return self( + "station.addMusic", + musicToken=music_token, + stationToken=station_token, + ) - def create_station(self, search_token=None, artist_token=None, - track_token=None): + def create_station( + self, search_token=None, artist_token=None, track_token=None + ): from .models.station import Station kwargs = {} @@ -210,26 +233,23 @@ class APIClient(BaseAPIClient): else: raise KeyError("Must pass a type of token") - return Station.from_json(self, - self("station.createStation", **kwargs)) + return Station.from_json(self, self("station.createStation", **kwargs)) def delete_feedback(self, feedback_id): - return self("station.deleteFeedback", - feedbackId=feedback_id) + return self("station.deleteFeedback", feedbackId=feedback_id) def delete_music(self, seed_id): - return self("station.deleteMusic", - seedId=seed_id) + return self("station.deleteMusic", seedId=seed_id) def delete_station(self, station_token): - return self("station.deleteStation", - stationToken=station_token) + return self("station.deleteStation", stationToken=station_token) def get_genre_stations(self): from .models.station import GenreStationList genre_stations = GenreStationList.from_json( - self, self("station.getGenreStations")) + self, self("station.getGenreStations") + ) genre_stations.checksum = self.get_genre_stations_checksum() return genre_stations @@ -238,44 +258,45 @@ class APIClient(BaseAPIClient): return self("station.getGenreStationsChecksum")["checksum"] def rename_station(self, station_token, name): - return self("station.renameStation", - stationToken=station_token, - stationName=name) + return self( + "station.renameStation", + stationToken=station_token, + stationName=name, + ) def explain_track(self, track_token): - return self("track.explainTrack", - trackToken=track_token) + return self("track.explainTrack", trackToken=track_token) def set_quick_mix(self, *args): - return self("user.setQuickMix", - quickMixStationIds=args) + return self("user.setQuickMix", quickMixStationIds=args) def sleep_song(self, track_token): - return self("user.sleepSong", - trackToken=track_token) + return self("user.sleepSong", trackToken=track_token) def share_station(self, station_id, station_token, *emails): - return self("station.shareStation", - stationId=station_id, - stationToken=station_token, - emails=emails) + return self( + "station.shareStation", + stationId=station_id, + stationToken=station_token, + emails=emails, + ) def transform_shared_station(self, station_token): - return self("station.transformSharedStation", - stationToken=station_token) + return self( + "station.transformSharedStation", stationToken=station_token + ) def share_music(self, music_token, *emails): - return self("music.shareMusic", - musicToken=music_token, - email=emails[0]) + return self( + "music.shareMusic", musicToken=music_token, email=emails[0] + ) def get_ad_item(self, station_id, ad_token): from .models.ad import AdItem if not station_id: - raise errors.ParameterMissing("The 'station_id' param must be " - "defined, got: '{}'" - .format(station_id)) + msg = "The 'station_id' param must be defined, got: '{}'" + raise errors.ParameterMissing(msg.format(station_id)) ad_item = AdItem.from_json(self, self.get_ad_metadata(ad_token)) ad_item.station_id = station_id @@ -283,12 +304,14 @@ class APIClient(BaseAPIClient): return ad_item def get_ad_metadata(self, ad_token): - return self("ad.getAdMetadata", - adToken=ad_token, - returnAdTrackingTokens=True, - supportAudioAds=True) + return self( + "ad.getAdMetadata", + adToken=ad_token, + returnAdTrackingTokens=True, + supportAudioAds=True, + ) def register_ad(self, station_id, tokens): - return self("ad.registerAd", - stationId=station_id, - adTrackingTokens=tokens) + return self( + "ad.registerAd", stationId=station_id, adTrackingTokens=tokens + ) diff --git a/pandora/clientbuilder.py b/pandora/clientbuilder.py index bc55cec..2b11ffb 100644 --- a/pandora/clientbuilder.py +++ b/pandora/clientbuilder.py @@ -70,8 +70,7 @@ class TranslatingDict(dict): def __setitem__(self, key, value): key = self.translate_key(key) - super().__setitem__( - key, self.translate_value(key, value)) + super().__setitem__(key, self.translate_value(key, value)) class APIClientBuilder: @@ -99,19 +98,25 @@ class APIClientBuilder: self.client_class = client_class or self.DEFAULT_CLIENT_CLASS def build_from_settings_dict(self, settings): - enc = Encryptor(settings["DECRYPTION_KEY"], - settings["ENCRYPTION_KEY"]) + enc = Encryptor(settings["DECRYPTION_KEY"], settings["ENCRYPTION_KEY"]) - trans = APITransport(enc, - settings.get("API_HOST", DEFAULT_API_HOST), - settings.get("PROXY", None)) + trans = APITransport( + enc, + settings.get("API_HOST", DEFAULT_API_HOST), + settings.get("PROXY", None), + ) - quality = settings.get("AUDIO_QUALITY", - self.client_class.MED_AUDIO_QUALITY) + quality = settings.get( + "AUDIO_QUALITY", self.client_class.MED_AUDIO_QUALITY + ) - return self.client_class(trans, settings["PARTNER_USER"], - settings["PARTNER_PASSWORD"], - settings["DEVICE"], quality) + return self.client_class( + trans, + settings["PARTNER_USER"], + settings["PARTNER_PASSWORD"], + settings["DEVICE"], + quality, + ) class SettingsDict(TranslatingDict): @@ -185,8 +190,9 @@ class FileBasedClientBuilder(APIClientBuilder): client = self.build_from_settings_dict(config) if self.authenticate: - client.login(config["USER"]["USERNAME"], - config["USER"]["PASSWORD"]) + client.login( + config["USER"]["USERNAME"], config["USER"]["PASSWORD"] + ) return client @@ -201,8 +207,9 @@ class PydoraConfigFileBuilder(FileBasedClientBuilder): @staticmethod def cfg_to_dict(cfg, key, kind=SettingsDict): - return kind((k.strip().upper(), v.strip()) - for k, v in cfg.items(key, raw=True)) + return kind( + (k.strip().upper(), v.strip()) for k, v in cfg.items(key, raw=True) + ) def parse_config(self): cfg = ConfigParser() @@ -212,7 +219,8 @@ class PydoraConfigFileBuilder(FileBasedClientBuilder): settings = PydoraConfigFileBuilder.cfg_to_dict(cfg, "api") settings["user"] = PydoraConfigFileBuilder.cfg_to_dict( - cfg, "user", dict) + cfg, "user", dict + ) return settings diff --git a/pandora/errors.py b/pandora/errors.py index c978708..3b03db2 100644 --- a/pandora/errors.py +++ b/pandora/errors.py @@ -86,10 +86,11 @@ class PandoraException(Exception): for code, api_message in __API_EXCEPTIONS__.items(): name = PandoraException._format_name(api_message) - exception = type(name, (PandoraException,), { - "code": code, - "message": api_message, - }) + exception = type( + name, + (PandoraException,), + {"code": code, "message": api_message,}, + ) export_to[name] = __API_EXCEPTIONS__[code] = exception diff --git a/pandora/models/_base.py b/pandora/models/_base.py index 6975d0a..8904be1 100644 --- a/pandora/models/_base.py +++ b/pandora/models/_base.py @@ -71,7 +71,6 @@ class DateField(SyntheticField): class ModelMetaClass(type): - def __new__(cls, name, parents, dct): dct["_fields"] = fields = {} new_dct = dct.copy() @@ -159,7 +158,8 @@ class PandoraModel(metaclass=ModelMetaClass): """ items = [ "=".join((key, repr(getattr(self, key)))) - for key in sorted(self._fields.keys())] + for key in sorted(self._fields.keys()) + ] if items: output = ", ".join(items) @@ -167,8 +167,9 @@ class PandoraModel(metaclass=ModelMetaClass): output = None if and_also: - return "{}({}, {})".format(self.__class__.__name__, - output, and_also) + return "{}({}, {})".format( + self.__class__.__name__, output, and_also + ) else: return "{}({})".format(self.__class__.__name__, output) @@ -301,7 +302,8 @@ class PandoraDictListModel(PandoraModel, dict): for part in item[self.__list_key__]: self[key].append( - cls.__list_model__.from_json(api_client, part)) + cls.__list_model__.from_json(api_client, part) + ) return self diff --git a/pandora/models/ad.py b/pandora/models/ad.py index ad4b7b0..48cd302 100644 --- a/pandora/models/ad.py +++ b/pandora/models/ad.py @@ -24,8 +24,9 @@ class AdItem(PlaylistModel): if self.tracking_tokens: self._api_client.register_ad(station_id, self.tracking_tokens) else: - raise ParameterMissing('No ad tracking tokens provided for ' - 'registration.') + raise ParameterMissing( + "No ad tracking tokens provided for registration." + ) def prepare_playback(self): try: diff --git a/pandora/models/playlist.py b/pandora/models/playlist.py index 38afb00..8e0d22e 100644 --- a/pandora/models/playlist.py +++ b/pandora/models/playlist.py @@ -5,15 +5,15 @@ from ._base import Field, SyntheticField, PandoraModel, PandoraListModel class AdditionalAudioUrl(Enum): - HTTP_40_AAC_MONO = 'HTTP_40_AAC_MONO' - HTTP_64_AAC = 'HTTP_64_AAC' - HTTP_32_AACPLUS = 'HTTP_32_AACPLUS' - HTTP_64_AACPLUS = 'HTTP_64_AACPLUS' - HTTP_24_AACPLUS_ADTS = 'HTTP_24_AACPLUS_ADTS' - HTTP_32_AACPLUS_ADTS = 'HTTP_32_AACPLUS_ADTS' - HTTP_64_AACPLUS_ADTS = 'HTTP_64_AACPLUS_ADTS' - HTTP_128_MP3 = 'HTTP_128_MP3' - HTTP_32_WMA = 'HTTP_32_WMA' + HTTP_40_AAC_MONO = "HTTP_40_AAC_MONO" + HTTP_64_AAC = "HTTP_64_AAC" + HTTP_32_AACPLUS = "HTTP_32_AACPLUS" + HTTP_64_AACPLUS = "HTTP_64_AACPLUS" + HTTP_24_AACPLUS_ADTS = "HTTP_24_AACPLUS_ADTS" + HTTP_32_AACPLUS_ADTS = "HTTP_32_AACPLUS_ADTS" + HTTP_64_AACPLUS_ADTS = "HTTP_64_AACPLUS_ADTS" + HTTP_128_MP3 = "HTTP_128_MP3" + HTTP_32_WMA = "HTTP_32_WMA" class PandoraType(Enum): @@ -28,14 +28,14 @@ class PandoraType(Enum): @staticmethod def from_string(value): - return { + types = { "TR": PandoraType.TRACK, "AR": PandoraType.ARTIST, - }.get(value, PandoraType.GENRE) + } + return types.get(value, PandoraType.GENRE) class AudioField(SyntheticField): - def formatter(self, api_client, data, newval): """Get audio-related fields @@ -61,9 +61,11 @@ class AudioField(SyntheticField): elif not url_map: # No audio url available (e.g. ad tokens) return None - valid_audio_formats = [BaseAPIClient.HIGH_AUDIO_QUALITY, - BaseAPIClient.MED_AUDIO_QUALITY, - BaseAPIClient.LOW_AUDIO_QUALITY] + valid_audio_formats = [ + BaseAPIClient.HIGH_AUDIO_QUALITY, + BaseAPIClient.MED_AUDIO_QUALITY, + BaseAPIClient.LOW_AUDIO_QUALITY, + ] # Only iterate over sublist, starting at preferred audio quality, or # from the beginning of the list if nothing is found. Ensures that the @@ -84,7 +86,6 @@ class AudioField(SyntheticField): class AdditionalUrlField(SyntheticField): - def formatter(self, api_client, data, newval): """Parse additional url fields and map them to inputs @@ -94,7 +95,7 @@ class AdditionalUrlField(SyntheticField): if newval is None: return None - user_param = data['_paramAdditionalUrls'] + user_param = data["_paramAdditionalUrls"] urls = {} if isinstance(newval, str): urls[user_param[0]] = newval @@ -105,7 +106,6 @@ class AdditionalUrlField(SyntheticField): class PlaylistModel(PandoraModel): - def get_is_playable(self): if not self.audio_url: return False diff --git a/pandora/models/search.py b/pandora/models/search.py index 94e6ee6..fe31561 100644 --- a/pandora/models/search.py +++ b/pandora/models/search.py @@ -12,13 +12,15 @@ class SearchResultItem(PandoraModel): @property def is_artist(self): - return isinstance(self, ArtistSearchResultItem) and \ - self.token.startswith("R") + return isinstance( + self, ArtistSearchResultItem + ) and self.token.startswith("R") @property def is_composer(self): - return isinstance(self, ArtistSearchResultItem) and \ - self.token.startswith("C") + return isinstance( + self, ArtistSearchResultItem + ) and self.token.startswith("C") @property def is_genre_station(self): @@ -36,8 +38,9 @@ class SearchResultItem(PandoraModel): elif data["musicToken"].startswith("G"): return GenreStationSearchResultItem.from_json(api_client, data) else: - raise NotImplementedError("Unknown result token type '{}'" - .format(data["musicToken"])) + raise NotImplementedError( + "Unknown result token type '{}'".format(data["musicToken"]) + ) class ArtistSearchResultItem(SearchResultItem): diff --git a/pandora/models/station.py b/pandora/models/station.py index a1880ec..d3f6552 100644 --- a/pandora/models/station.py +++ b/pandora/models/station.py @@ -80,8 +80,7 @@ class Station(PandoraModel): feedback = Field("feedback", model=StationFeedback) def get_playlist(self, additional_urls=None): - return iter(self._api_client.get_playlist(self.token, - additional_urls)) + return iter(self._api_client.get_playlist(self.token, additional_urls)) class StationList(PandoraListModel): @@ -105,8 +104,10 @@ class GenreStation(PandoraModel): category = Field("categoryName") def get_playlist(self): - raise NotImplementedError("Genre stations do not have playlists. " - "Create a real station using the token.") + raise NotImplementedError( + "Genre stations do not have playlists. " + "Create a real station using the token." + ) class GenreStationList(PandoraDictListModel): diff --git a/pandora/transport.py b/pandora/transport.py index edec8a8..84f153b 100644 --- a/pandora/transport.py +++ b/pandora/transport.py @@ -40,6 +40,7 @@ def retries(max_tries, exceptions=(Exception,)): function will only be retried if it raises one of the specified exceptions. """ + def decorator(func): def function(*args, **kwargs): @@ -55,8 +56,9 @@ def retries(max_tries, exceptions=(Exception,)): if isinstance(exc, PandoraException): raise if retries_left > 0: - time.sleep(delay_exponential( - 0.5, 2, max_tries - retries_left)) + time.sleep( + delay_exponential(0.5, 2, max_tries - retries_left) + ) else: raise @@ -76,11 +78,12 @@ def delay_exponential(base, growth_factor, attempts): Base must be greater than 0, otherwise a ValueError will be raised. """ - if base == 'rand': + if base == "rand": base = random.random() elif base <= 0: - raise ValueError("The 'base' param must be greater than 0, " - "got: {}".format(base)) + raise ValueError( + "The 'base' param must be greater than 0, got: {}".format(base) + ) time_to_sleep = base * (growth_factor ** (attempts - 1)) return time_to_sleep @@ -95,8 +98,8 @@ class RetryingSession(requests.Session): def __init__(self): super().__init__() - self.mount('https://', HTTPAdapter(max_retries=3)) - self.mount('http://', HTTPAdapter(max_retries=3)) + self.mount("https://", HTTPAdapter(max_retries=3)) + self.mount("http://", HTTPAdapter(max_retries=3)) class APITransport: @@ -109,10 +112,14 @@ class APITransport: API_VERSION = "5" - REQUIRE_RESET = ("auth.partnerLogin", ) - NO_ENCRYPT = ("auth.partnerLogin", ) - REQUIRE_TLS = ("auth.partnerLogin", "auth.userLogin", - "station.getPlaylist", "user.createUser") + REQUIRE_RESET = ("auth.partnerLogin",) + NO_ENCRYPT = ("auth.partnerLogin",) + REQUIRE_TLS = ( + "auth.partnerLogin", + "auth.userLogin", + "station.getPlaylist", + "user.createUser", + ) def __init__(self, cryptor, api_host=DEFAULT_API_HOST, proxy=None): self.cryptor = cryptor @@ -199,8 +206,8 @@ class APITransport: def _build_url(self, method): return "{}://{}".format( - "https" if method in self.REQUIRE_TLS else "http", - self.api_host) + "https" if method in self.REQUIRE_TLS else "http", self.api_host + ) def _build_data(self, method, data): data["userAuthToken"] = self.user_auth_token @@ -260,7 +267,7 @@ class BlowfishCryptor: computed = b"".join([chr(pad_size).encode("ascii")] * pad_size) if not data[-pad_size:] == computed: - raise ValueError('Invalid padding') + raise ValueError("Invalid padding") return data[:-pad_size] diff --git a/pydora/audio_backend.py b/pydora/audio_backend.py index bd778ad..aecd87e 100644 --- a/pydora/audio_backend.py +++ b/pydora/audio_backend.py @@ -15,18 +15,21 @@ log = logging.getLogger("pydora.audio_backend") class PlayerException(Exception): """Base class for all player exceptions """ + pass class UnsupportedEncoding(PlayerException): """Song encoding is not supported by player backend """ + pass class PlayerUnusable(PlayerException): """Player can not be used on this system """ + pass @@ -172,7 +175,8 @@ class BasePlayer: self._loop_hook() readers, _, _ = select.select( - self._get_select_readers(), [], [], 1) + self._get_select_readers(), [], [], 1 + ) for handle in readers: if handle.fileno() == self._control_fd: @@ -296,7 +300,6 @@ class VLCPlayer(BasePlayer): class RemoteVLC(VLCPlayer): - def __init__(self, host, port, callbacks, control_channel): self._connect_to = (host, int(port)) self._control_sock = None diff --git a/pydora/configure.py b/pydora/configure.py index d31c097..d58d445 100644 --- a/pydora/configure.py +++ b/pydora/configure.py @@ -32,11 +32,13 @@ class PandoraKeysConfigParser: the pandora API docs keys source file. """ - KEYS_URL = ("http://6xq.net/git/lars/pandora-apidoc.git/" - "plain/json/partners.rst") + KEYS_URL = ( + "http://6xq.net/git/lars/pandora-apidoc.git/plain/json/partners.rst" + ) FIELD_RE = re.compile( - ":(?P[^:]+): (?:`{2})?(?P[^`\n]+)(?:`{2})?$") + ":(?P[^:]+): (?:`{2})?(?P[^`\n]+)(?:`{2})?$" + ) def _fixup_key(self, key): key = key.lower() @@ -90,7 +92,7 @@ class PandoraKeysConfigParser: key = self._clean_device_name(buffer.pop()) current_partner = partners[key] = { "api_host": self._format_api_host(api_host) - } + } buffer.append(line.strip().lower()) @@ -165,8 +167,9 @@ class Configurator: self.add_partner_config(self.get_partner_config()) self.get_value("user", "username", "Pandora Username: ") self.get_password("user", "password", "Pandora Password: ") - self.set_static_value("api", "default_audio_quality", - APIClient.HIGH_AUDIO_QUALITY) + self.set_static_value( + "api", "default_audio_quality", APIClient.HIGH_AUDIO_QUALITY + ) self.write_config() diff --git a/pydora/player.py b/pydora/player.py index fdacc63..c4ce2e0 100644 --- a/pydora/player.py +++ b/pydora/player.py @@ -132,8 +132,12 @@ class PlayerApp: if song.is_ad: print("{} ".format(Colors.cyan("Advertisement"))) else: - print("{} by {}".format(Colors.cyan(song.song_name), - Colors.yellow(song.artist_name))) + print( + "{} by {}".format( + Colors.cyan(song.song_name), + Colors.yellow(song.artist_name), + ) + ) def skip_song(self, song): if song.is_ad: @@ -183,13 +187,15 @@ class PlayerApp: self.screen.print_error("Failed to bookmark artis") except NotImplementedError: self.screen.print_error( - "Cannot bookmark artist for this type of track") + "Cannot bookmark artist for this type of track" + ) def sleep_song(self, song): try: if song.sleep(): self.screen.print_success( - "Song will not be played for 30 days") + "Song will not be played for 30 days" + ) self.player.stop() else: self.screen.print_error("Failed to sleep song") @@ -214,10 +220,14 @@ class PlayerApp: def help(self, song): print("") - print("\n".join([ - "\t{:>2} - {}".format(k, v[0]) - for k, v in sorted(self.CMD_MAP.items()) - ])) + print( + "\n".join( + [ + "\t{:>2} - {}".format(k, v[0]) + for k, v in sorted(self.CMD_MAP.items()) + ] + ) + ) print("") def input(self, input, song): @@ -227,7 +237,8 @@ class PlayerApp: cmd = getattr(self, self.CMD_MAP[input][1]) except (IndexError, KeyError): return self.screen.print_error( - "Invalid command {!r}!".format(input)) + "Invalid command {!r}!".format(input) + ) cmd(song) @@ -240,21 +251,30 @@ class PlayerApp: def pre_flight_checks(self): # See #52, this key no longer passes some server-side check if self.client.partner_user == "iphone": - self.screen.print_error(( - "The `iphone` partner key set is no longer compatible with " - "pydora. Please re-run pydora-configure to re-generate " - "your config file before continuing.")) + self.screen.print_error( + ( + "The `iphone` partner key set is no longer compatible " + "with pydora. Please re-run pydora-configure to " + "re-generate your config file before continuing." + ) + ) sys.exit(1) def _parse_args(self): parser = argparse.ArgumentParser( - description="command line Pandora player") + description="command line Pandora player" + ) parser.add_argument( - "--vlc-net", dest="vlc_net", - help="connect to VLC over the network (host:port)") + "--vlc-net", + dest="vlc_net", + help="connect to VLC over the network (host:port)", + ) parser.add_argument( - "-v", dest="verbose", action="store_true", - help="enable verbose logging") + "-v", + dest="verbose", + action="store_true", + help="enable verbose logging", + ) return parser.parse_args() def run(self): diff --git a/pydora/utils.py b/pydora/utils.py index 6675773..5a96ff1 100644 --- a/pydora/utils.py +++ b/pydora/utils.py @@ -10,11 +10,11 @@ class TerminalPlatformUnsupported(Exception): Raised by code that can not be used to interact with the terminal on this platform. """ + pass class Colors: - def __wrap_with(raw_code): @staticmethod def inner(text, bold=False): @@ -22,6 +22,7 @@ class Colors: if bold: code = "1;{}".format(code) return "\033[{}m{}\033[0m".format(code, text) + return inner red = __wrap_with("31") @@ -44,6 +45,7 @@ class PosixEchoControl: def __init__(self): try: import termios + self.termios = termios except ImportError: raise TerminalPlatformUnsupported("POSIX not supported") @@ -110,7 +112,6 @@ class Win32EchoControl: class Screen: - def __init__(self): try: self._echo_driver = PosixEchoControl() diff --git a/setup.py b/setup.py index 490bf1d..d7344c5 100755 --- a/setup.py +++ b/setup.py @@ -52,23 +52,32 @@ class TestsWithCoverage(test): self.missed_coverage_goals = True self.announce( "Coverage: {!r} coverage is {}%, goal is {}%".format( - rel_path, coverage_percent, self.coverage_goal), log.ERROR) + rel_path, coverage_percent, self.coverage_goal + ), + log.ERROR, + ) missed_branches = analysis.numbers.n_missing_branches if missed_branches != self.missed_branches_goal: self.missed_coverage_goals = True self.announce( "Coverage: {!r} missed branch count is {}, goal is {}".format( - rel_path, missed_branches, self.missed_branches_goal), - log.ERROR) + rel_path, missed_branches, self.missed_branches_goal + ), + log.ERROR, + ) partially_covered_branches = analysis.numbers.n_partial_branches if partially_covered_branches != self.partial_branches_goal: self.missed_coverage_goals = True self.announce( "Coverage: {!r} partial branch count is {}, goal is {}".format( - rel_path, partially_covered_branches, - self.partial_branches_goal), log.ERROR) + rel_path, + partially_covered_branches, + self.partial_branches_goal, + ), + log.ERROR, + ) def run(self): from coverage import Coverage @@ -114,13 +123,20 @@ class PyPiReleaseCommand(Command): def configure_environment(self): log.info("Configuring release environment") subprocess.check_call(["python3", "-m", "venv", ".release/py3"]) - self.venv_run("pip", "install", "-U", - "pip", "setuptools", "virtualenv", "twine") + self.venv_run( + "pip", "install", "-U", "pip", "setuptools", "virtualenv", "twine" + ) def build_py3_artifact(self): log.info("Building Python 3 Artifact") - self.venv_run("python", "setup.py", - "release", "bdist_wheel", "--python-tag", "py3") + self.venv_run( + "python", + "setup.py", + "release", + "bdist_wheel", + "--python-tag", + "py3", + ) def build_sdist_artifact(self): log.info("Building Source Dist Artifact") @@ -172,10 +188,7 @@ setup( "setuptools>=36.0.1", "coverage>=4.1,<5", ], - install_requires=[ - "requests>=2,<3", - "blowfish>=0.6.1,<1.0", - ], + install_requires=["requests>=2,<3", "blowfish>=0.6.1,<1.0",], python_requires=">=3.5", classifiers=[ "Development Status :: 5 - Production/Stable", @@ -191,5 +204,5 @@ setup( "Programming Language :: Python :: 3 :: Only", "Topic :: Internet :: WWW/HTTP", "Topic :: Multimedia :: Sound/Audio :: Players", - ] + ], ) diff --git a/tests/test_pandora/test_client.py b/tests/test_pandora/test_client.py index 8144f42..1c5a229 100644 --- a/tests/test_pandora/test_client.py +++ b/tests/test_pandora/test_client.py @@ -13,7 +13,6 @@ from tests.test_pandora.test_models import TestAdItem class TestAPIClientLogin(TestCase): - class StubTransport: API_VERSION = None @@ -56,7 +55,6 @@ class TestAPIClientLogin(TestCase): class TestCallingAPIClient(TestCase): - def test_call_should_retry_on_token_error(self): transport = Mock(side_effect=[errors.InvalidAuthToken(), None]) @@ -70,37 +68,44 @@ class TestCallingAPIClient(TestCase): transport.assert_has_calls([call("method"), call("method")]) def test_playlist_fetches_ads(self): - fake_playlist = {"items": [ - {"songName": "test"}, - {"adToken": "foo"}, - {"songName": "test"}, - ]} - with patch.object(APIClient, '__call__', return_value=fake_playlist): + fake_playlist = { + "items": [ + {"songName": "test"}, + {"adToken": "foo"}, + {"songName": "test"}, + ] + } + with patch.object(APIClient, "__call__", return_value=fake_playlist): client = APIClient(Mock(), None, None, None, None) client._authenticate = Mock() - items = client.get_playlist('token_mock') + items = client.get_playlist("token_mock") self.assertIsInstance(items[1], AdItem) def test_ad_support_enabled_parameters(self): - with patch.object(APIClient, '__call__') as playlist_mock: + with patch.object(APIClient, "__call__") as playlist_mock: transport = Mock(side_effect=[errors.InvalidAuthToken(), None]) client = APIClient(transport, None, None, None, None) client._authenticate = Mock() - client.get_playlist('token_mock') + client.get_playlist("token_mock") - playlist_mock.assert_has_calls([call("station.getPlaylist", - additionalAudioUrl='', - audioAdPodCapable=True, - includeTrackLength=True, - stationToken='token_mock', - xplatformAdCapable=True)]) + playlist_mock.assert_has_calls( + [ + call( + "station.getPlaylist", + additionalAudioUrl="", + audioAdPodCapable=True, + includeTrackLength=True, + stationToken="token_mock", + xplatformAdCapable=True, + ) + ] + ) class TestGettingQualities(TestCase): - def test_with_invalid_quality_returning_all(self): result = BaseAPIClient.get_qualities("foo", True) self.assertEqual(BaseAPIClient.ALL_QUALITIES, result) @@ -111,20 +116,22 @@ class TestGettingQualities(TestCase): def test_with_valid_quality(self): result = BaseAPIClient.get_qualities( - BaseAPIClient.MED_AUDIO_QUALITY, False) + BaseAPIClient.MED_AUDIO_QUALITY, False + ) expected = [ - BaseAPIClient.LOW_AUDIO_QUALITY, - BaseAPIClient.MED_AUDIO_QUALITY] + BaseAPIClient.LOW_AUDIO_QUALITY, + BaseAPIClient.MED_AUDIO_QUALITY, + ] self.assertEqual(expected, result) class TestGettingAds(TestCase): - def test_get_ad_item_(self): metamock = patch.object( - APIClient, '__call__', return_value=TestAdItem.JSON_DATA) + APIClient, "__call__", return_value=TestAdItem.JSON_DATA + ) with metamock as ad_metadata_mock: transport = Mock(side_effect=[errors.InvalidAuthToken(), None]) @@ -132,13 +139,20 @@ class TestGettingAds(TestCase): client = APIClient(transport, None, None, None, None) client._authenticate = Mock() - ad_item = client.get_ad_item('id_mock', 'token_mock') - assert ad_item.station_id == 'id_mock' - assert ad_item.ad_token == 'token_mock' - - ad_metadata_mock.assert_has_calls([ - call("ad.getAdMetadata", adToken='token_mock', - returnAdTrackingTokens=True, supportAudioAds=True)]) + ad_item = client.get_ad_item("id_mock", "token_mock") + assert ad_item.station_id == "id_mock" + assert ad_item.ad_token == "token_mock" + + ad_metadata_mock.assert_has_calls( + [ + call( + "ad.getAdMetadata", + adToken="token_mock", + returnAdTrackingTokens=True, + supportAudioAds=True, + ) + ] + ) def test_get_ad_item_with_no_station_id_specified_raises_exception(self): transport = Mock(side_effect=[errors.InvalidAuthToken(), None]) @@ -147,28 +161,31 @@ class TestGettingAds(TestCase): client.get_ad_metadata = Mock() self.assertRaises( - errors.ParameterMissing, client.get_ad_item, '', 'token_mock') + errors.ParameterMissing, client.get_ad_item, "", "token_mock" + ) class TestCreatingStation(TestCase): - def test_using_search_token(self): client = APIClient(Mock(return_value={}), None, None, None, None) client.create_station(search_token="foo") client.transport.assert_called_with( - "station.createStation", musicToken="foo") + "station.createStation", musicToken="foo" + ) def test_using_artist_token(self): client = APIClient(Mock(return_value={}), None, None, None, None) client.create_station(artist_token="foo") client.transport.assert_called_with( - "station.createStation", trackToken="foo", musicType="artist") + "station.createStation", trackToken="foo", musicType="artist" + ) def test_using_track_token(self): client = APIClient(Mock(return_value={}), None, None, None, None) client.create_station(track_token="foo") client.transport.assert_called_with( - "station.createStation", trackToken="foo", musicType="song") + "station.createStation", trackToken="foo", musicType="song" + ) def test_with_no_token(self): with self.assertRaises(KeyError): @@ -177,25 +194,20 @@ class TestCreatingStation(TestCase): class TestCreatingGenreStation(TestCase): - def test_has_initial_checksum(self): fake_data = { - "categories": [ - {"categoryName": "foo", "stations": []}, - ], - + "categories": [{"categoryName": "foo", "stations": []},], # Not actually part of the genre station response but is needed to # fake out the mock for get_genre_stations_checksum - "checksum": "foo" + "checksum": "foo", } - with patch.object(APIClient, '__call__', return_value=fake_data): + with patch.object(APIClient, "__call__", return_value=fake_data): client = APIClient(Mock(), None, None, None, None) station = client.get_genre_stations() self.assertEqual(station.checksum, "foo") class TestAdditionalUrls(TestCase): - def test_non_iterable_string(self): with self.assertRaises(TypeError): transport = Mock(side_effect=[errors.InvalidAuthToken(), None]) @@ -203,7 +215,7 @@ class TestAdditionalUrls(TestCase): client = APIClient(transport, None, None, None, None) client._authenticate = Mock() - client.get_playlist('token_mock', additional_urls='') + client.get_playlist("token_mock", additional_urls="") def test_non_iterable_other(self): with self.assertRaises(TypeError): @@ -212,50 +224,64 @@ class TestAdditionalUrls(TestCase): client = APIClient(transport, None, None, None, None) client._authenticate = Mock() - client.get_playlist('token_mock', - additional_urls=AdditionalAudioUrl.HTTP_32_WMA) + client.get_playlist( + "token_mock", additional_urls=AdditionalAudioUrl.HTTP_32_WMA + ) def test_without_enum(self): - with patch.object(APIClient, '__call__') as playlist_mock: + with patch.object(APIClient, "__call__") as playlist_mock: transport = Mock(side_effect=[errors.InvalidAuthToken(), None]) client = APIClient(transport, None, None, None, None) client._authenticate = Mock() - urls = ['HTTP_128_MP3', - 'HTTP_24_AACPLUS_ADTS'] + urls = ["HTTP_128_MP3", "HTTP_24_AACPLUS_ADTS"] - desired = 'HTTP_128_MP3,HTTP_24_AACPLUS_ADTS' + desired = "HTTP_128_MP3,HTTP_24_AACPLUS_ADTS" - client.get_playlist('token_mock', additional_urls=urls) + client.get_playlist("token_mock", additional_urls=urls) - playlist_mock.assert_has_calls([call("station.getPlaylist", - additionalAudioUrl=desired, - audioAdPodCapable=True, - includeTrackLength=True, - stationToken='token_mock', - xplatformAdCapable=True)]) + playlist_mock.assert_has_calls( + [ + call( + "station.getPlaylist", + additionalAudioUrl=desired, + audioAdPodCapable=True, + includeTrackLength=True, + stationToken="token_mock", + xplatformAdCapable=True, + ) + ] + ) def test_with_enum(self): - with patch.object(APIClient, '__call__') as playlist_mock: + with patch.object(APIClient, "__call__") as playlist_mock: transport = Mock(side_effect=[errors.InvalidAuthToken(), None]) client = APIClient(transport, None, None, None, None) client._authenticate = Mock() - urls = [AdditionalAudioUrl.HTTP_128_MP3, - AdditionalAudioUrl.HTTP_24_AACPLUS_ADTS] + urls = [ + AdditionalAudioUrl.HTTP_128_MP3, + AdditionalAudioUrl.HTTP_24_AACPLUS_ADTS, + ] - desired = 'HTTP_128_MP3,HTTP_24_AACPLUS_ADTS' + desired = "HTTP_128_MP3,HTTP_24_AACPLUS_ADTS" - client.get_playlist('token_mock', additional_urls=urls) + client.get_playlist("token_mock", additional_urls=urls) - playlist_mock.assert_has_calls([call("station.getPlaylist", - additionalAudioUrl=desired, - audioAdPodCapable=True, - includeTrackLength=True, - stationToken='token_mock', - xplatformAdCapable=True)]) + playlist_mock.assert_has_calls( + [ + call( + "station.getPlaylist", + additionalAudioUrl=desired, + audioAdPodCapable=True, + includeTrackLength=True, + stationToken="token_mock", + xplatformAdCapable=True, + ) + ] + ) # On the surface this test class seems dumb because it's mostly just exercising @@ -263,7 +289,6 @@ class TestAdditionalUrls(TestCase): # introduced to API client methods that will only be spotted at runtime (import # errors, etc...) class TestAPIClientExhaustive(TestCase): - def setUp(self): self.transport = Mock() self.api = APIClient(self.transport, "puser", "ppass", "device") @@ -271,23 +296,29 @@ class TestAPIClientExhaustive(TestCase): def test_register_ad(self): self.api.register_ad("sid", "tokens") self.transport.assert_called_with( - "ad.registerAd", stationId="sid", adTrackingTokens="tokens") + "ad.registerAd", stationId="sid", adTrackingTokens="tokens" + ) def test_share_music(self): self.api.share_music("token", "foo@example.com") self.transport.assert_called_with( - "music.shareMusic", musicToken="token", email="foo@example.com") + "music.shareMusic", musicToken="token", email="foo@example.com" + ) def test_transform_shared_station(self): self.api.transform_shared_station("token") self.transport.assert_called_with( - "station.transformSharedStation", stationToken="token") + "station.transformSharedStation", stationToken="token" + ) def test_share_station(self): self.api.share_station("sid", "token", "foo@example.com") self.transport.assert_called_with( - "station.shareStation", stationId="sid", stationToken="token", - emails=("foo@example.com",)) + "station.shareStation", + stationId="sid", + stationToken="token", + emails=("foo@example.com",), + ) def test_sleep_song(self): self.api.sleep_song("token") @@ -296,22 +327,26 @@ class TestAPIClientExhaustive(TestCase): def test_set_quick_mix(self): self.api.set_quick_mix("id") self.transport.assert_called_with( - "user.setQuickMix", quickMixStationIds=("id",)) + "user.setQuickMix", quickMixStationIds=("id",) + ) def test_explain_track(self): self.api.explain_track("token") self.transport.assert_called_with( - "track.explainTrack", trackToken="token") + "track.explainTrack", trackToken="token" + ) def test_rename_station(self): self.api.rename_station("token", "name") self.transport.assert_called_with( - "station.renameStation", stationToken="token", stationName="name") + "station.renameStation", stationToken="token", stationName="name" + ) def test_delete_station(self): self.api.delete_station("token") self.transport.assert_called_with( - "station.deleteStation", stationToken="token") + "station.deleteStation", stationToken="token" + ) def test_delete_music(self): self.api.delete_music("seed") @@ -320,37 +355,44 @@ class TestAPIClientExhaustive(TestCase): def test_delete_feedback(self): self.api.delete_feedback("id") self.transport.assert_called_with( - "station.deleteFeedback", feedbackId="id") + "station.deleteFeedback", feedbackId="id" + ) def test_add_music(self): self.api.add_music("mt", "st") self.transport.assert_called_with( - "station.addMusic", musicToken="mt", stationToken="st") + "station.addMusic", musicToken="mt", stationToken="st" + ) def test_add_feedback(self): self.api.add_feedback("token", False) self.transport.assert_called_with( - "station.addFeedback", trackToken="token", isPositive=False) + "station.addFeedback", trackToken="token", isPositive=False + ) def test_add_artist_bookmark(self): self.api.add_artist_bookmark("tt") self.transport.assert_called_with( - "bookmark.addArtistBookmark", trackToken="tt") + "bookmark.addArtistBookmark", trackToken="tt" + ) def test_add_song_bookmark(self): self.api.add_song_bookmark("tt") self.transport.assert_called_with( - "bookmark.addSongBookmark", trackToken="tt") + "bookmark.addSongBookmark", trackToken="tt" + ) def test_delete_song_bookmark(self): self.api.delete_song_bookmark("bt") self.transport.assert_called_with( - "bookmark.deleteSongBookmark", bookmarkToken="bt") + "bookmark.deleteSongBookmark", bookmarkToken="bt" + ) def test_delete_artist_bookmark(self): self.api.delete_artist_bookmark("bt") self.transport.assert_called_with( - "bookmark.deleteArtistBookmark", bookmarkToken="bt") + "bookmark.deleteArtistBookmark", bookmarkToken="bt" + ) def test_get_station_list_checksum(self): self.transport.return_value = {"checksum": "foo"} @@ -364,7 +406,8 @@ class TestAPIClientExhaustive(TestCase): self.transport.return_value = {"stations": []} self.assertIsInstance(self.api.get_station_list(), StationList) self.transport.assert_called_with( - "user.getStationList", includeStationArtUrl=True) + "user.getStationList", includeStationArtUrl=True + ) def test_get_bookmarks(self): self.transport.return_value = {} @@ -375,14 +418,22 @@ class TestAPIClientExhaustive(TestCase): self.transport.return_value = {} self.assertIsInstance(self.api.get_station("st"), Station) self.transport.assert_called_with( - "station.getStation", stationToken="st", - includeExtendedAttributes=True) + "station.getStation", + stationToken="st", + includeExtendedAttributes=True, + ) def test_search(self): self.transport.return_value = {} - self.assertIsInstance(self.api.search( - "text", include_near_matches=True, include_genre_stations=True), - SearchResult) + self.assertIsInstance( + self.api.search( + "text", include_near_matches=True, include_genre_stations=True + ), + SearchResult, + ) self.transport.assert_called_with( - "music.search", searchText="text", includeNearMatches=True, - includeGenreStations=True) + "music.search", + searchText="text", + includeNearMatches=True, + includeGenreStations=True, + ) diff --git a/tests/test_pandora/test_clientbuilder.py b/tests/test_pandora/test_clientbuilder.py index af0e219..17a4036 100644 --- a/tests/test_pandora/test_clientbuilder.py +++ b/tests/test_pandora/test_clientbuilder.py @@ -8,7 +8,6 @@ from pandora.transport import DEFAULT_API_HOST class TestTranslatingDict(TestCase): - class TestDict(cb.TranslatingDict): KEY_TRANSLATIONS = {"FOO": "BAR"} @@ -62,29 +61,32 @@ class TestTranslatingDict(TestCase): class TestSettingsDictBuilder(TestCase): - @classmethod def _build_minimal(self): - return cb.SettingsDictBuilder({ - "DECRYPTION_KEY": "blowfishkey", - "ENCRYPTION_KEY": "blowfishkey", - "PARTNER_USER": "user", - "PARTNER_PASSWORD": "pass", - "DEVICE": "dev", - }).build() + return cb.SettingsDictBuilder( + { + "DECRYPTION_KEY": "blowfishkey", + "ENCRYPTION_KEY": "blowfishkey", + "PARTNER_USER": "user", + "PARTNER_PASSWORD": "pass", + "DEVICE": "dev", + } + ).build() @classmethod def _build_maximal(self): - return cb.SettingsDictBuilder({ - "DECRYPTION_KEY": "blowfishkey", - "ENCRYPTION_KEY": "blowfishkey", - "PARTNER_USER": "user", - "PARTNER_PASSWORD": "pass", - "DEVICE": "dev", - "PROXY": "proxy.example.com", - "AUDIO_QUALITY": "high", - "API_HOST": "example.com", - }).build() + return cb.SettingsDictBuilder( + { + "DECRYPTION_KEY": "blowfishkey", + "ENCRYPTION_KEY": "blowfishkey", + "PARTNER_USER": "user", + "PARTNER_PASSWORD": "pass", + "DEVICE": "dev", + "PROXY": "proxy.example.com", + "AUDIO_QUALITY": "high", + "API_HOST": "example.com", + } + ).build() def test_building(self): client = TestSettingsDictBuilder._build_minimal() @@ -97,14 +99,15 @@ class TestSettingsDictBuilder(TestCase): self.assertEqual({}, client.transport._http.proxies) self.assertEqual(DEFAULT_API_HOST, client.transport.api_host) self.assertEqual( - APIClient.MED_AUDIO_QUALITY, client.default_audio_quality) + APIClient.MED_AUDIO_QUALITY, client.default_audio_quality + ) def test_validate_client(self): client = TestSettingsDictBuilder._build_maximal() expected_proxies = { - "http": "proxy.example.com", - "https": "proxy.example.com" - } + "http": "proxy.example.com", + "https": "proxy.example.com", + } self.assertIsNotNone(client.transport.cryptor.bf_in) self.assertIsNotNone(client.transport.cryptor.bf_out) @@ -119,7 +122,6 @@ class TestSettingsDictBuilder(TestCase): class TestFileBasedBuilder(TestCase): - class StubBuilder(cb.FileBasedClientBuilder): DEFAULT_CONFIG_FILE = "foo" @@ -170,7 +172,6 @@ class TestFileBasedBuilder(TestCase): class TestPydoraConfigFileBuilder(TestCase): - def test_cfg_to_dict(self): cfg = Mock() cfg.items = Mock(return_value=[("a", "b"), ("c", "d")]) @@ -184,38 +185,43 @@ class TestPydoraConfigFileBuilder(TestCase): path = os.path.join(os.path.dirname(__file__), "pydora.cfg") cfg = cb.PydoraConfigFileBuilder(path).parse_config() - self.assertDictEqual(cfg, { - "AUDIO_QUALITY": "test_quality", - "DECRYPTION_KEY": "test_decryption_key", - "DEVICE": "test_device", - "ENCRYPTION_KEY": "test_encryption_key", - "PARTNER_PASSWORD": "test_partner_password", - "PARTNER_USER": "test_partner_username", - "API_HOST": "test_host", - "USER": { - "USERNAME": "test_username", - "PASSWORD": "test_password", - } - }) + self.assertDictEqual( + cfg, + { + "AUDIO_QUALITY": "test_quality", + "DECRYPTION_KEY": "test_decryption_key", + "DEVICE": "test_device", + "ENCRYPTION_KEY": "test_encryption_key", + "PARTNER_PASSWORD": "test_partner_password", + "PARTNER_USER": "test_partner_username", + "API_HOST": "test_host", + "USER": { + "USERNAME": "test_username", + "PASSWORD": "test_password", + }, + }, + ) class TestPianobarConfigFileBuilder(TestCase): - def test_integration(self): path = os.path.join(os.path.dirname(__file__), "pianobar.cfg") cfg = cb.PianobarConfigFileBuilder(path).parse_config() - self.assertDictEqual(cfg, { - "AUDIO_QUALITY": "test_qualityQuality", - "DECRYPTION_KEY": "test_decryption_key", - "DEVICE": "test_device", - "ENCRYPTION_KEY": "test_encryption_key", - "PARTNER_PASSWORD": "test_partner_password", - "PARTNER_USER": "test_partner_username", - "API_HOST": "test_host/services/json/", - "PROXY": "test_proxy", - "USER": { - "USERNAME": "test_username", - "PASSWORD": "test_password", - } - }) + self.assertDictEqual( + cfg, + { + "AUDIO_QUALITY": "test_qualityQuality", + "DECRYPTION_KEY": "test_decryption_key", + "DEVICE": "test_device", + "ENCRYPTION_KEY": "test_encryption_key", + "PARTNER_PASSWORD": "test_partner_password", + "PARTNER_USER": "test_partner_username", + "API_HOST": "test_host/services/json/", + "PROXY": "test_proxy", + "USER": { + "USERNAME": "test_username", + "PASSWORD": "test_password", + }, + }, + ) diff --git a/tests/test_pandora/test_errors.py b/tests/test_pandora/test_errors.py index 778771a..d9df41f 100644 --- a/tests/test_pandora/test_errors.py +++ b/tests/test_pandora/test_errors.py @@ -4,7 +4,6 @@ from pandora.errors import InternalServerError, PandoraException class TestPandoraExceptionConstructionFromErrorCode(TestCase): - def test_it_returns_specific_error_class_if_possible(self): error = PandoraException.from_code(0, "Test Message") self.assertIsInstance(error, InternalServerError) diff --git a/tests/test_pandora/test_models.py b/tests/test_pandora/test_models.py index cb650df..16307b4 100644 --- a/tests/test_pandora/test_models.py +++ b/tests/test_pandora/test_models.py @@ -14,7 +14,6 @@ import pandora.models.playlist as plm class TestField(TestCase): - def test_defaults(self): field = m.Field("name") @@ -24,7 +23,6 @@ class TestField(TestCase): class TestModelMetaClass(TestCase): - class TestModel(metaclass=m.ModelMetaClass): foo = "bar" @@ -40,7 +38,6 @@ class TestModelMetaClass(TestCase): class TestDateField(TestCase): - class SampleModel(m.PandoraModel): date_field = m.DateField("foo") @@ -57,34 +54,26 @@ class TestDateField(TestCase): class TestAdditionalUrlField(TestCase): - def test_single_url(self): - dummy_data = { - '_paramAdditionalUrls': ['foo'] - } + dummy_data = {"_paramAdditionalUrls": ["foo"]} field = plm.AdditionalUrlField("additionalAudioUrl") - ret = field.formatter(None, dummy_data, 'test') + ret = field.formatter(None, dummy_data, "test") - self.assertEqual(ret, {'foo': 'test'}) + self.assertEqual(ret, {"foo": "test"}) def test_multiple_urls(self): - dummy_data = { - '_paramAdditionalUrls': [ - 'abc', - 'def', - ] - } + dummy_data = {"_paramAdditionalUrls": ["abc", "def",]} field = plm.AdditionalUrlField("additionalAudioUrl") - ret = field.formatter(None, dummy_data, ['foo', 'bar']) + ret = field.formatter(None, dummy_data, ["foo", "bar"]) expected = { - 'abc': 'foo', - 'def': 'bar', - } + "abc": "foo", + "def": "bar", + } self.assertEqual(ret, expected) @@ -99,7 +88,6 @@ class TestPandoraModel(TestCase): } class TestModel(m.PandoraModel): - class SubModel(m.PandoraModel): field1 = m.Field("field1") @@ -116,7 +104,6 @@ class TestPandoraModel(TestCase): pass class ExtraReprModel(m.PandoraModel): - def __repr__(self): return self._base_repr("Foo") @@ -153,9 +140,11 @@ class TestPandoraModel(TestCase): self.assertEqual("a string", result[1].field1) def test_repr(self): - expected = ("TestModel(field1='a string', field2=['test2'], field3=42," - " field4=SubModel(field1='foo'), " - "field5=[SubModel(field1='foo'), SubModel(field1='bar')])") + expected = ( + "TestModel(field1='a string', field2=['test2'], field3=42," + " field4=SubModel(field1='foo'), " + "field5=[SubModel(field1='foo'), SubModel(field1='bar')])" + ) result = self.TestModel.from_json(None, self.JSON_DATA) self.assertEqual(expected, repr(result)) @@ -179,12 +168,12 @@ class TestSubModel(m.PandoraModel): class TestPandoraListModel(TestCase): JSON_DATA = { - "field1": 42, - "field2": [ - {"idx": "foo", "fieldS1": "Foo"}, - {"idx": "bar", "fieldS1": "Bar"}, - ] - } + "field1": 42, + "field2": [ + {"idx": "foo", "fieldS1": "Foo"}, + {"idx": "bar", "fieldS1": "Bar"}, + ], + } class TestModel(m.PandoraListModel): @@ -204,8 +193,10 @@ class TestPandoraListModel(TestCase): self.assertEqual("Bar", self.result[1].fieldS1) def test_repr(self): - expected = ("TestModel(field1=42, [TestSubModel(fieldS1='Foo', " - "idx='foo'), TestSubModel(fieldS1='Bar', idx='bar')])") + expected = ( + "TestModel(field1=42, [TestSubModel(fieldS1='Foo', " + "idx='foo'), TestSubModel(fieldS1='Bar', idx='bar')])" + ) self.assertEqual(expected, repr(self.result)) def test_indexed_model(self): @@ -232,17 +223,17 @@ class TestPandoraListModel(TestCase): class TestPandoraDictListModel(TestCase): JSON_DATA = { - "field1": 42, - "fieldD1": [ - { - "dictKey": "Foobear", - "listKey": [ - {"idx": "foo", "fieldS1": "Foo"}, - {"idx": "bar", "fieldS1": "Bar"}, - ] - } - ] - } + "field1": 42, + "fieldD1": [ + { + "dictKey": "Foobear", + "listKey": [ + {"idx": "foo", "fieldS1": "Foo"}, + {"idx": "bar", "fieldS1": "Bar"}, + ], + } + ], + } class TestModel(m.PandoraDictListModel): @@ -266,9 +257,11 @@ class TestPandoraDictListModel(TestCase): self.assertEqual("bar", self.result["Foobear"][1].idx) def test_repr(self): - expected = ("TestModel(field1=42, {'Foobear': " - "[TestSubModel(fieldS1='Foo', idx='foo'), " - "TestSubModel(fieldS1='Bar', idx='bar')]})") + expected = ( + "TestModel(field1=42, {'Foobear': " + "[TestSubModel(fieldS1='Foo', idx='foo'), " + "TestSubModel(fieldS1='Bar', idx='bar')]})" + ) self.assertEqual(expected, repr(self.result)) @@ -320,7 +313,6 @@ class TestPlaylistItemModel(TestCase): class TestPlaylistModel(TestCase): - def setUp(self): self.client = Mock() self.playlist = plm.PlaylistModel(self.client) @@ -354,47 +346,47 @@ class TestPlaylistModel(TestCase): class TestAdItem(TestCase): JSON_DATA = { - 'audioUrlMap': { - 'mediumQuality': { - 'audioUrl': 'med_url_mock', - 'bitrate': '64', - 'protocol': 'http', - 'encoding': 'aacplus' + "audioUrlMap": { + "mediumQuality": { + "audioUrl": "med_url_mock", + "bitrate": "64", + "protocol": "http", + "encoding": "aacplus", }, - 'highQuality': { - 'audioUrl': 'high_url_mock', - 'bitrate': '64', - 'protocol': 'http', - 'encoding': 'aacplus' + "highQuality": { + "audioUrl": "high_url_mock", + "bitrate": "64", + "protocol": "http", + "encoding": "aacplus", + }, + "lowQuality": { + "audioUrl": "low_url_mock", + "bitrate": "32", + "protocol": "http", + "encoding": "aacplus", }, - 'lowQuality': { - 'audioUrl': 'low_url_mock', - 'bitrate': '32', - 'protocol': 'http', - 'encoding': 'aacplus' - } }, - 'clickThroughUrl': 'click_url_mock', - 'imageUrl': 'img_url_mock', - 'companyName': '', - 'title': '', - 'trackGain': '0.0', - 'adTrackingTokens': ['token_1_mock', 'token_2_mock'] + "clickThroughUrl": "click_url_mock", + "imageUrl": "img_url_mock", + "companyName": "", + "title": "", + "trackGain": "0.0", + "adTrackingTokens": ["token_1_mock", "token_2_mock"], } def setUp(self): api_client_mock = Mock(spec=APIClient) api_client_mock.default_audio_quality = APIClient.HIGH_AUDIO_QUALITY self.result = am.AdItem.from_json(api_client_mock, self.JSON_DATA) - self.result.station_id = 'station_id_mock' - self.result.ad_token = 'token_mock' + self.result.station_id = "station_id_mock" + self.result.ad_token = "token_mock" def test_is_ad_is_true(self): assert self.result.is_ad is True def test_register_ad(self): self.result._api_client.register_ad = Mock() - self.result.register_ad('id_mock') + self.result.register_ad("id_mock") assert self.result._api_client.register_ad.called @@ -403,12 +395,12 @@ class TestAdItem(TestCase): self.result.tracking_tokens = [] self.result._api_client.register_ad = Mock(spec=am.AdItem) - self.result.register_ad('id_mock') + self.result.register_ad("id_mock") assert self.result._api_client.register_ad.called def test_prepare_playback(self): - with patch.object(plm.PlaylistModel, 'prepare_playback') as super_mock: + with patch.object(plm.PlaylistModel, "prepare_playback") as super_mock: self.result.register_ad = Mock() self.result.prepare_playback() @@ -416,21 +408,26 @@ class TestAdItem(TestCase): assert super_mock.called def test_prepare_playback_raises_paramater_missing(self): - with patch.object(plm.PlaylistModel, 'prepare_playback') as super_mock: + with patch.object(plm.PlaylistModel, "prepare_playback") as super_mock: - self.result.register_ad = Mock(side_effect=ParameterMissing( - 'No ad tracking tokens provided for registration.') - ) + self.result.register_ad = Mock( + side_effect=ParameterMissing( + "No ad tracking tokens provided for registration." + ) + ) self.assertRaises(ParameterMissing, self.result.prepare_playback) assert self.result.register_ad.called assert not super_mock.called def test_prepare_playback_handles_paramater_missing_if_no_tokens(self): - with patch.object(plm.PlaylistModel, 'prepare_playback') as super_mock: + with patch.object(plm.PlaylistModel, "prepare_playback") as super_mock: self.result.tracking_tokens = [] - self.result.register_ad = Mock(side_effect=ParameterMissing( - 'No ad tracking tokens provided for registration.')) + self.result.register_ad = Mock( + side_effect=ParameterMissing( + "No ad tracking tokens provided for registration." + ) + ) self.result.prepare_playback() assert self.result.register_ad.called assert super_mock.called @@ -449,43 +446,45 @@ class TestSearchResultItem(TestCase): "artistName": "artist_name_mock", "musicToken": "S0000000", "songName": "song_name_mock", - "score": 100 + "score": 100, } ARTIST_JSON_DATA = { "artistName": "artist_name_mock", "musicToken": "R0000000", "likelyMatch": False, - "score": 100 + "score": 100, } COMPOSER_JSON_DATA = { "artistName": "composer_name_mock", "musicToken": "C0000000", "likelyMatch": False, - "score": 100 + "score": 100, } GENRE_JSON_DATA = { "stationName": "station_name_mock", "musicToken": "G0000000", - "score": 100 + "score": 100, } UNKNOWN_JSON_DATA = { "stationName": "unknown_name_mock", "musicToken": "U0000000", - "score": 100 + "score": 100, } def setUp(self): self.api_client_mock = Mock(spec=APIClient) - self.api_client_mock.default_audio_quality = \ + self.api_client_mock.default_audio_quality = ( APIClient.HIGH_AUDIO_QUALITY + ) def test_is_song(self): result = sm.SearchResultItem.from_json( - self.api_client_mock, self.SONG_JSON_DATA) + self.api_client_mock, self.SONG_JSON_DATA + ) assert result.is_song assert not result.is_artist assert not result.is_composer @@ -493,7 +492,8 @@ class TestSearchResultItem(TestCase): def test_is_artist(self): result = sm.SearchResultItem.from_json( - self.api_client_mock, self.ARTIST_JSON_DATA) + self.api_client_mock, self.ARTIST_JSON_DATA + ) assert not result.is_song assert result.is_artist assert not result.is_composer @@ -501,7 +501,8 @@ class TestSearchResultItem(TestCase): def test_is_composer(self): result = sm.SearchResultItem.from_json( - self.api_client_mock, self.COMPOSER_JSON_DATA) + self.api_client_mock, self.COMPOSER_JSON_DATA + ) assert not result.is_song assert not result.is_artist assert result.is_composer @@ -509,7 +510,8 @@ class TestSearchResultItem(TestCase): def test_is_genre_station(self): result = sm.SearchResultItem.from_json( - self.api_client_mock, self.GENRE_JSON_DATA) + self.api_client_mock, self.GENRE_JSON_DATA + ) assert not result.is_song assert not result.is_artist assert not result.is_composer @@ -518,7 +520,8 @@ class TestSearchResultItem(TestCase): def test_fails_if_unknown(self): with self.assertRaises(NotImplementedError): sm.SearchResultItem.from_json( - self.api_client_mock, self.UNKNOWN_JSON_DATA) + self.api_client_mock, self.UNKNOWN_JSON_DATA + ) def test_interface(self): result = sm.SearchResultItem(self.api_client_mock) @@ -533,44 +536,51 @@ class TestArtistSearchResultItem(TestCase): "artistName": "artist_name_mock", "musicToken": "R0000000", "likelyMatch": False, - "score": 100 + "score": 100, } COMPOSER_JSON_DATA = { "artistName": "composer_name_mock", "musicToken": "C0000000", "likelyMatch": False, - "score": 100 + "score": 100, } def setUp(self): self.api_client_mock = Mock(spec=APIClient) - self.api_client_mock.default_audio_quality = \ + self.api_client_mock.default_audio_quality = ( APIClient.HIGH_AUDIO_QUALITY + ) def test_repr(self): result = sm.SearchResultItem.from_json( - self.api_client_mock, self.ARTIST_JSON_DATA) + self.api_client_mock, self.ARTIST_JSON_DATA + ) expected = ( "ArtistSearchResultItem(artist='artist_name_mock', " - "likely_match=False, score=100, token='R0000000')") + "likely_match=False, score=100, token='R0000000')" + ) self.assertEqual(expected, repr(result)) result = sm.SearchResultItem.from_json( - self.api_client_mock, self.COMPOSER_JSON_DATA) + self.api_client_mock, self.COMPOSER_JSON_DATA + ) expected = ( "ArtistSearchResultItem(artist='composer_name_mock', " - "likely_match=False, score=100, token='C0000000')") + "likely_match=False, score=100, token='C0000000')" + ) self.assertEqual(expected, repr(result)) def test_create_station(self): result = sm.SearchResultItem.from_json( - self.api_client_mock, self.ARTIST_JSON_DATA) + self.api_client_mock, self.ARTIST_JSON_DATA + ) result._api_client.create_station = Mock() result.create_station() result._api_client.create_station.assert_called_with( - artist_token=result.token) + artist_token=result.token + ) class TestSongSearchResultItem(TestCase): @@ -579,30 +589,35 @@ class TestSongSearchResultItem(TestCase): "artistName": "artist_name_mock", "musicToken": "S0000000", "songName": "song_name_mock", - "score": 100 + "score": 100, } def setUp(self): self.api_client_mock = Mock(spec=APIClient) - self.api_client_mock.default_audio_quality = \ + self.api_client_mock.default_audio_quality = ( APIClient.HIGH_AUDIO_QUALITY + ) def test_repr(self): result = sm.SearchResultItem.from_json( - self.api_client_mock, self.SONG_JSON_DATA) + self.api_client_mock, self.SONG_JSON_DATA + ) expected = ( "SongSearchResultItem(artist='artist_name_mock', score=100, " - "song_name='song_name_mock', token='S0000000')") + "song_name='song_name_mock', token='S0000000')" + ) self.assertEqual(expected, repr(result)) def test_create_station(self): result = sm.SearchResultItem.from_json( - self.api_client_mock, self.SONG_JSON_DATA) + self.api_client_mock, self.SONG_JSON_DATA + ) result._api_client.create_station = Mock() result.create_station() result._api_client.create_station.assert_called_with( - track_token=result.token) + track_token=result.token + ) class TestGenreStationSearchResultItem(TestCase): @@ -610,61 +625,69 @@ class TestGenreStationSearchResultItem(TestCase): GENRE_JSON_DATA = { "stationName": "station_name_mock", "musicToken": "G0000000", - "score": 100 + "score": 100, } def setUp(self): self.api_client_mock = Mock(spec=APIClient) - self.api_client_mock.default_audio_quality = \ + self.api_client_mock.default_audio_quality = ( APIClient.HIGH_AUDIO_QUALITY + ) def test_repr(self): result = sm.SearchResultItem.from_json( - self.api_client_mock, self.GENRE_JSON_DATA) + self.api_client_mock, self.GENRE_JSON_DATA + ) expected = ( "GenreStationSearchResultItem(score=100, " - "station_name='station_name_mock', token='G0000000')") + "station_name='station_name_mock', token='G0000000')" + ) self.assertEqual(expected, repr(result)) def test_create_station(self): result = sm.SearchResultItem.from_json( - self.api_client_mock, self.GENRE_JSON_DATA) + self.api_client_mock, self.GENRE_JSON_DATA + ) result._api_client.create_station = Mock() result.create_station() result._api_client.create_station.assert_called_with( - search_token=result.token) + search_token=result.token + ) class TestSearchResult(TestCase): JSON_DATA = { - 'nearMatchesAvailable': True, - 'explanation': '', - 'songs': [{ - 'artistName': 'song_artist_mock', - 'musicToken': 'S0000000', - 'songName': 'song_name_mock', - 'score': 100 - }], - 'artists': [{ - 'artistName': 'artist_mock', - 'musicToken': 'R000000', - 'likelyMatch': False, - 'score': 80 - }], - 'genreStations': [{ - 'musicToken': 'G0000', - 'stationName': 'station_mock', - 'score': 50 - }] + "nearMatchesAvailable": True, + "explanation": "", + "songs": [ + { + "artistName": "song_artist_mock", + "musicToken": "S0000000", + "songName": "song_name_mock", + "score": 100, + } + ], + "artists": [ + { + "artistName": "artist_mock", + "musicToken": "R000000", + "likelyMatch": False, + "score": 80, + } + ], + "genreStations": [ + {"musicToken": "G0000", "stationName": "station_mock", "score": 50} + ], } def setUp(self): api_client_mock = Mock(spec=APIClient) api_client_mock.default_audio_quality = APIClient.HIGH_AUDIO_QUALITY self.result = sm.SearchResult.from_json( - api_client_mock, self.JSON_DATA) + api_client_mock, self.JSON_DATA + ) def test_repr(self): expected = ( @@ -676,7 +699,8 @@ class TestSearchResult(TestCase): "nearest_matches_available=True, " "songs=[SongSearchResultItem(artist='song_artist_mock', " "score=100, song_name='song_name_mock', " - "token='S0000000')])") + "token='S0000000')])" + ) self.assertEqual(expected, repr(self.result)) @@ -684,9 +708,7 @@ class TestGenreStationList(TestCase): TEST_DATA = { "checksum": "bar", - "categories": [ - {"categoryName": "foo", "stations": []}, - ] + "categories": [{"categoryName": "foo", "stations": []},], } def test_has_changed(self): @@ -706,7 +728,8 @@ class TestGenreStation(TestCase): genre_station = stm.GenreStation.from_json(api_client, self.TEST_DATA) with self.assertRaisesRegex( - NotImplementedError, "Genre stations do not have playlists.*"): + NotImplementedError, "Genre stations do not have playlists.*" + ): genre_station.get_playlist() @@ -750,7 +773,6 @@ class TestBookmark(TestCase): class TestPandoraType(TestCase): - def test_it_can_be_built_from_a_model(self): pt = plm.PandoraType.from_model(None, "TR") self.assertIs(plm.PandoraType.TRACK, pt) @@ -765,7 +787,6 @@ class TestPandoraType(TestCase): class TestSyntheticField(TestCase): - def test_interface(self): sf = m.SyntheticField(field="foo") diff --git a/tests/test_pandora/test_transport.py b/tests/test_pandora/test_transport.py index 851fa06..28ca6b2 100644 --- a/tests/test_pandora/test_transport.py +++ b/tests/test_pandora/test_transport.py @@ -16,12 +16,12 @@ class SysCallError(Exception): class TestTransport(TestCase): - def test_test_url_should_return_true_if_request_okay(self): transport = t.APITransport(Mock()) transport._http = Mock() transport._http.head.return_value = Mock( - status_code=requests.codes.not_found) + status_code=requests.codes.not_found + ) self.assertFalse(transport.test_url("foo")) @@ -34,7 +34,8 @@ class TestTransport(TestCase): time.sleep = Mock() client.transport._make_http_request = Mock( - side_effect=SysCallError("error_mock")) + side_effect=SysCallError("error_mock") + ) client.transport._start_request = Mock() client("method") @@ -48,7 +49,8 @@ class TestTransport(TestCase): time.sleep = Mock() client.transport._make_http_request = Mock( - side_effect=PandoraException("error_mock")) + side_effect=PandoraException("error_mock") + ) client.transport._start_request = Mock() client("method") @@ -62,7 +64,8 @@ class TestTransport(TestCase): time.sleep = Mock() client.transport._make_http_request = Mock( - side_effect=InvalidAuthToken("error_mock")) + side_effect=InvalidAuthToken("error_mock") + ) client.transport._start_request = Mock() client._authenticate = Mock() @@ -82,11 +85,11 @@ class TestTransport(TestCase): transport._http.post.return_value = http_result self.assertEqual( - "bar", transport(t.APITransport.NO_ENCRYPT[0], foo="bar")) + "bar", transport(t.APITransport.NO_ENCRYPT[0], foo="bar") + ) class TestTransportSetters(TestCase): - def setUp(self): self.cryptor = Mock() self.transport = t.APITransport(self.cryptor) @@ -94,27 +97,29 @@ class TestTransportSetters(TestCase): def test_set_partner(self): self.cryptor.decrypt_sync_time.return_value = 456 - self.transport.set_partner({ - "syncTime": "123", - "partnerAuthToken": "partner_auth_token", - "partnerId": "partner_id", - }) + self.transport.set_partner( + { + "syncTime": "123", + "partnerAuthToken": "partner_auth_token", + "partnerId": "partner_id", + } + ) self.cryptor.decrypt_sync_time.assert_called_with("123") self.assertEqual("partner_auth_token", self.transport.auth_token) self.assertEqual("partner_id", self.transport.partner_id) self.assertEqual( - "partner_auth_token", self.transport.partner_auth_token) + "partner_auth_token", self.transport.partner_auth_token + ) self.transport.start_time = 10 with patch.object(time, "time", return_value=30): self.assertEqual(476, self.transport.sync_time) def test_set_user(self): - self.transport.set_user({ - "userId": "user", - "userAuthToken": "auth", - }) + self.transport.set_user( + {"userId": "user", "userAuthToken": "auth",} + ) self.assertEqual("user", self.transport.user_id) self.assertEqual("auth", self.transport.user_auth_token) @@ -126,7 +131,6 @@ class TestTransportSetters(TestCase): class TestDelayExponential(TestCase): - def test_fixed_delay(self): self.assertEqual(8, t.delay_exponential(2, 2, 3)) @@ -143,7 +147,6 @@ class TestDelayExponential(TestCase): class TestRetries(TestCase): - def test_no_retries_returns_none(self): @t.retries(0) def foo(): @@ -178,7 +181,6 @@ class TestParseResponse(TestCase): class TestTransportRequestPrep(TestCase): - def setUp(self): self.cryptor = Mock() self.transport = t.APITransport(self.cryptor) @@ -207,7 +209,8 @@ class TestTransportRequestPrep(TestCase): self.transport._http = http res = self.transport._make_http_request( - "/url", b"data", {"a": None, "b": "c"}) + "/url", b"data", {"a": None, "b": "c"} + ) http.post.assert_called_with("/url", data=b"data", params={"b": "c"}) retval.raise_for_status.assert_called_with() @@ -237,7 +240,8 @@ class TestTransportRequestPrep(TestCase): with patch.object(time, "time", return_value=20): val = self.transport._build_data( - t.APITransport.NO_ENCRYPT[0], {"a": "b", "c": None}) + t.APITransport.NO_ENCRYPT[0], {"a": "b", "c": None} + ) val = json.loads(val) self.assertEqual("b", val["a"]) @@ -247,7 +251,6 @@ class TestTransportRequestPrep(TestCase): # All Cryptor implementations must pass these test cases unmodified class CommonCryptorTestCases: - def test_decrypt_invalid_padding(self): with self.assertRaises(ValueError): data = b"12345678\x00" @@ -267,7 +270,6 @@ class CommonCryptorTestCases: class TestPurePythonBlowfishCryptor(TestCase, CommonCryptorTestCases): - def setUp(self): # Ugh... blowfish can't even be *imported* in python2 if not t.blowfish: @@ -288,7 +290,6 @@ class TestEncryptor(TestCase): ENCODED_TIME = "31353037343131313539" class NoopCrypto: - def __init__(self, key): pass @@ -303,14 +304,17 @@ class TestEncryptor(TestCase): def test_decrypt(self): self.assertEqual( - {"foo": "bar"}, self.cryptor.decrypt(self.ENCODED_JSON)) + {"foo": "bar"}, self.cryptor.decrypt(self.ENCODED_JSON) + ) def test_encrypt(self): self.assertEqual( self.ENCODED_JSON.encode("ascii"), - self.cryptor.encrypt(self.UNENCODED_JSON)) + self.cryptor.encrypt(self.UNENCODED_JSON), + ) def test_decrypt_sync_time(self): self.assertEqual( self.EXPECTED_TIME, - self.cryptor.decrypt_sync_time(self.ENCODED_TIME)) + self.cryptor.decrypt_sync_time(self.ENCODED_TIME), + ) diff --git a/tests/test_pydora/test_utils.py b/tests/test_pydora/test_utils.py index fc33a40..9d3bc78 100644 --- a/tests/test_pydora/test_utils.py +++ b/tests/test_pydora/test_utils.py @@ -10,23 +10,26 @@ from pydora.utils import iterate_forever class TestIterateForever(TestCase): - def setUp(self): self.transport = Mock(side_effect=[InvalidAuthToken(), None]) self.client = APIClient(self.transport, None, None, None, None) self.client._authenticate = Mock() def test_handle_missing_params_exception_due_to_missing_ad_tokens(self): - with patch.object(APIClient, 'get_playlist') as get_playlist_mock: + with patch.object(APIClient, "get_playlist") as get_playlist_mock: admock = patch.object( - APIClient, 'register_ad', - side_effect=ParameterMissing("ParameterMissing")) + APIClient, + "register_ad", + side_effect=ParameterMissing("ParameterMissing"), + ) with admock: station = Station.from_json( - self.client, {'stationToken': 'token_mock'}) + self.client, {"stationToken": "token_mock"} + ) ad_mock = AdItem.from_json( - self.client, {'station_id': 'id_mock'}) + self.client, {"station_id": "id_mock"} + ) get_playlist_mock.return_value = iter([ad_mock]) station_iter = iterate_forever(station.get_playlist) @@ -36,15 +39,19 @@ class TestIterateForever(TestCase): def test_reraise_missing_params_exception(self): plmock = patch.object( - APIClient, 'get_playlist', - side_effect=ParameterMissing("ParameterMissing")) + APIClient, + "get_playlist", + side_effect=ParameterMissing("ParameterMissing"), + ) with plmock as get_playlist_mock: with self.assertRaises(ParameterMissing): station = Station.from_json( - self.client, {'stationToken': 'token_mock'}) + self.client, {"stationToken": "token_mock"} + ) track_mock = PlaylistItem.from_json( - self.client, {'token': 'token_mock'}) + self.client, {"token": "token_mock"} + ) get_playlist_mock.return_value = iter([track_mock]) station_iter = iterate_forever(station.get_playlist) -- cgit v1.2.3