Allow getting venue info through Event.get_venue() as a workaround until
the Venue.getInfo API call is added to Last.fm. Changed version to 0.6 in setup.py and pylast.py to reflect update.
This commit is contained in:
parent
4621a1a6e4
commit
c8216a139e
37
.gitignore
vendored
37
.gitignore
vendored
|
@ -1,37 +0,0 @@
|
||||||
*.py[cod]
|
|
||||||
|
|
||||||
# C extensions
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Packages
|
|
||||||
*.egg
|
|
||||||
*.egg-info
|
|
||||||
dist
|
|
||||||
build
|
|
||||||
eggs
|
|
||||||
parts
|
|
||||||
bin
|
|
||||||
var
|
|
||||||
sdist
|
|
||||||
develop-eggs
|
|
||||||
.installed.cfg
|
|
||||||
lib
|
|
||||||
lib64
|
|
||||||
__pycache__
|
|
||||||
|
|
||||||
# Installer logs
|
|
||||||
pip-log.txt
|
|
||||||
|
|
||||||
# Unit test / coverage reports
|
|
||||||
.coverage
|
|
||||||
.tox
|
|
||||||
nosetests.xml
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
*.mo
|
|
||||||
|
|
||||||
# Mr Developer
|
|
||||||
.mr.developer.cfg
|
|
||||||
.project
|
|
||||||
.pydevproject
|
|
||||||
|
|
2
AUTHORS
2
AUTHORS
|
@ -1,2 +0,0 @@
|
||||||
Amr Hassan <amr.hassan@gmail.com>
|
|
||||||
Lukas Lipka <lukaslipka@gmail.com>
|
|
6
MANIFEST.in
Executable file
6
MANIFEST.in
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
include pylast.py
|
||||||
|
include setup.py
|
||||||
|
include README
|
||||||
|
include COPYING
|
||||||
|
include INSTALL
|
||||||
|
include .build
|
10
PKG-INFO
10
PKG-INFO
|
@ -1,10 +0,0 @@
|
||||||
Metadata-Version: 1.0
|
|
||||||
Name: pylast
|
|
||||||
Version: 0.5.11
|
|
||||||
Summary: A Python interface to Last.fm (and other API compatible social networks)
|
|
||||||
Home-page: http://code.google.com/p/pylast/
|
|
||||||
Author: Amr Hassan <amr.hassan@gmail.com>
|
|
||||||
Author-email: amr.hassan@gmail.com
|
|
||||||
License: Apache2
|
|
||||||
Description: UNKNOWN
|
|
||||||
Platform: UNKNOWN
|
|
4
README
4
README
|
@ -3,5 +3,5 @@ pylast
|
||||||
|
|
||||||
A python interface to Last.fm. Try using the pydoc utility for help
|
A python interface to Last.fm. Try using the pydoc utility for help
|
||||||
on usage.
|
on usage.
|
||||||
For more info check out the project's home page at http://code.google.com/p/pylast/
|
|
||||||
or the mailing list http://groups.google.com/group/pylast/
|
Original code can be found at: http://code.google.com/p/pylast/
|
||||||
|
|
168
pylast.py
168
pylast.py
|
@ -33,7 +33,6 @@ import tempfile
|
||||||
import sys
|
import sys
|
||||||
import collections
|
import collections
|
||||||
import warnings
|
import warnings
|
||||||
import re
|
|
||||||
|
|
||||||
def _deprecation_warning(message):
|
def _deprecation_warning(message):
|
||||||
warnings.warn(message, DeprecationWarning)
|
warnings.warn(message, DeprecationWarning)
|
||||||
|
@ -314,37 +313,6 @@ class _Network(object):
|
||||||
|
|
||||||
return Playlist(user, e_id, self)
|
return Playlist(user, e_id, self)
|
||||||
|
|
||||||
def get_top_artists(self, limit=None):
|
|
||||||
"""Returns a sequence of the most played artists."""
|
|
||||||
|
|
||||||
doc = _Request(self, "chart.getTopArtists").execute(True)
|
|
||||||
seq = []
|
|
||||||
for node in doc.getElementsByTagName("artist"):
|
|
||||||
title = _extract(node, "name")
|
|
||||||
artist = Artist(title, self)
|
|
||||||
seq.append(artist)
|
|
||||||
|
|
||||||
if limit:
|
|
||||||
seq = seq[:limit]
|
|
||||||
|
|
||||||
return seq
|
|
||||||
|
|
||||||
def get_top_tracks(self, limit=None):
|
|
||||||
"""Returns a sequence of the most played tracks."""
|
|
||||||
|
|
||||||
doc = _Request(self, "chart.getTopTracks").execute(True)
|
|
||||||
seq = []
|
|
||||||
for node in doc.getElementsByTagName("track"):
|
|
||||||
title = _extract(node, "name")
|
|
||||||
artist = _extract(node, "name", 1)
|
|
||||||
track = Track(artist, title, self)
|
|
||||||
seq.append(track)
|
|
||||||
|
|
||||||
if limit:
|
|
||||||
seq = seq[:limit]
|
|
||||||
|
|
||||||
return seq
|
|
||||||
|
|
||||||
def get_top_tags(self, limit=None):
|
def get_top_tags(self, limit=None):
|
||||||
"""Returns a sequence of the most used tags as a sequence of TopItem objects."""
|
"""Returns a sequence of the most used tags as a sequence of TopItem objects."""
|
||||||
|
|
||||||
|
@ -384,8 +352,6 @@ class _Network(object):
|
||||||
|
|
||||||
def enable_caching(self, file_path = None):
|
def enable_caching(self, file_path = None):
|
||||||
"""Enables caching request-wide for all cachable calls.
|
"""Enables caching request-wide for all cachable calls.
|
||||||
In choosing the backend used for caching, it will try _SqliteCacheBackend first if
|
|
||||||
the module sqlite3 is present. If not, it will fallback to _ShelfCacheBackend which uses shelve.Shelf objects.
|
|
||||||
|
|
||||||
* 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.
|
None set, a temp file would probably be created, according the backend.
|
||||||
|
@ -848,10 +814,6 @@ class _Request(object):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise MalformedResponseError(self.network, e)
|
raise MalformedResponseError(self.network, e)
|
||||||
|
|
||||||
# Pretty decent catch for invalid & characters - which last.fm
|
|
||||||
# seems to generate for some artist eg. "K'nann"
|
|
||||||
response_text = re.sub("&(?![^\W]+;)", "&", response_text)
|
|
||||||
|
|
||||||
self._check_response_for_errors(response_text)
|
self._check_response_for_errors(response_text)
|
||||||
return response_text
|
return response_text
|
||||||
|
|
||||||
|
@ -1188,8 +1150,9 @@ class Album(_BaseObject, _Taggable):
|
||||||
|
|
||||||
title = None
|
title = None
|
||||||
artist = None
|
artist = None
|
||||||
|
username = None
|
||||||
|
|
||||||
def __init__(self, artist, title, network):
|
def __init__(self, artist, title, network, username=None):
|
||||||
"""
|
"""
|
||||||
Create an album instance.
|
Create an album instance.
|
||||||
# Parameters:
|
# Parameters:
|
||||||
|
@ -1206,6 +1169,7 @@ class Album(_BaseObject, _Taggable):
|
||||||
self.artist = Artist(artist, self.network)
|
self.artist = Artist(artist, self.network)
|
||||||
|
|
||||||
self.title = title
|
self.title = title
|
||||||
|
self.username = username
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "pylast.Album(%s, %s, %s)" %(repr(self.artist.name), repr(self.title), repr(self.network))
|
return "pylast.Album(%s, %s, %s)" %(repr(self.artist.name), repr(self.title), repr(self.network))
|
||||||
|
@ -1265,6 +1229,16 @@ class Album(_BaseObject, _Taggable):
|
||||||
|
|
||||||
return _number(_extract(self._request("album.getInfo", cacheable = True), "playcount"))
|
return _number(_extract(self._request("album.getInfo", cacheable = True), "playcount"))
|
||||||
|
|
||||||
|
def get_userplaycount(self):
|
||||||
|
"""Returns the number of plays by a given username"""
|
||||||
|
|
||||||
|
if not self.username: return
|
||||||
|
|
||||||
|
params = self._get_params()
|
||||||
|
params['username'] = self.username
|
||||||
|
|
||||||
|
return _number(_extract(self._request("album.getInfo", True, params), "userplaycount"))
|
||||||
|
|
||||||
def get_listener_count(self):
|
def get_listener_count(self):
|
||||||
"""Returns the number of liteners on the network"""
|
"""Returns the number of liteners on the network"""
|
||||||
|
|
||||||
|
@ -1360,8 +1334,9 @@ class Artist(_BaseObject, _Taggable):
|
||||||
"""An artist."""
|
"""An artist."""
|
||||||
|
|
||||||
name = None
|
name = None
|
||||||
|
username = None
|
||||||
|
|
||||||
def __init__(self, name, network):
|
def __init__(self, name, network, username=None):
|
||||||
"""Create an artist object.
|
"""Create an artist object.
|
||||||
# Parameters:
|
# Parameters:
|
||||||
* name str: The artist's name.
|
* name str: The artist's name.
|
||||||
|
@ -1371,6 +1346,7 @@ class Artist(_BaseObject, _Taggable):
|
||||||
_Taggable.__init__(self, 'artist')
|
_Taggable.__init__(self, 'artist')
|
||||||
|
|
||||||
self.name = name
|
self.name = name
|
||||||
|
self.username = username
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "pylast.Artist(%s, %s)" %(repr(self.get_name()), repr(self.network))
|
return "pylast.Artist(%s, %s)" %(repr(self.get_name()), repr(self.network))
|
||||||
|
@ -1416,6 +1392,16 @@ class Artist(_BaseObject, _Taggable):
|
||||||
|
|
||||||
return _number(_extract(self._request("artist.getInfo", True), "playcount"))
|
return _number(_extract(self._request("artist.getInfo", True), "playcount"))
|
||||||
|
|
||||||
|
def get_userplaycount(self):
|
||||||
|
"""Returns the number of plays by a given username"""
|
||||||
|
|
||||||
|
if not self.username: return
|
||||||
|
|
||||||
|
params = self._get_params()
|
||||||
|
params['username'] = self.username
|
||||||
|
|
||||||
|
return _number(_extract(self._request("artist.getInfo", True, params), "userplaycount"))
|
||||||
|
|
||||||
def get_mbid(self):
|
def get_mbid(self):
|
||||||
"""Returns the MusicBrainz ID of this artist."""
|
"""Returns the MusicBrainz ID of this artist."""
|
||||||
|
|
||||||
|
@ -1749,7 +1735,7 @@ class Event(_BaseObject):
|
||||||
v = doc.getElementsByTagName("venue")[0]
|
v = doc.getElementsByTagName("venue")[0]
|
||||||
venue_id = _number(_extract(v, "id"))
|
venue_id = _number(_extract(v, "id"))
|
||||||
|
|
||||||
return Venue(venue_id, self.network)
|
return Venue(venue_id, self.network, venue_element=v)
|
||||||
|
|
||||||
def get_start_date(self):
|
def get_start_date(self):
|
||||||
"""Returns the date when the event starts."""
|
"""Returns the date when the event starts."""
|
||||||
|
@ -2358,8 +2344,9 @@ class Track(_BaseObject, _Taggable):
|
||||||
|
|
||||||
artist = None
|
artist = None
|
||||||
title = None
|
title = None
|
||||||
|
username = None
|
||||||
|
|
||||||
def __init__(self, artist, title, network):
|
def __init__(self, artist, title, network, username=None):
|
||||||
_BaseObject.__init__(self, network)
|
_BaseObject.__init__(self, network)
|
||||||
_Taggable.__init__(self, 'track')
|
_Taggable.__init__(self, 'track')
|
||||||
|
|
||||||
|
@ -2370,6 +2357,8 @@ class Track(_BaseObject, _Taggable):
|
||||||
|
|
||||||
self.title = title
|
self.title = title
|
||||||
|
|
||||||
|
self.username = username
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "pylast.Track(%s, %s, %s)" %(repr(self.artist.name), repr(self.title), repr(self.network))
|
return "pylast.Track(%s, %s, %s)" %(repr(self.artist.name), repr(self.title), repr(self.network))
|
||||||
|
|
||||||
|
@ -2441,6 +2430,17 @@ class Track(_BaseObject, _Taggable):
|
||||||
doc = self._request("track.getInfo", True)
|
doc = self._request("track.getInfo", True)
|
||||||
return _number(_extract(doc, "playcount"))
|
return _number(_extract(doc, "playcount"))
|
||||||
|
|
||||||
|
def get_userplaycount(self):
|
||||||
|
"""Returns the number of plays by a given username"""
|
||||||
|
|
||||||
|
if not self.username: return
|
||||||
|
|
||||||
|
params = self._get_params()
|
||||||
|
params['username'] = self.username
|
||||||
|
|
||||||
|
doc = self._request("track.getInfo", True, params)
|
||||||
|
return _number(_extract(doc, "userplaycount"))
|
||||||
|
|
||||||
def is_streamable(self):
|
def is_streamable(self):
|
||||||
"""Returns True if the track is available at Last.fm."""
|
"""Returns True if the track is available at Last.fm."""
|
||||||
|
|
||||||
|
@ -2835,6 +2835,24 @@ class User(_BaseObject):
|
||||||
|
|
||||||
return events
|
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
|
||||||
|
|
||||||
|
seq = []
|
||||||
|
for track in _collect_nodes(None, self, "user.getArtistTracks", False, params):
|
||||||
|
title = _extract(track, "name")
|
||||||
|
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):
|
def get_friends(self, limit = 50):
|
||||||
"""Returns a list of the user's friends. """
|
"""Returns a list of the user's friends. """
|
||||||
|
|
||||||
|
@ -2928,7 +2946,7 @@ class User(_BaseObject):
|
||||||
artist = _extract(e, 'artist')
|
artist = _extract(e, 'artist')
|
||||||
title = _extract(e, 'name')
|
title = _extract(e, 'name')
|
||||||
|
|
||||||
return Track(artist, title, self.network)
|
return Track(artist, title, self.network, self.name)
|
||||||
|
|
||||||
|
|
||||||
def get_recent_tracks(self, limit = 10):
|
def get_recent_tracks(self, limit = 10):
|
||||||
|
@ -3441,13 +3459,25 @@ class Venue(_BaseObject):
|
||||||
"""A venue where events are held."""
|
"""A venue where events are held."""
|
||||||
|
|
||||||
# TODO: waiting for a venue.getInfo web service to use.
|
# 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
|
id = None
|
||||||
|
info = None
|
||||||
|
name = None
|
||||||
|
location = None
|
||||||
|
url = None
|
||||||
|
|
||||||
def __init__(self, id, network):
|
def __init__(self, id, network, venue_element=None):
|
||||||
_BaseObject.__init__(self, network)
|
_BaseObject.__init__(self, network)
|
||||||
|
|
||||||
self.id = _number(id)
|
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):
|
def __repr__(self):
|
||||||
return "pylast.Venue(%s, %s)" %(repr(self.id), repr(self.network))
|
return "pylast.Venue(%s, %s)" %(repr(self.id), repr(self.network))
|
||||||
|
@ -3467,6 +3497,21 @@ class Venue(_BaseObject):
|
||||||
|
|
||||||
return self.id
|
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):
|
def get_upcoming_events(self):
|
||||||
"""Returns the upcoming events in this venue."""
|
"""Returns the upcoming events in this venue."""
|
||||||
|
|
||||||
|
@ -3534,7 +3579,7 @@ def _string(text):
|
||||||
|
|
||||||
def _collect_nodes(limit, sender, method_name, cacheable, params=None):
|
def _collect_nodes(limit, sender, method_name, cacheable, params=None):
|
||||||
"""
|
"""
|
||||||
Returns a sequqnce of dom.Node objects about as close to
|
Returns a sequence of dom.Node objects about as close to
|
||||||
limit as possible
|
limit as possible
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -3559,7 +3604,7 @@ def _collect_nodes(limit, sender, method_name, cacheable, params=None):
|
||||||
raise Exception("No total pages attribute")
|
raise Exception("No total pages attribute")
|
||||||
|
|
||||||
for node in main.childNodes:
|
for node in main.childNodes:
|
||||||
if not node.nodeType == xml.dom.Node.TEXT_NODE and len(nodes) < limit:
|
if not node.nodeType == xml.dom.Node.TEXT_NODE and (not limit or (len(nodes) < limit)):
|
||||||
nodes.append(node)
|
nodes.append(node)
|
||||||
|
|
||||||
if page >= total_pages:
|
if page >= total_pages:
|
||||||
|
@ -3576,10 +3621,39 @@ def _extract(node, name, index = 0):
|
||||||
|
|
||||||
if len(nodes):
|
if len(nodes):
|
||||||
if nodes[index].firstChild:
|
if nodes[index].firstChild:
|
||||||
return _unescape_htmlentity(nodes[index].firstChild.wholeText.strip())
|
return _unescape_htmlentity(nodes[index].firstChild.data.strip())
|
||||||
else:
|
else:
|
||||||
return None
|
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:
|
||||||
|
targetDict[node.tagName] = _unescape_htmlentity(node.firstChild.data.strip())
|
||||||
|
|
||||||
|
return targetDict
|
||||||
|
|
||||||
|
return _recurse_build_tree(node, {})
|
||||||
|
|
||||||
def _extract_all(node, name, limit_count = None):
|
def _extract_all(node, name, limit_count = None):
|
||||||
"""Extracts all the values from the xml string. returning a list."""
|
"""Extracts all the values from the xml string. returning a list."""
|
||||||
|
|
||||||
|
|
7
setup.py
Normal file → Executable file
7
setup.py
Normal file → Executable file
|
@ -1,8 +1,5 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
try:
|
|
||||||
from setuptools import setup
|
|
||||||
except:
|
|
||||||
from distutils.core import setup
|
from distutils.core import setup
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
@ -25,11 +22,11 @@ def get_build():
|
||||||
return str(build)
|
return str(build)
|
||||||
|
|
||||||
setup(name = "pylast",
|
setup(name = "pylast",
|
||||||
version = "0.5." + get_build(),
|
version = "0.1+0.5." + get_build(),
|
||||||
author = "Amr Hassan <amr.hassan@gmail.com>",
|
author = "Amr Hassan <amr.hassan@gmail.com>",
|
||||||
description = "A Python interface to Last.fm (and other API compatible social networks)",
|
description = "A Python interface to Last.fm (and other API compatible social networks)",
|
||||||
author_email = "amr.hassan@gmail.com",
|
author_email = "amr.hassan@gmail.com",
|
||||||
url = "http://code.google.com/p/pylast/",
|
url = "https://github.com/Elizacat/",
|
||||||
py_modules = ("pylast",),
|
py_modules = ("pylast",),
|
||||||
license = "Apache2"
|
license = "Apache2"
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue