Merge pull request #121 from pylast/develop

Merge develop to master
This commit is contained in:
Hugo 2015-01-10 10:48:45 +02:00
commit a698c8a7e9
10 changed files with 80 additions and 84 deletions

1
.build
View file

@ -1 +0,0 @@
0

2
.gitignore vendored
View file

@ -58,3 +58,5 @@ docs/_build/
test_pylast.yaml test_pylast.yaml
lastfm.txt.pkl lastfm.txt.pkl
secrets.sh secrets.sh
.dir-locals.el

View file

@ -11,12 +11,10 @@ Try using the pydoc utility for help on usage or see [test_pylast.py](test_pylas
Installation Installation
------------ ------------
The easiest way is via pip: Install via pip:
pip install pylast pip install pylast
Or copy [pylast.py](pylast.py) to somewhere your Python can see it. No other dependencies are needed.
Features Features
-------- --------
@ -27,7 +25,6 @@ Features
* Full object-oriented design. * Full object-oriented design.
* Proxy support. * Proxy support.
* Internal caching support for some web services calls (disabled by default). * Internal caching support for some web services calls (disabled by default).
* No extra dependencies but Python itself.
* Support for other API-compatible networks like Libre.fm. * Support for other API-compatible networks like Libre.fm.
* Python 3-friendly (Starting from 0.5). * Python 3-friendly (Starting from 0.5).
@ -70,9 +67,9 @@ More examples in <a href="https://github.com/hugovk/lastfm-tools">hugovk/lastfm-
Testing Testing
------- -------
[test_pylast.py](test_pylast.py) contains integration tests with Last.fm, and plenty of code examples. [tests/test_pylast.py](tests/test_pylast.py) contains integration tests with Last.fm, and plenty of code examples. Unit tests are also in the [tests/](tests/) directory.
You need a test account at Last.fm that will be cluttered with test data, and an API key and secret. Either copy [example_test_pylast.yaml](example_test_pylast.yaml) to test_pylast.yaml and fill out the credentials, or set them as environment variables like: For integration tests you need a test account at Last.fm that will be cluttered with test data, and an API key and secret. Either copy [example_test_pylast.yaml](example_test_pylast.yaml) to test_pylast.yaml and fill out the credentials, or set them as environment variables like:
```sh ```sh
export PYLAST_USERNAME=TODO_ENTER_YOURS_HERE export PYLAST_USERNAME=TODO_ENTER_YOURS_HERE
@ -81,31 +78,21 @@ export PYLAST_API_KEY=TODO_ENTER_YOURS_HERE
export PYLAST_API_SECRET=TODO_ENTER_YOURS_HERE export PYLAST_API_SECRET=TODO_ENTER_YOURS_HERE
``` ```
To run all: To run all unit and integration tests:
```sh ```sh
pip install -r test_requirements.txt pip install pytest
./test_pylast.py py.test
``` ```
Or run just one: Or run just one test case:
```sh ```sh
./test_pylast.py -1 test_scrobble py.test -k test_scrobble
```
Or all those tests matching a term:
```sh
./test_pylast.py -m geo
``` ```
To run with coverage: To run with coverage:
```sh ```sh
coverage run --source=pylast ./test_pylast.py py.test -v --cov pylast --cov-report term-missing
coverage report # for command-line report coverage report # for command-line report
coverage html # for HTML report coverage html # for HTML report
open htmlcov/index.html open htmlcov/index.html
``` ```
To perform some static analysis:
```sh
./check.sh
```

4
clonedigger.sh Executable file
View file

@ -0,0 +1,4 @@
#!/usr/bin/env bash
clonedigger pylast
grep -E "Clones detected|lines are duplicates" output.html
exit 0

View file

@ -20,7 +20,7 @@
# #
# http://code.google.com/p/pylast/ # http://code.google.com/p/pylast/
__version__ = '1.0.0' __version__ = '1.0.1'
__author__ = 'Amr Hassan, hugovk' __author__ = 'Amr Hassan, hugovk'
__copyright__ = "Copyright (C) 2008-2010 Amr Hassan, 2013-2014 hugovk" __copyright__ = "Copyright (C) 2008-2010 Amr Hassan, 2013-2014 hugovk"
__license__ = "apache2" __license__ = "apache2"
@ -37,6 +37,8 @@ import collections
import warnings import warnings
import re import re
import six
def _deprecation_warning(message): def _deprecation_warning(message):
warnings.warn(message, DeprecationWarning) warnings.warn(message, DeprecationWarning)
@ -1326,9 +1328,9 @@ class _BaseObject(object):
def __hash__(self): def __hash__(self):
# Convert any ints (or whatever) into strings # Convert any ints (or whatever) into strings
values = map(str, self._get_params().values()) values = map(six.text_type, self._get_params().values())
return hash(self.network) + hash(str(type(self)) + "".join( return hash(self.network) + hash(six.text_type(type(self)) + "".join(
list(self._get_params().keys()) + list(values) list(self._get_params().keys()) + list(values)
).lower()) ).lower())
@ -1910,9 +1912,12 @@ class Artist(_BaseObject, _Taggable):
return "pylast.Artist(%s, %s)" % ( return "pylast.Artist(%s, %s)" % (
repr(self.get_name()), repr(self.network)) repr(self.get_name()), repr(self.network))
def __unicode__(self):
return six.text_type(self.get_name())
@_string_output @_string_output
def __str__(self): def __str__(self):
return self.get_name() return self.__unicode__()
def __eq__(self, other): def __eq__(self, other):
if type(self) is type(other): if type(self) is type(other):
@ -3966,40 +3971,22 @@ def md5(text):
def _unicode(text): def _unicode(text):
if sys.version_info[0] == 3: if isinstance(text, six.binary_type):
if type(text) in (bytes, bytearray): return six.text_type(text, "utf-8")
return str(text, "utf-8") elif isinstance(text, six.text_type):
elif type(text) == str: return text
return text else:
else: return six.text_type(text)
return str(text)
elif sys.version_info[0] == 2:
if type(text) in (str,):
return unicode(text, "utf-8")
elif type(text) == unicode:
return text
else:
return unicode(text)
def _string(text): def _string(string):
"""For Python2 routines that can only process str type.""" """For Python2 routines that can only process str type."""
if isinstance(string, str):
if sys.version_info[0] == 3: return string
if type(text) != str: casted = six.text_type(string)
return str(text) if sys.version_info[0] == 2:
else: casted = casted.encode("utf-8")
return text return casted
elif sys.version_info[0] == 2:
if type(text) == str:
return text
if type(text) == int:
return str(text)
return text.encode("utf-8")
def _collect_nodes(limit, sender, method_name, cacheable, params=None): def _collect_nodes(limit, sender, method_name, cacheable, params=None):

View file

@ -6,8 +6,9 @@ from setuptools import setup, find_packages
setup( setup(
name="pylast", name="pylast",
version="1.0.0", version="1.0.1",
author="Amr Hassan <amr.hassan@gmail.com>", author="Amr Hassan <amr.hassan@gmail.com>",
install_requires=['six'],
tests_require=['mock', 'pytest', 'coverage', 'pep8', 'pyyaml', 'pyflakes'], tests_require=['mock', 'pytest', 'coverage', 'pep8', 'pyyaml', 'pyflakes'],
description=("A Python interface to Last.fm " description=("A Python interface to Last.fm "
"(and other API compatible social networks)"), "(and other API compatible social networks)"),
@ -24,9 +25,9 @@ setup(
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.3",
"Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.4",
], ],
keywords=["Last.fm", "music", "scrobble", "scrobbling"], keywords=["Last.fm", "music", "scrobble", "scrobbling"],
packages=find_packages(exclude=('tests*')), packages=find_packages(exclude=('tests*',)),
license="Apache2" license="Apache2"
) )

