Source code for itunes.base

# -*- coding: utf-8 -*-
"""This module contains base data models for the rest of the iTunes store API
"""
import requests
import sys
from cachecontrol import CacheControl
from datetime import datetime
from itunes import HOST_NAME
__all__ = ['TS_FORMAT', 'SESSION', 'ITunesException', 'BaseObject', 'Resource',
           'NoResultsFoundException', 'Lookup', 'Artist', 'Album', 'Track',
           'Audiobook', 'Software', 'TVEpisode', 'lookup']
#: iTunes API Timestamp format
TS_FORMAT = '%Y-%m-%dT%H:%M:%S'

#: Globally accessible cache-enabled requests session
SESSION = CacheControl(requests.session())


[docs]class ITunesException(Exception): """Base iTunes request exception""" def __init__(self, message): self.message = message def __str__(self): return '{type}: {msg}'.format(type=self.__class__.__name__, msg=self.message)
[docs]class NoResultsFoundException(ITunesException): """iTunes error for when no results are returned from a Lookup""" def __init__(self): super(self.__class__, self).__init__('No Results Found')
[docs]class BaseObject(object): """Base object for representing an iTunes request""" resource = None def __init__(self, **kwargs): self._search_terms = {k: v for (k, v) in kwargs.items() if v is not None} self.json = None self.num_results = None @property def url(self): """The url pointing at the resource defined by the implementing object """ return '{host}{resource}'.format(host=HOST_NAME, resource=self.resource)
[docs] def get(self): """Execute an HTTP GET against the iTunes API and construct an appropriate assortment of :class:`Resource`'s based on the response """ response = SESSION.get(self.url, params=self._search_terms) self.json = response.json() if 'errorMessage' in self.json: raise ITunesException(self.json['errorMessage']) self.num_results = self.json['resultCount'] l = [] for json in self.json['results']: typ = None if 'wrapperType' in json: typ = json['wrapperType'] elif 'kind' in json: typ = json['kind'] if typ == 'artist': id_ = json['artistId'] item = Artist(id_) elif typ == 'collection': id_ = json['collectionId'] item = Album(id_) elif typ == 'track': id_ = json['trackId'] if 'kind' in json: kind = json['kind'] if kind == 'tv-episode': item = TVEpisode(id_) else: item = Track(id_) else: item = Track(id_) elif typ == 'audiobook': id_ = json['collectionId'] item = Audiobook(id_) elif typ == 'software': id_ = json['trackId'] item = Software(id_) else: if 'collectionId' in json: id_ = json['collectionId'] elif 'artistId' in json: id_ = json['artistId'] item = Resource(id_) item._set(json) l.append(item) return l
[docs]class Resource(object): """Base class for the various types of Resources returned by the iTunes Store API """ def __init__(self, id): self.id = id self.name = None self.url = None self._release_date = None self.artwork = dict() def _set(self, json): """Construct this resource based on the provided JSON data""" self.json = json if 'kind' in json: self.type = json['kind'] else: self.type = json['wrapperType'] # Resource information self.genre = json.get('primaryGenreName', None) self.release_date_raw = json.get('releaseDate', '') self.country_store = json.get('country', None) self._set_artwork(json) self._set_url(json) @property def release_date(self): """Accessor for a :class:`datetime.datetime` representation of the release date for this :class:`Resource` """ if not self.release_date_raw: return None if self._release_date is None: rd = self.release_date_raw.split('Z')[0] self._release_date = datetime.strptime(rd, TS_FORMAT) return self._release_date def _set_artwork(self, json): """Set the artwork urls from the json data""" if 'artworkUrl30' in json: self.artwork['30'] = json['artworkUrl30'] if 'artworkUrl60' in json: self.artwork['60'] = json['artworkUrl60'] self.artwork['600'] = self.artwork['60'].replace('60x60', '600x600') if '' in json: self.artwork['100'] = json['artworkUrl100'] if 'artworkUrl512' in json: self.artwork['512'] = json['artworkUrl512'] def _set_url(self, json): self.url = None if 'trackViewUrl' in json: self.url = json['trackViewUrl'] elif 'collectionViewUrl' in json: self.url = json['collectionViewUrl'] elif 'artistViewUrl' in json: self.url = json['artistViewUrl'] def __repr__(self): if not self.name: return '<{type}>: {id}'.format(type=self.type.title(), id=self.id) name = self.name if sys.version_info[0] == 2: name = self.name.encode('utf8') return '<{type}>: {name}'.format(type=self.type.title(), name=name) def __eq__(self, other): return self.id == other.id def __ne__(self, other): return not self.__eq__(other)
[docs] def get_tracks(self, limit=500): """Returns the tracks associated with this :class:`Resource`""" if self.type == 'song': return self items = Lookup(id=self.id, entity='song', limit=limit).get() if not items: raise NoResultsFoundException() return items[1:]
[docs] def get_albums(self, limit=200): """Returns the albums associated with this :class:`Resource`""" if self.type == 'collection': return self if self.type == 'song': return self.get_album() items = Lookup(id=self.id, entity='album', limit=limit).get() if not items: raise NoResultsFoundException() return items[1:]
[docs] def get_album(self): """Returns the first album associated with this :class:`Resource`""" if self.type == 'collection': return self items = Lookup(id=self.id, entity='album', limit=1).get() if not items or len(items) == 1: raise NoResultsFoundException() return items[1]
[docs]class Lookup(BaseObject): """A data model for an individual resource look up against iTunes""" resource = 'lookup' def __init__(self, id, entity=None, limit=50): super(self.__class__, self).__init__(id=id, entity=entity, limit=limit) self.id = id
[docs]class Artist(Resource): """The Artist :class:`Resource` represents an iTunes artist""" def _set(self, json): super(Artist, self)._set(json) self.name = json['artistName'] self.amg_id = json.get('amgArtistId', None) self.url = json.get('artistViewUrl', json.get('artistLinkUrl', None))
[docs]class Album(Resource): """The Album :class:`Resource` represents an Album (or collection of single resources) of other resource types """ def _set(self, json): super(Album, self)._set(json) self.name = json['collectionName'] self.url = json.get('collectionViewUrl', None) self.amg_id = json.get('amgAlbumId', None) self.price = round(json['collectionPrice'] or 0, 4) self.price_currency = json['currency'] self.track_count = json['trackCount'] self.copyright = json.get('copyright', None) self._set_artist(json) def _set_artist(self, json): self.artist = None if json.get('artistId'): id = json['artistId'] self.artist = Artist(id) self.artist._set(json)
[docs]class Track(Resource): """The Track :class:`Resource` represents a single track from the iTunes store """ def _set(self, json): super(Track, self)._set(json) # Track information self.name = json['trackName'] self.url = json.get('trackViewUrl', None) self.preview_url = json.get('previewUrl', None) self.price = None if 'trackPrice' in json and json['trackPrice'] is not None: self.price = round(json['trackPrice'], 4) self.number = json.get('trackNumber', None) self.duration = None if 'trackTimeMillis' in json and json['trackTimeMillis'] is not None: self.duration = round(json.get('trackTimeMillis', 0.0)/1000.0, 2) try: self._set_artist(json) except KeyError: self.artist = None try: self._set_album(json) except KeyError: self.album = None def _set_artist(self, json): self.artist = None if json.get('artistId'): id = json['artistId'] self.artist = Artist(id) self.artist._set(json) def _set_album(self, json): if 'collectionId' in json: id = json['collectionId'] self.album = Album(id) self.album._set(json)
[docs]class Audiobook(Album): """The Audiobook :class:`Resource` represents an iTunes Audiobook"""
[docs]class Software(Track): """The Software :class:`Resource` represents an iTunes App resource""" def __init__(self, *args, **kwargs): super(self.__class__, self).__init__(*args, **kwargs) self.ratings = dict(avg=dict(current=None, all=None), num=dict(current=None, all=None)) def _set(self, json): super(Software, self)._set(json) self.version = json.get('version', None) self.price = json.get('price', None) self.description = json.get('description', None) self.screenshots = json.get('screenshotUrls', None) self.genres = json.get('genres', None) self.seller_url = json.get('sellerUrl', None) self.languages = json.get('languageCodesISO2A', None) self._set_ratings(json) def _set_ratings(self, json): k = 'averageUserRatingForCurrentVersion' self.ratings['avg']['current'] = json.get(k, None) self.ratings['avg']['all'] = json.get('averageUserRating', None) k = 'userRatingCountForCurrentVersion' self.ratings['num']['current'] = json.get(k, None) self.ratings['num']['all'] = json.get('userRatingCount', None)
[docs]class TVEpisode(Track): """The TVEpisode :class:`Resource` represents a track type that represents a single TV Episode. """ def _set(self, json): super(TVEpisode, self)._set(json) self.content_rating = json.get('contentAdvisoryRating', None) self.short_description = json.get('shortDescription', None) self.long_description = json.get('longDescription', None) self.explicitness = json.get('trackExplicitness', None) self.episode_number = json.get('trackNumber', None) self.genre = json.get('primaryGenreName', None) self.episode_id = json.get('trackId', None) self.show_id = json.get('artistId', None) self.season = json.get('collectionName', None)[-1:] self.season_id = json.get('collectionId', None)
[docs]def lookup(id): """Perform an individual :class:`Lookup` on a single resource in the iTunes Store API """ items = Lookup(id).get() if not items: raise NoResultsFoundException() return items[0]