def login(self, username, password, reuse_token=True): if not self.is_authenticated( ) or self.token.username != username or not reuse_token: data = { 'username': username, 'password': password, 'grant_type': 'password' } try: response = requests.post(API.BASE_URL + 'oauth/token', data=data, auth=self._auth) self.token = Token(json.loads(response.text)) self.account = None self._cache.set('token', self.token, API.TOKEN_CACHE_LIFETIME) except KeyError: raise exceptions.AuthenticationError except ConnectionError: raise exceptions.RequestError self._log_response(response) elif self.token.is_expired(): try: self.refresh_token() self.account = None except KeyError: self.login(username, password, reuse_token=False) if self.account is None: self.account = self.get_detail(Account, 'me', 'account') self._cache.set('account', self.account, API.TOKEN_CACHE_LIFETIME)
def login(self, username, password, reuse_token=True): if not self.is_authenticated() or self.token.username != username or not reuse_token: data = { 'username': username, 'password': password, 'grant_type': 'password' } try: response = requests.post(API.BASE_URL + 'oauth/token', data=data, auth=self._auth) self.token = Token(json.loads(response.text)) self.account = None self._cache.set('token', self.token, API.TOKEN_CACHE_LIFETIME) except KeyError: raise exceptions.AuthenticationError except ConnectionError: raise exceptions.RequestError self._log_response(response) elif self.token.is_expired(): try: self.refresh_token() self.account = None except KeyError: self.login(username, password, reuse_token=False) if self.account is None: self.account = self.get_detail(Account, 'me', 'account') self._cache.set('account', self.account, API.TOKEN_CACHE_LIFETIME)
class API: BASE_URL = 'https://api.rhapsody.com/' VERSION = 'v1' TOKEN_CACHE_LIFETIME = 60 * 60 * 24 * 7 # 7 days DEFAULT_CACHE_TIMEOUT = 60 * 60 * 2 # 2 hours MAX_RETRIES = 3 ENABLE_CACHE = True ENABLE_DEBUG = False instance = None token = Token def __init__(self, key, secret, cache_instance=cache.Dummy(), log_callback=None): API.instance = self self._cache = cache_instance self._auth = (key, secret) self._key = key self._secret = secret self._log_callback = log_callback self.artists = Artists(self) self.albums = Albums(self) self.events = Events(self) self.genres = Genres(self) self.library = Library(self) self.playlists = Playlists(self) self.search = Search(self) self.stations = Stations(self) self.stations_tracks = StationsTracks(self) self.streams = Streams(self) self.tracks = Tracks(self) self.token = self._cache.get('token', API.TOKEN_CACHE_LIFETIME) self.account = self._cache.get('account', API.TOKEN_CACHE_LIFETIME) self.session = self._cache.get('session', API.TOKEN_CACHE_LIFETIME) def is_authenticated(self): try: return len(self.token.access_token) > 0 except AttributeError: return False def login(self, username, password, reuse_token=True): if not self.is_authenticated( ) or self.token.username != username or not reuse_token: data = { 'username': username, 'password': password, 'grant_type': 'password' } try: response = requests.post(API.BASE_URL + 'oauth/token', data=data, auth=self._auth) self.token = Token(json.loads(response.text)) self.account = None self._cache.set('token', self.token, API.TOKEN_CACHE_LIFETIME) except KeyError: raise exceptions.AuthenticationError except ConnectionError: raise exceptions.RequestError self._log_response(response) elif self.token.is_expired(): try: self.refresh_token() self.account = None except KeyError: self.login(username, password, reuse_token=False) if self.account is None: self.account = self.get_detail(Account, 'me', 'account') self._cache.set('account', self.account, API.TOKEN_CACHE_LIFETIME) def logout(self): self.token = None def refresh_token(self): data = { 'client_id': self._key, 'client_secret': self._secret, 'response_type': 'code', 'grant_type': 'refresh_token', 'refresh_token': self.token.refresh_token } try: response = requests.post(API.BASE_URL + 'oauth/access_token', data=data, auth=self._auth) except ConnectionError: raise exceptions.RequestError self._log_response(response) self.token.update_token(json.loads(response.text)) self._cache.set('token', self.token, API.TOKEN_CACHE_LIFETIME) def refresh_session(self): try: response = requests.post(API.BASE_URL + 'v1/sessions', headers=self._get_headers()) except ConnectionError: raise exceptions.RequestError self._log_response(response) try: self.session = Session(json.loads(response.text)) self._cache.set('session', self.session, API.TOKEN_CACHE_LIFETIME) except Exception as e: self._log_message(str(e)) raise exceptions.ResponseError def validate_session(self): if self.session is not None: if self.session.valid and self.session.created + timedelta( seconds=30) > datetime.now(): return self.session.valid try: response = requests.get(API.BASE_URL + 'v1/sessions/' + self.session.id, headers=self._get_headers()) self._log_response(response) self.session = Session(json.loads(response.text)) self._cache.set('session', self.session, API.TOKEN_CACHE_LIFETIME) if not self.session.valid: self.refresh_session() except (ConnectionError, KeyError): self.refresh_session() else: self.refresh_session() return self.session.valid def _get_headers(self, headers=None): if headers is None: headers = dict() if self.is_authenticated(): if self.token.is_expired(): self.refresh_token() headers['Authorization'] = 'Bearer ' + self.token.access_token return headers def _log_response(self, response): if self.ENABLE_DEBUG: self._log_message( str({ 'request': { 'url': response.request.url, 'headers': response.request.headers, 'body': response.request.body }, 'response': { 'status': response.status_code, 'text': response.text } })) def _log_message(self, msg): if self._log_callback is not None: self._log_callback(msg) else: logging.log(logging.INFO, msg) def get(self, url, params, headers=None, retry=0, version=None): headers = self._get_headers(headers) version = version or self.VERSION try: response = requests.get(API.BASE_URL + version + '/' + url, params=params, headers=headers) self._log_response(response) if response.status_code in [400, 401, 403, 405, 409, 429]: raise exceptions.RequestError(response.status_code) if response.status_code in [404]: raise exceptions.ResourceNotFoundError(response.status_code) if response.status_code in [500]: raise exceptions.ResponseError(response.status_code) return response.text except ConnectionError: if retry < self.MAX_RETRIES: return self.get(url, params, headers, retry + 1) else: raise exceptions.RequestError def post(self, url, data, headers=None, retry=0, version=None): headers = self._get_headers(headers) version = version or self.VERSION try: response = requests.post(API.BASE_URL + version + '/' + url, data=data, headers=headers) except ConnectionError: if retry < self.MAX_RETRIES: return self.post(url, data, headers, retry + 1) else: raise exceptions.RequestError self._log_response(response) return response.text def put(self, url, data, headers=None, retry=0, version=None): headers = self._get_headers(headers) version = version or self.VERSION try: response = requests.put(API.BASE_URL + version + '/' + url, data=data, headers=headers) except ConnectionError: if retry < self.MAX_RETRIES: return self.put(url, data, headers, retry + 1) else: raise exceptions.RequestError self._log_response(response) return response.text def delete(self, url, headers=None, retry=0, version=None): headers = self._get_headers(headers) version = version or self.VERSION try: response = requests.delete(API.BASE_URL + version + '/' + url, headers=headers) except ConnectionError: if retry < self.MAX_RETRIES: return self.delete(url, headers, retry + 1) else: raise exceptions.RequestError self._log_response(response) return response.text def get_json(self, url, params, cache_timeout=None, retry=0, version=None): cache_data = { 'url': url, 'params': params, 'user': '', 'version': version or self.VERSION } cache_timeout = cache_timeout or self.DEFAULT_CACHE_TIMEOUT if self.is_authenticated(): if self.token.is_expired(): self.refresh_token() # params['catalog'] = self.token.catalog cache_data['user'] = self.token.username cache_key = hashlib.sha1(json.dumps(cache_data).encode()).hexdigest() response_text = None if self.ENABLE_CACHE and cache_timeout is not None and retry == 0: response_text = self._cache.get(cache_key, cache_timeout) if response_text is None: response_text = self.get(url, params=params, headers=self._get_headers(), version=version) if cache_timeout is not None: self._cache.set(cache_key, response_text, cache_timeout) try: return json.loads(response_text) except ValueError: if retry < self.MAX_RETRIES: return self.get_json(url, params, cache_timeout, retry + 1) else: raise exceptions.ResponseError def get_detail(self, model, obj, obj_id, cache_timeout=None, params=None, version=None): if params is None: params = dict() if not self.is_authenticated(): params['apikey'] = self._key url = obj if obj_id is not None: url += '/' + obj_id data = self.get_json(url, params, cache_timeout or self.DEFAULT_CACHE_TIMEOUT, version=version) if type(data) == list and len(data) == 1: data = data[0] return model(data) def get_list(self, model, obj, limit=None, offset=None, cache_timeout=None, params=None, version=None): if params is None: params = dict() if not self.is_authenticated(): params['apikey'] = self._key if limit is not None: params['limit'] = limit if offset is not None: params['offset'] = offset items = [] for item in self.get_json(obj, params, cache_timeout or self.DEFAULT_CACHE_TIMEOUT, version=version): items.append(model(item)) return items
class API: BASE_URL = 'https://api.rhapsody.com/' VERSION = 'v1' TOKEN_CACHE_LIFETIME = timedelta(days=30).total_seconds() DEFAULT_CACHE_TIMEOUT = timedelta(hours=2).total_seconds() MAX_RETRIES = 3 DEBUG = False instance = None token = Token def __init__(self, key, secret, cache_instance=cache.Dummy()): API.instance = self self._cache = cache_instance self._auth = (key, secret) self._key = key self._secret = secret self.artists = Artists(self) self.albums = Albums(self) self.events = Events(self) self.genres = Genres(self) self.library = Library(self) self.playlists = Playlists(self) self.search = Search(self) self.streams = Streams(self) self.tracks = Tracks(self) self.token = self._cache.get('token', API.TOKEN_CACHE_LIFETIME) self.account = self._cache.get('account', API.TOKEN_CACHE_LIFETIME) def is_authenticated(self): try: return len(self.token.access_token) > 0 except AttributeError: return False def login(self, username, password, reuse_token=True): if not self.is_authenticated() or self.token.username != username or not reuse_token: data = { 'username': username, 'password': password, 'grant_type': 'password' } try: response = requests.post(API.BASE_URL + 'oauth/token', data=data, auth=self._auth) self.token = Token(json.loads(response.text)) self.account = None self._cache.set('token', self.token, API.TOKEN_CACHE_LIFETIME) except KeyError: raise exceptions.AuthenticationError except ConnectionError: raise exceptions.RequestError self._log_response(response) elif self.token.is_expired(): try: self.refresh_token() self.account = None except KeyError: self.login(username, password, reuse_token=False) if self.account is None: self.account = self.get_detail(Account, 'me', 'account') self._cache.set('account', self.account, API.TOKEN_CACHE_LIFETIME) def logout(self): self.token = None def refresh_token(self): data = { 'client_id': self._key, 'client_secret': self._secret, 'response_type': 'code', 'grant_type': 'refresh_token', 'refresh_token': self.token.refresh_token } try: response = requests.post(API.BASE_URL + 'oauth/access_token', data=data, auth=self._auth) except ConnectionError: raise exceptions.RequestError self._log_response(response) self.token.update_token(json.loads(response.text)) self._cache.set('token', self.token, API.TOKEN_CACHE_LIFETIME) def _get_headers(self, headers=None): if headers is None: headers = dict() if self.is_authenticated(): if self.token.is_expired(): self.refresh_token() headers['Authorization'] = 'Bearer ' + self.token.access_token return headers def _log_response(self, response): if self.DEBUG: print { 'request': { 'url': response.request.url, }, 'response': { 'status': response.status_code, 'text': response.text } } def get(self, url, params, headers=None, retry=0): headers = self._get_headers(headers) try: response = requests.get(API.BASE_URL + API.VERSION + '/' + url, params=params, headers=headers) except ConnectionError: if retry < self.MAX_RETRIES: return self.get(url, params, headers, retry + 1) else: raise exceptions.RequestError if response.status_code == 404: raise exceptions.ResourceNotFoundError self._log_response(response) return response.text def post(self, url, data, headers=None, retry=0): headers = self._get_headers(headers) try: response = requests.post(API.BASE_URL + API.VERSION + '/' + url, data=data, headers=headers) except ConnectionError: if retry < self.MAX_RETRIES: return self.post(url, data, headers, retry + 1) else: raise exceptions.RequestError self._log_response(response) return response.text def put(self, url, data, headers=None, retry=0): headers = self._get_headers(headers) try: response = requests.put(API.BASE_URL + API.VERSION + '/' + url, data=data, headers=headers) except ConnectionError: if retry < self.MAX_RETRIES: return self.put(url, data, headers, retry + 1) else: raise exceptions.RequestError self._log_response(response) return response.text def delete(self, url, headers=None, retry=0): headers = self._get_headers(headers) try: response = requests.delete(API.BASE_URL + API.VERSION + '/' + url, headers=headers) except ConnectionError: if retry < self.MAX_RETRIES: return self.delete(url, headers, retry + 1) else: raise exceptions.RequestError self._log_response(response) return response.text def get_json(self, url, params, cache_timeout=DEFAULT_CACHE_TIMEOUT, retry=0): cache_data = { 'url': url, 'params': params, 'user': '' } if self.is_authenticated(): if self.token.is_expired(): self.refresh_token() params['catalog'] = self.token.catalog cache_data['user'] = self.token.username cache_key = hashlib.sha1(json.dumps(cache_data)).hexdigest() response_text = None if cache_timeout is not None and not self.DEBUG and retry == 0: response_text = self._cache.get(cache_key, cache_timeout) if response_text is None: response_text = self.get(url, params=params, headers=self._get_headers()) if cache_timeout is not None: self._cache.set(cache_key, response_text, cache_timeout) try: return json.loads(response_text) except ValueError: if retry < self.MAX_RETRIES: return self.get_json(url, params, cache_timeout, retry + 1) else: raise exceptions.ResponseError def get_detail(self, model, obj, obj_id, cache_timeout=None, params=None): if params is None: params = dict() params['apikey'] = self._key return model(self.get_json(obj + '/' + obj_id, params, cache_timeout)) def get_list(self, model, obj, limit=None, offset=None, cache_timeout=None, params=None): if params is None: params = dict() params['apikey'] = self._key if limit is not None: params['limit'] = limit if offset is not None: params['offset'] = limit items = [] for item in self.get_json(obj, params, cache_timeout): items.append(model(item)) return items