From 954816d5c27fd03e2896e7d79b7cb1175bb037ce Mon Sep 17 00:00:00 2001 From: Mike Crute Date: Sat, 7 Oct 2017 04:34:52 +0000 Subject: Refactor audio URL extraction This generalizes the audio URL extraction logic into a synthesized field that can be extracted from the PlaylistModel class. It also removes a few transmogrifiers that are no longer needed in the general case. Technically this breaks a publicly exposed API within playlists models but it was always considered an implementation detail so nobody should be relying on it. --- pandora/models/__init__.py | 23 +++++++++++++- pandora/models/pandora.py | 75 +++++++--------------------------------------- 2 files changed, 33 insertions(+), 65 deletions(-) diff --git a/pandora/models/__init__.py b/pandora/models/__init__.py index d4e3b87..6a14f45 100644 --- a/pandora/models/__init__.py +++ b/pandora/models/__init__.py @@ -12,6 +12,22 @@ class Field(namedtuple("Field", ["field", "default", "formatter"])): return super(Field, cls).__new__(cls, field, default, formatter) +class SyntheticField(namedtuple("SyntheticField", ["field"])): + """Field That Requires Synthesis + + Synthetic fields may exist in the data but generally do not and require + additional synthesis to arrive ate a sane value. Subclasses must define + a formatter method that receives an API client, field name, and full data + payload. + """ + + default = None + + @staticmethod + def formatter(api_client, field, data): # pragma: no cover + raise NotImplementedError + + class ModelMetaClass(type): def __new__(cls, name, parents, dct): @@ -22,7 +38,7 @@ class ModelMetaClass(type): if key.startswith("__"): continue - if isinstance(val, Field): + if isinstance(val, Field) or isinstance(val, SyntheticField): fields[key] = val del new_dct[key] @@ -57,6 +73,11 @@ class PandoraModel(with_metaclass(ModelMetaClass, object)): for key, value in instance.__class__._fields.items(): newval = data.get(value.field, value.default) + if isinstance(value, SyntheticField): + newval = value.formatter(api_client, value.field, data, newval) + setattr(instance, key, newval) + continue + if newval and value.formatter: newval = value.formatter(api_client, newval) diff --git a/pandora/models/pandora.py b/pandora/models/pandora.py index 455bcf3..d7d2bf1 100644 --- a/pandora/models/pandora.py +++ b/pandora/models/pandora.py @@ -1,5 +1,6 @@ from ..client import BaseAPIClient from ..errors import ParameterMissing +from . import SyntheticField from . import Field, PandoraModel, PandoraListModel, PandoraDictListModel @@ -51,42 +52,15 @@ class StationList(PandoraListModel): return checksum != self.checksum -class PlaylistModel(PandoraModel): - - @classmethod - def from_json(cls, api_client, data): - self = cls(api_client) - - for key, value in cls._fields.items(): - newval = data.get(value.field, value.default) - - if value.field == "audioUrl" and newval is None: - newval = cls.get_audio_url( - data, api_client.default_audio_quality) - - if value.field == "bitrate" and newval is None: - newval = cls.get_audio_bitrate( - data, api_client.default_audio_quality) +class AudioField(SyntheticField): - if value.field == "encoding" and newval is None: - newval = cls.get_audio_encoding( - data, api_client.default_audio_quality) - - if newval and value.formatter: - newval = value.formatter(newval) - - setattr(self, key, newval) - - return self - - @classmethod - def get_audio_field(cls, data, field, preferred_quality): + @staticmethod + def formatter(api_client, field, data, value): """Get audio-related fields Try to find fields for the audio url for specified preferred quality level, or next-lowest available quality url otherwise. """ - audio_url = None url_map = data.get("audioUrlMap") audio_url = data.get("audioUrl") @@ -103,8 +77,7 @@ class PlaylistModel(PandoraModel): "encoding": "aacplus", } } - # No audio url available (e.g. ad tokens) - elif not url_map: + elif not url_map: # No audio url available (e.g. ad tokens) return None valid_audio_formats = [BaseAPIClient.HIGH_AUDIO_QUALITY, @@ -115,6 +88,7 @@ class PlaylistModel(PandoraModel): # from the beginning of the list if nothing is found. Ensures that the # bitrate used will always be the same or lower quality than was # specified to prevent audio from skipping for slow connections. + preferred_quality = api_client.default_audio_quality if preferred_quality in valid_audio_formats: i = valid_audio_formats.index(preferred_quality) valid_audio_formats = valid_audio_formats[i:] @@ -127,35 +101,8 @@ class PlaylistModel(PandoraModel): return audio_url[field] if audio_url else None - @classmethod - def get_audio_url(cls, data, - preferred_quality=BaseAPIClient.MED_AUDIO_QUALITY): - """Get audio url - - Try to find audio url for specified preferred quality level, or - next-lowest available quality url otherwise. - """ - return cls.get_audio_field(data, "audioUrl", preferred_quality) - - @classmethod - def get_audio_bitrate(cls, data, - preferred_quality=BaseAPIClient.MED_AUDIO_QUALITY): - """Get audio bitrate - - Try to find bitrate of audio url for specified preferred quality level, - or next-lowest available quality url otherwise. - """ - return cls.get_audio_field(data, "bitrate", preferred_quality) - - @classmethod - def get_audio_encoding(cls, data, - preferred_quality=BaseAPIClient.MED_AUDIO_QUALITY): - """Get audio encoding - Try to find encoding of audio url for specified preferred quality - level, or next-lowest available quality url otherwise. - """ - return cls.get_audio_field(data, "encoding", preferred_quality) +class PlaylistModel(PandoraModel): def get_is_playable(self): if not self.audio_url: @@ -195,9 +142,9 @@ class PlaylistItem(PlaylistModel): track_gain = Field("trackGain") track_length = Field("trackLength") track_token = Field("trackToken") - audio_url = Field("audioUrl") - bitrate = Field("bitrate") - encoding = Field("encoding") + audio_url = AudioField("audioUrl") + bitrate = AudioField("bitrate") + encoding = AudioField("encoding") album_art_url = Field("albumArtUrl") allow_feedback = Field("allowFeedback") station_id = Field("stationId") @@ -245,7 +192,7 @@ class AdItem(PlaylistModel): title = Field("title") company_name = Field("companyName") tracking_tokens = Field("adTrackingTokens") - audio_url = Field("audioUrl") + audio_url = AudioField("audioUrl") image_url = Field("imageUrl") click_through_url = Field("clickThroughUrl") station_id = None -- cgit v1.2.3