diff --git a/pylast.py b/pylast.py index aab208e..db824c3 100644 --- a/pylast.py +++ b/pylast.py @@ -18,7 +18,7 @@ # # http://code.google.com/p/pylast/ -__version__ = '0.6' +__version__ = '0.5' __author__ = 'Amr Hassan' __copyright__ = "Copyright (C) 2008-2010 Amr Hassan" __license__ = "apache2" @@ -47,7 +47,7 @@ if sys.version_info[0] == 3: elif sys.version_info[0] == 2: from httplib import HTTPConnection - import htmlentitydefs + import htmlentitydefs from urllib import splithost as url_split_host from urllib import quote_plus as url_quote_plus @@ -143,7 +143,7 @@ class _Network(object): """ self.name = name - self.homepage = homepage + self.homepage = homepage self.ws_server = ws_server self.api_key = api_key self.api_secret = api_secret @@ -241,13 +241,13 @@ class _Network(object): Quote from http://www.last.fm/api/submissions: ======== - Client identifiers are used to provide a centrally managed database of - the client versions, allowing clients to be banned if they are found to - be behaving undesirably. The client ID is associated with a version - number on the server, however these are only incremented if a client is + Client identifiers are used to provide a centrally managed database of + the client versions, allowing clients to be banned if they are found to + be behaving undesirably. The client ID is associated with a version + number on the server, however these are only incremented if a client is banned and do not have to reflect the version of the actual client application. - During development, clients which have not been allocated an identifier should + During development, clients which have not been allocated an identifier should use the identifier tst, with a version number of 1.0. Do not distribute code or client implementations which use this test identifier. Do not use the identifiers used by other clients. @@ -257,7 +257,7 @@ class _Network(object): * Last.fm: submissions@last.fm * # TODO: list others - ...and provide us with the name of your client and its homepage address. + ...and provide us with the name of your client and its homepage address. """ _deprecation_warning("Use _Network.scrobble(...), _Network.scrobble_many(...), and Netowrk.update_now_playing(...) instead") @@ -353,7 +353,7 @@ class _Network(object): def enable_caching(self, file_path = None): """Enables caching request-wide for all cachable calls. - * file_path: A file path for the backend storage file. If + * file_path: A file path for the backend storage file. If None set, a temp file would probably be created, according the backend. """ @@ -435,10 +435,10 @@ class _Network(object): return Album(_extract(doc, "artist"), _extract(doc, "name"), self) - def update_now_playing(self, artist, title, album = None, album_artist = None, + def update_now_playing(self, artist, title, album = None, album_artist = None, duration = None, track_number = None, mbid = None, context = None): """ - Used to notify Last.fm that a user has started listening to a track. + Used to notify Last.fm that a user has started listening to a track. Parameters: artist (Required) : The artist name @@ -462,7 +462,7 @@ class _Network(object): _Request(self, "track.updateNowPlaying", params).execute() - def scrobble(self, artist, title, timestamp, album = None, album_artist = None, track_number = None, + def scrobble(self, artist, title, timestamp, album = None, album_artist = None, track_number = None, duration = None, stream_id = None, context = None, mbid = None): """Used to add a track-play to a user's profile. @@ -578,7 +578,7 @@ class LastFMNetwork(_Network): ) def __repr__(self): - return "pylast.LastFMNetwork(%s)" %(", ".join(("'%s'" %self.api_key, "'%s'" %self.api_secret, "'%s'" %self.session_key, + return "pylast.LastFMNetwork(%s)" %(", ".join(("'%s'" %self.api_key, "'%s'" %self.api_secret, "'%s'" %self.session_key, "'%s'" %self.username, "'%s'" %self.password_hash))) def __str__(self): @@ -661,7 +661,7 @@ class LibreFMNetwork(_Network): ) def __repr__(self): - return "pylast.LibreFMNetwork(%s)" %(", ".join(("'%s'" %self.api_key, "'%s'" %self.api_secret, "'%s'" %self.session_key, + return "pylast.LibreFMNetwork(%s)" %(", ".join(("'%s'" %self.api_key, "'%s'" %self.api_secret, "'%s'" %self.session_key, "'%s'" %self.username, "'%s'" %self.password_hash))) def __str__(self): @@ -788,7 +788,7 @@ class _Request(object): "Content-type": "application/x-www-form-urlencoded", 'Accept-Charset': 'utf-8', 'User-Agent': "pylast" + '/' + __version__ - } + } (HOST_NAME, HOST_SUBDIR) = self.network.ws_server @@ -796,7 +796,7 @@ class _Request(object): conn = HTTPConnection(host = self._get_proxy()[0], port = self._get_proxy()[1]) try: - conn.request(method='POST', url="http://" + HOST_NAME + HOST_SUBDIR, + conn.request(method='POST', url="http://" + HOST_NAME + HOST_SUBDIR, body=data, headers=headers) except Exception as e: raise NetworkError(self.network, e) @@ -864,7 +864,7 @@ class SessionKeyGenerator(object): manually, unless you want to. """ - def __init__(self, network): + def __init__(self, network): self.network = network self.web_auth_tokens = {} @@ -1272,7 +1272,7 @@ class Album(_BaseObject, _Taggable): return _extract(self._request("album.getInfo", cacheable = True), "mbid") def get_url(self, domain_name = DOMAIN_ENGLISH): - """Returns the url of the album page on the network. + """Returns the url of the album page on the network. # Parameters: * domain_name str: The network's language domain. Possible values: o DOMAIN_ENGLISH @@ -1537,10 +1537,10 @@ class Artist(_BaseObject, _Taggable): return seq def share(self, users, message = None): - """Shares this artist (sends out recommendations). + """Shares this artist (sends out recommendations). # Parameters: * users [User|str,]: A list that can contain usernames, emails, User objects, or all of them. - * message str: A message to include in the recommendation message. + * message str: A message to include in the recommendation message. """ #last.fm currently accepts a max of 10 recipient at a time @@ -1565,7 +1565,7 @@ class Artist(_BaseObject, _Taggable): self._request('artist.share', False, params) def get_url(self, domain_name = DOMAIN_ENGLISH): - """Returns the url of the artist page on the network. + """Returns the url of the artist page on the network. # Parameters: * domain_name: The network's language domain. Possible values: o DOMAIN_ENGLISH @@ -1579,7 +1579,7 @@ class Artist(_BaseObject, _Taggable): o DOMAIN_TURKISH o DOMAIN_RUSSIAN o DOMAIN_JAPANESE - o DOMAIN_CHINESE + o DOMAIN_CHINESE """ artist = _url_safe(self.get_name()) @@ -1675,7 +1675,7 @@ class Event(_BaseObject): * attending_status: The attending status. Possible values: o EVENT_ATTENDING o EVENT_MAYBE_ATTENDING - o EVENT_NOT_ATTENDING + o EVENT_NOT_ATTENDING """ params = self._get_params() @@ -1735,7 +1735,7 @@ class Event(_BaseObject): v = doc.getElementsByTagName("venue")[0] venue_id = _number(_extract(v, "id")) - return Venue(venue_id, self.network, venue_element=v) + return Venue(venue_id, self.network) def get_start_date(self): """Returns the date when the event starts.""" @@ -1781,7 +1781,7 @@ class Event(_BaseObject): return _number(_extract(doc, "reviews")) def get_url(self, domain_name = DOMAIN_ENGLISH): - """Returns the url of the event page on the network. + """Returns the url of the event page on the network. * domain_name: The network's language domain. Possible values: o DOMAIN_ENGLISH o DOMAIN_GERMAN @@ -1794,15 +1794,15 @@ class Event(_BaseObject): o DOMAIN_TURKISH o DOMAIN_RUSSIAN o DOMAIN_JAPANESE - o DOMAIN_CHINESE + o DOMAIN_CHINESE """ return self.network._get_url(domain_name, "event") %{'id': self.get_id()} def share(self, users, message = None): - """Shares this event (sends out recommendations). + """Shares this event (sends out recommendations). * users: A list that can contain usernames, emails, User objects, or all of them. - * message: A message to include in the recommendation message. + * message: A message to include in the recommendation message. """ #last.fm currently accepts a max of 10 recipient at a time @@ -1919,7 +1919,7 @@ class Country(_BaseObject): return seq def get_url(self, domain_name = DOMAIN_ENGLISH): - """Returns the url of the event page on the network. + """Returns the url of the event page on the network. * domain_name: The network's language domain. Possible values: o DOMAIN_ENGLISH o DOMAIN_GERMAN @@ -1932,7 +1932,7 @@ class Country(_BaseObject): o DOMAIN_TURKISH o DOMAIN_RUSSIAN o DOMAIN_JAPANESE - o DOMAIN_CHINESE + o DOMAIN_CHINESE """ country_name = _url_safe(self.get_name()) @@ -1976,7 +1976,7 @@ class Library(_BaseObject): """Add an album to this library.""" params = self._get_params() - params["artist"] = album.get_artist.get_name() + params["artist"] = album.get_artist().get_name() params["album"] = album.get_name() self._request("library.addAlbum", False, params) @@ -2173,7 +2173,7 @@ class Playlist(_BaseObject): return _extract(self._get_info_node(), "image")[size] def get_url(self, domain_name = DOMAIN_ENGLISH): - """Returns the url of the playlist on the network. + """Returns the url of the playlist on the network. * domain_name: The network's language domain. Possible values: o DOMAIN_ENGLISH o DOMAIN_GERMAN @@ -2186,7 +2186,7 @@ class Playlist(_BaseObject): o DOMAIN_TURKISH o DOMAIN_RUSSIAN o DOMAIN_JAPANESE - o DOMAIN_CHINESE + o DOMAIN_CHINESE """ english_url = _extract(self._get_info_node(), "url") @@ -2319,7 +2319,7 @@ class Tag(_BaseObject): return seq def get_url(self, domain_name = DOMAIN_ENGLISH): - """Returns the url of the tag page on the network. + """Returns the url of the tag page on the network. * domain_name: The network's language domain. Possible values: o DOMAIN_ENGLISH o DOMAIN_GERMAN @@ -2332,7 +2332,7 @@ class Tag(_BaseObject): o DOMAIN_TURKISH o DOMAIN_RUSSIAN o DOMAIN_JAPANESE - o DOMAIN_CHINESE + o DOMAIN_CHINESE """ name = _url_safe(self.get_name()) @@ -2548,9 +2548,9 @@ class Track(_BaseObject, _Taggable): return seq def share(self, users, message = None): - """Shares this track (sends out recommendations). + """Shares this track (sends out recommendations). * users: A list that can contain usernames, emails, User objects, or all of them. - * message: A message to include in the recommendation message. + * message: A message to include in the recommendation message. """ #last.fm currently accepts a max of 10 recipient at a time @@ -2575,7 +2575,7 @@ class Track(_BaseObject, _Taggable): self._request('track.share', False, params) def get_url(self, domain_name = DOMAIN_ENGLISH): - """Returns the url of the track page on the network. + """Returns the url of the track page on the network. * domain_name: The network's language domain. Possible values: o DOMAIN_ENGLISH o DOMAIN_GERMAN @@ -2588,7 +2588,7 @@ class Track(_BaseObject, _Taggable): o DOMAIN_TURKISH o DOMAIN_RUSSIAN o DOMAIN_JAPANESE - o DOMAIN_CHINESE + o DOMAIN_CHINESE """ artist = _url_safe(self.get_artist().get_name()) @@ -2707,7 +2707,7 @@ class Group(_BaseObject): return seq def get_url(self, domain_name = DOMAIN_ENGLISH): - """Returns the url of the group page on the network. + """Returns the url of the group page on the network. * domain_name: The network's language domain. Possible values: o DOMAIN_ENGLISH o DOMAIN_GERMAN @@ -2720,7 +2720,7 @@ class Group(_BaseObject): o DOMAIN_TURKISH o DOMAIN_RUSSIAN o DOMAIN_JAPANESE - o DOMAIN_CHINESE + o DOMAIN_CHINESE """ name = _url_safe(self.get_name()) @@ -2832,13 +2832,13 @@ class User(_BaseObject): for e_id in ids: events.append(Event(e_id, self.network)) - + return events - + def get_artist_tracks(self, artist): """Get a list of tracks by a given artist scrobbled by this user, including scrobble time.""" # Not implemented: "Can be limited to specific timeranges, defaults to all time." - + params = self._get_params() params['artist'] = artist @@ -2848,14 +2848,14 @@ class User(_BaseObject): artist = _extract(track, "artist") date = _extract(track, "date") timestamp = track.getElementsByTagName("date")[0].getAttribute("uts") - + seq.append(PlayedTrack(Track(artist, title, self.network), date, timestamp)) return seq def get_friends(self, limit = 50): """Returns a list of the user's friends. """ - + seq = [] for node in _collect_nodes(limit, self, "user.getFriends", False): seq.append(User(_extract(node, "name"), self.network)) @@ -3037,13 +3037,13 @@ class User(_BaseObject): return _number(_extract(doc, "playcount")) def get_top_albums(self, period = PERIOD_OVERALL): - """Returns the top albums played by a user. + """Returns the top albums played by a user. * period: The period of time. Possible values: o PERIOD_OVERALL o PERIOD_7DAYS o PERIOD_3MONTHS o PERIOD_6MONTHS - o PERIOD_12MONTHS + o PERIOD_12MONTHS """ params = self._get_params() @@ -3062,13 +3062,13 @@ class User(_BaseObject): return seq def get_top_artists(self, period = PERIOD_OVERALL): - """Returns the top artists played by a user. + """Returns the top artists played by a user. * period: The period of time. Possible values: o PERIOD_OVERALL o PERIOD_7DAYS o PERIOD_3MONTHS o PERIOD_6MONTHS - o PERIOD_12MONTHS + o PERIOD_12MONTHS """ params = self._get_params() @@ -3086,8 +3086,8 @@ class User(_BaseObject): return seq def get_top_tags(self, limit=None): - """Returns a sequence of the top tags used by this user with their counts as TopItem objects. - * limit: The limit of how many tags to return. + """Returns a sequence of the top tags used by this user with their counts as TopItem objects. + * limit: The limit of how many tags to return. """ doc = self._request("user.getTopTags", True) @@ -3102,13 +3102,13 @@ class User(_BaseObject): return seq def get_top_tracks(self, period = PERIOD_OVERALL): - """Returns the top tracks played by a user. + """Returns the top tracks played by a user. * period: The period of time. Possible values: o PERIOD_OVERALL o PERIOD_7DAYS o PERIOD_3MONTHS o PERIOD_6MONTHS - o PERIOD_12MONTHS + o PERIOD_12MONTHS """ params = self._get_params() @@ -3230,7 +3230,7 @@ class User(_BaseObject): return _extract(doc, "image") def get_url(self, domain_name = DOMAIN_ENGLISH): - """Returns the url of the user page on the network. + """Returns the url of the user page on the network. * domain_name: The network's language domain. Possible values: o DOMAIN_ENGLISH o DOMAIN_GERMAN @@ -3243,7 +3243,7 @@ class User(_BaseObject): o DOMAIN_TURKISH o DOMAIN_RUSSIAN o DOMAIN_JAPANESE - o DOMAIN_CHINESE + o DOMAIN_CHINESE """ name = _url_safe(self.get_name()) @@ -3459,25 +3459,13 @@ class Venue(_BaseObject): """A venue where events are held.""" # TODO: waiting for a venue.getInfo web service to use. - # TODO: As an intermediate use case, can pass the venue DOM element when using - # Event.get_venue() to populate the venue info, if the venue.getInfo API - # call becomes available this workaround should be removed id = None - info = None - name = None - location = None - url = None - def __init__(self, id, network, venue_element=None): + def __init__(self, id, network): _BaseObject.__init__(self, network) self.id = _number(id) - if venue_element is not None: - self.info = _extract_element_tree(venue_element) - self.name = self.info.get('name') - self.url = self.info.get('url') - self.location = self.info.get('location') def __repr__(self): return "pylast.Venue(%s, %s)" %(repr(self.id), repr(self.network)) @@ -3497,21 +3485,6 @@ class Venue(_BaseObject): return self.id - def get_name(self): - """Returns the name of the venue.""" - - return self.name - - def get_url(self): - """Returns the URL of the venue page.""" - - return self.url - - def get_location(self): - """Returns the location of the venue (dictionary).""" - - return self.location - def get_upcoming_events(self): """Returns the upcoming events in this venue.""" @@ -3602,14 +3575,14 @@ def _collect_nodes(limit, sender, method_name, cacheable, params=None): total_pages = _number(main.getAttribute("totalpages")) else: raise Exception("No total pages attribute") - + for node in main.childNodes: if not node.nodeType == xml.dom.Node.TEXT_NODE and (not limit or (len(nodes) < limit)): nodes.append(node) - + if page >= total_pages: end_of_pages = True - + page += 1 return nodes @@ -3625,35 +3598,6 @@ def _extract(node, name, index = 0): else: return None -def _extract_element_tree(node, index = 0): - """Extract an element tree into a multi-level dictionary - - NB: If any elements have text nodes as well as nested - elements this will ignore the text nodes""" - - def _recurse_build_tree(rootNode, targetDict): - """Recursively build a multi-level dict""" - - def _has_child_elements(rootNode): - """Check if an element has any nested (child) elements""" - - for node in rootNode.childNodes: - if node.nodeType == node.ELEMENT_NODE: - return True - return False - - for node in rootNode.childNodes: - if node.nodeType == node.ELEMENT_NODE: - if _has_child_elements(node): - targetDict[node.tagName] = {} - _recurse_build_tree(node, targetDict[node.tagName]) - else: - val = None if node.firstChild is None else _unescape_htmlentity(node.firstChild.data.strip()) - targetDict[node.tagName] = val - return targetDict - - return _recurse_build_tree(node, {}) - def _extract_all(node, name, limit_count = None): """Extracts all the values from the xml string. returning a list.""" @@ -3689,7 +3633,7 @@ def _number(string): def _unescape_htmlentity(string): - #string = _unicode(string) + #string = _unicode(string) mapping = htmlentitydefs.name2codepoint for key in mapping: diff --git a/setup.py b/setup.py index 7bde338..50654e0 100755 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ from distutils.core import setup import os def get_build(): path = "./.build" - + if os.path.exists(path): fp = open(path, "r") build = eval(fp.read()) @@ -14,19 +14,19 @@ def get_build(): fp.close() else: build = 1 - + fp = open(path, "w") fp.write(str(build)) fp.close() - + return str(build) setup(name = "pylast", - version = "0.6." + get_build(), + version = "0.1+0.5." + get_build(), author = "Amr Hassan ", description = "A Python interface to Last.fm (and other API compatible social networks)", author_email = "amr.hassan@gmail.com", - url = "https://github.com/inversion/", + url = "https://github.com/Elizacat/", py_modules = ("pylast",), license = "Apache2" )