This commit is contained in:
hugovk 2014-02-26 13:16:28 +02:00
parent eadce48315
commit b48f56badf
2 changed files with 67 additions and 123 deletions

180
pylast.py
View file

@ -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:

View file

@ -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 <amr.hassan@gmail.com>",
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"
)