1# -*- coding: utf-8 -*- 2# gpodder.net API Client 3# Copyright (C) 2009-2013 Thomas Perl and the gPodder Team 4# 5# This program is free software: you can redistribute it and/or modify 6# it under the terms of the GNU General Public License as published by 7# the Free Software Foundation, either version 3 of the License, or 8# (at your option) any later version. 9# 10# This program is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# GNU General Public License for more details. 14# 15# You should have received a copy of the GNU General Public License 16# along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18from functools import wraps 19 20import mygpoclient 21 22from mygpoclient import locator 23from mygpoclient import json 24 25 26class MissingCredentials(Exception): 27 """ Raised when instantiating a SimpleClient without credentials """ 28 29 30def needs_credentials(f): 31 """ apply to all methods that initiate requests that require credentials """ 32 33 @wraps(f) 34 def _wrapper(self, *args, **kwargs): 35 if not self.username or not self.password: 36 raise MissingCredentials 37 38 return f(self, *args, **kwargs) 39 40 return _wrapper 41 42 43 44class Podcast(object): 45 """Container class for a podcast 46 47 Encapsulates the metadata for a podcast. 48 49 Attributes: 50 url - The URL of the podcast feed 51 title - The title of the podcast 52 description - The description of the podcast 53 """ 54 REQUIRED_FIELDS = ('url', 'title', 'description', 'website', 'subscribers', 55 'subscribers_last_week', 'mygpo_link', 'logo_url') 56 57 def __init__(self, url, title, description, website, subscribers, subscribers_last_week, mygpo_link, logo_url): 58 self.url = url 59 self.title = title 60 self.description = description 61 self.website = website 62 self.subscribers = subscribers 63 self.subscribers_last_week = subscribers_last_week 64 self.mygpo_link = mygpo_link 65 self.logo_url = logo_url 66 67 @classmethod 68 def from_dict(cls, d): 69 for key in cls.REQUIRED_FIELDS: 70 if key not in d: 71 raise ValueError('Missing keys for toplist podcast') 72 73 return cls(*(d.get(k) for k in cls.REQUIRED_FIELDS)) 74 75 def __eq__(self, other): 76 """Test two Podcast objects for equality 77 78 >>> Podcast('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h') == Podcast('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h') 79 True 80 >>> Podcast('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h') == Podcast('s', 't', 'u', 'v', 'w', 'x', 'y', 'z') 81 False 82 >>> Podcast('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h') == 'a' 83 False 84 """ 85 if not isinstance(other, self.__class__): 86 return False 87 88 return all(getattr(self, k) == getattr(other, k) \ 89 for k in self.REQUIRED_FIELDS) 90 91 92class SimpleClient(object): 93 """Client for the gpodder.net Simple API 94 95 This is the API client implementation that provides a 96 pythonic interface to the gpodder.net Simple API. 97 """ 98 FORMAT = 'json' 99 100 def __init__(self, username, password, root_url=mygpoclient.ROOT_URL, 101 client_class=json.JsonClient): 102 """Creates a new Simple API client 103 104 Username and password must be specified and are 105 the user's login data for the webservice. 106 107 The parameter root_url is optional and defaults to 108 the main webservice. It can be either a hostname or 109 a full URL (to force https, for instance). 110 111 The parameter client_class is optional and should 112 not need to be changed in normal use cases. If it 113 is changed, it should provide the same interface 114 as the json.JsonClient class in mygpoclient. 115 """ 116 self.username = username 117 self.password = password 118 self._locator = locator.Locator(username, root_url) 119 self._client = client_class(username, password) 120 121 @needs_credentials 122 def get_subscriptions(self, device_id): 123 """Get a list of subscriptions for a device 124 125 Returns a list of URLs (one per subscription) for 126 the given device_id that reflects the current list 127 of subscriptions. 128 129 Raises http.NotFound if the device does not exist. 130 """ 131 uri = self._locator.subscriptions_uri(device_id, self.FORMAT) 132 return self._client.GET(uri) 133 134 @needs_credentials 135 def put_subscriptions(self, device_id, urls): 136 """Update a device's subscription list 137 138 Sets the server-side subscription list for the device 139 "device_id" to be equivalent to the URLs in the list of 140 strings "urls". 141 142 The device will be created if it does not yet exist. 143 144 Returns True if the update was successful, False otherwise. 145 """ 146 uri = self._locator.subscriptions_uri(device_id, self.FORMAT) 147 return (self._client.PUT(uri, urls) == None) 148 149 @needs_credentials 150 def get_suggestions(self, count=10): 151 """Get podcast suggestions for the user 152 153 Returns a list of Podcast objects that are 154 to be suggested to the user. 155 156 The parameter count is optional and if 157 specified has to be a value between 1 158 and 100 (with 10 being the default), and 159 determines how much search results are 160 returned (at maximum). 161 """ 162 uri = self._locator.suggestions_uri(count, self.FORMAT) 163 return [Podcast.from_dict(x) for x in self._client.GET(uri)] 164 165 @property 166 def locator(self): 167 """ read-only access to the locator """ 168 return self._locator 169