View file

@ -1,18 +0,0 @@
# -*- coding: utf-8 -*-
import mock
import pytest
import pylast
def mock_network():
return mock.Mock(
_get_ws_auth=mock.Mock(return_value=("", "", ""))
)
@pytest.mark.parametrize('unicode_artist', [u'\xe9lafdasfdsafdsa', u'ééééééé'])
def test_get_cache_key(unicode_artist):
request = pylast._Request(mock_network(), 'some_method',
params={'artist': unicode_artist})
request._get_cache_key()

View file

@ -3,6 +3,7 @@
Integration (not unit) tests for pylast.py Integration (not unit) tests for pylast.py
""" """
import os import os
import pytest
from random import choice from random import choice
import sys import sys
import time import time
@ -19,10 +20,13 @@ def load_secrets():
doc = yaml.load(f) doc = yaml.load(f)
else: else:
doc = {} doc = {}
doc["username"] = os.environ['PYLAST_USERNAME'].strip() try:
doc["password_hash"] = os.environ['PYLAST_PASSWORD_HASH'].strip() doc["username"] = os.environ['PYLAST_USERNAME'].strip()
doc["api_key"] = os.environ['PYLAST_API_KEY'].strip() doc["password_hash"] = os.environ['PYLAST_PASSWORD_HASH'].strip()
doc["api_secret"] = os.environ['PYLAST_API_SECRET'].strip() doc["api_key"] = os.environ['PYLAST_API_KEY'].strip()
doc["api_secret"] = os.environ['PYLAST_API_SECRET'].strip()
except KeyError:
pytest.skip("Missing environment variables: PYLAST_USERNAME etc.")
return doc return doc

29
tests/unicode_test.py Normal file
View file

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
import mock
import pytest
import six
import pylast
def mock_network():
return mock.Mock(
_get_ws_auth=mock.Mock(return_value=("", "", ""))
)
@pytest.mark.parametrize('artist', [
u'\xe9lafdasfdsafdsa', u'ééééééé',
pylast.Artist(u'B\xe9l', mock_network()),
'fdasfdsafsaf not unicode',
])
def test_get_cache_key(artist):
request = pylast._Request(mock_network(), 'some_method',
params={'artist': artist})
request._get_cache_key()
@pytest.mark.parametrize('obj', [pylast.Artist(u'B\xe9l', mock_network())])
def test_cast_and_hash(obj):
assert type(six.text_type(obj)) is six.text_type
assert isinstance(hash(obj), int)

View file

@ -8,6 +8,7 @@ deps =
pyyaml pyyaml
pytest pytest
mock mock
ipdb
pytest-cov pytest-cov
commands = py.test -v --cov pylast --cov-report term-missing {posargs} commands = py.test -v --cov pylast --cov-report term-missing {posargs}
@ -27,4 +28,4 @@ commands =
pyflakes tests pyflakes tests
pep8 pylast pep8 pylast
pep8 tests pep8 tests
clonedigger pylast -o /dev/stdout | grep -E "Clones detected\|lines are duplicates" ./clonedigger.sh