Merge pull request #198 from pylast/develop

Merge develop into master
This commit is contained in:
Hugo 2017-02-12 22:47:47 +02:00 committed by GitHub
commit 3f8b03f48c
5 changed files with 50 additions and 18 deletions

2
.gitignore vendored
View file

@ -1,5 +1,6 @@
# User Credentials
test_pylast.yaml
.envrc
# Byte-compiled / optimized / DLL files
__pycache__/
@ -12,6 +13,7 @@ __pycache__/
# Distribution / packaging
.Python
env/
.venv/
build/
develop-eggs/
dist/

View file

@ -38,11 +38,12 @@ sudo: false
install:
- travis_retry pip install tox==2.1.1
- travis_retry pip install coveralls
- travis_retry pip install coverage
script: tox
after_success:
- travis_retry pip install coveralls && coveralls
- travis_retry pip install codecov && codecov
- travis_retry pip install scrutinizer-ocular && ocular

View file

@ -1,7 +1,12 @@
pyLast
======
[![Build Status](https://travis-ci.org/pylast/pylast.svg?branch=develop)](https://travis-ci.org/pylast/pylast) [![PyPI version](https://img.shields.io/pypi/v/pylast.svg)](https://pypi.python.org/pypi/pylast/) [![PyPI downloads](https://img.shields.io/pypi/dm/pylast.svg)](https://pypi.python.org/pypi/pylast/) [![Coverage Status](https://coveralls.io/repos/pylast/pylast/badge.png?branch=develop)](https://coveralls.io/r/pylast/pylast?branch=develop) [![Code Health](https://landscape.io/github/pylast/pylast/develop/landscape.svg)](https://landscape.io/github/hugovk/pylast/develop)
[![Build status](https://travis-ci.org/pylast/pylast.svg?branch=develop)](https://travis-ci.org/pylast/pylast)
[![PyPI version](https://img.shields.io/pypi/v/pylast.svg)](https://pypi.python.org/pypi/pylast/)
[![PyPI downloads](https://img.shields.io/pypi/dm/pylast.svg)](https://pypi.python.org/pypi/pylast/)
[![Coverage (Codecov)](https://codecov.io/gh/pylast/pylast/branch/develop/graph/badge.svg)](https://codecov.io/gh/pylast/pylast)
[![Coverage (Coveralls)](https://coveralls.io/repos/github/pylast/pylast/badge.svg?branch=develop)](https://coveralls.io/github/pylast/pylast?branch=develop)
[![Code health](https://landscape.io/github/pylast/pylast/develop/landscape.svg)](https://landscape.io/github/hugovk/pylast/develop)
A Python interface to [Last.fm](http://www.last.fm/) and other API-compatible websites such as [Libre.fm](http://libre.fm/).
@ -38,16 +43,16 @@ Here's some simple code example to get you started. In order to create any objec
import pylast
# You have to have your own unique two values for API_KEY and API_SECRET
# Obtain yours from http://www.last.fm/api/account for Last.fm
API_KEY = "b25b959554ed76058ac220b7b2e0a026" # this is a sample key
# Obtain yours from http://www.last.fm/api/account/create for Last.fm
API_KEY = "b25b959554ed76058ac220b7b2e0a026" # this is a sample key
API_SECRET = "425b55975eed76058ac220b7b4e8a054"
# In order to perform a write operation you need to authenticate yourself
username = "your_user_name"
password_hash = pylast.md5("your_password")
network = pylast.LastFMNetwork(api_key = API_KEY, api_secret =
API_SECRET, username = username, password_hash = password_hash)
network = pylast.LastFMNetwork(api_key=API_KEY, api_secret=API_SECRET,
username=username, password_hash=password_hash)
# Now you can use that object everywhere
artist = network.get_artist("System of a Down")
@ -58,8 +63,8 @@ track = network.get_track("Iron Maiden", "The Nomad")
track.love()
track.add_tags(("awesome", "favorite"))
# Type help(pylast.LastFMNetwork) or help(pylast) in a Python interpreter to get more help
# about anything and see examples of how it works
# Type help(pylast.LastFMNetwork) or help(pylast) in a Python interpreter
# to get more help about anything and see examples of how it works
```
More examples in <a href="https://github.com/hugovk/lastfm-tools">hugovk/lastfm-tools</a> and [test_pylast.py](test_pylast.py).

View file

@ -211,7 +211,8 @@ class _Network(object):
def __init__(
self, name, homepage, ws_server, api_key, api_secret, session_key,
submission_server, username, password_hash, domain_names, urls):
submission_server, username, password_hash, domain_names, urls,
token=None):
"""
name: the name of the network
homepage: the homepage URL
@ -227,6 +228,7 @@ class _Network(object):
domain_names: a dict mapping each DOMAIN_* value to a string domain
name
urls: a dict mapping types to URLs
token: an authentication token to retrieve a session
if username and password_hash were provided and not session_key,
session_key will be generated automatically when needed.
@ -257,6 +259,12 @@ class _Network(object):
self.last_call_time = 0
self.limit_rate = False
# Load session_key from authentication token if provided
if token and not self.session_key:
sk_gen = SessionKeyGenerator(self)
self.session_key = sk_gen.get_web_auth_session_key(
url=None, token=token)
# Generate a session_key if necessary
if ((self.api_key and self.api_secret) and not self.session_key and
(self.username and self.password_hash)):
@ -886,7 +894,7 @@ class LastFMNetwork(_Network):
def __init__(
self, api_key="", api_secret="", session_key="", username="",
password_hash=""):
password_hash="", token=""):
_Network.__init__(
self,
name="Last.fm",
@ -898,6 +906,7 @@ class LastFMNetwork(_Network):
submission_server="http://post.audioscrobbler.com:80/",
username=username,
password_hash=password_hash,
token=token,
domain_names={
DOMAIN_ENGLISH: 'www.last.fm',
DOMAIN_GERMAN: 'www.lastfm.de',
@ -936,7 +945,7 @@ class LastFMNetwork(_Network):
def get_lastfm_network(
api_key="", api_secret="", session_key="", username="",
password_hash=""):
password_hash="", token=""):
"""
Returns a preconfigured _Network object for Last.fm
@ -946,12 +955,13 @@ def get_lastfm_network(
username: a username of a valid user
password_hash: the output of pylast.md5(password) where password is the
user's password
token: an authentication token to retrieve a session
if username and password_hash were provided and not session_key,
session_key will be generated automatically when needed.
Either a valid session_key or a combination of username and password_hash
must be present for scrobbling.
Either a valid session_key, a combination of username and password_hash,
or token must be present for scrobbling.
Most read-only webservices only require an api_key and an api_secret, see
about obtaining them from:
@ -961,7 +971,7 @@ def get_lastfm_network(
_deprecation_warning("Create a LastFMNetwork object instead")
return LastFMNetwork(
api_key, api_secret, session_key, username, password_hash)
api_key, api_secret, session_key, username, password_hash, token)
class LibreFMNetwork(_Network):
@ -1304,7 +1314,7 @@ class SessionKeyGenerator(object):
return url
def get_web_auth_session_key(self, url):
def get_web_auth_session_key(self, url, token=""):
"""
Retrieves the session key of a web authorization process by its url.
"""
@ -1312,9 +1322,8 @@ class SessionKeyGenerator(object):
if url in self.web_auth_tokens.keys():
token = self.web_auth_tokens[url]
else:
# That's going to raise a WSError of an unauthorized token when the
# request is executed.
token = ""
# This will raise a WSError if token is blank or unauthorized
token = token
request = _Request(self.network, 'auth.getSession', {'token': token})
@ -1344,6 +1353,7 @@ class SessionKeyGenerator(object):
return _extract(doc, "key")
TopItem = collections.namedtuple("TopItem", ["item", "weight"])
SimilarItem = collections.namedtuple("SimilarItem", ["item", "match"])
LibraryItem = collections.namedtuple(

View file

@ -2160,6 +2160,20 @@ class TestPyLast(unittest.TestCase):
# Assert
self.assertEqual(mbid, None)
def test_init_with_token(self):
# Arrange/Act
try:
pylast.LastFMNetwork(
api_key=self.__class__.secrets["api_key"],
api_secret=self.__class__.secrets["api_secret"],
token="invalid",
)
except pylast.WSError as exc:
msg = str(exc)
# Assert
self.assertEqual(msg, "Invalid authentication token supplied")
@flaky(max_runs=5, min_passes=1)
class TestPyLastWithLibreFm(unittest.TestCase):