Support authentication tokens to construct networks

Closes #193
This commit is contained in:
Jace Browning 2017-01-31 15:00:38 -05:00
parent 7b3ab250df
commit 80f391761c
3 changed files with 36 additions and 10 deletions

2
.gitignore vendored
View file

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

View file

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

View file

@ -2160,6 +2160,20 @@ class TestPyLast(unittest.TestCase):
# Assert # Assert
self.assertEqual(mbid, None) 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) @flaky(max_runs=5, min_passes=1)
class TestPyLastWithLibreFm(unittest.TestCase): class TestPyLastWithLibreFm(unittest.TestCase):