diff --git a/.build b/.build deleted file mode 100644 index c227083..0000000 --- a/.build +++ /dev/null @@ -1 +0,0 @@ -0 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 23b3b3f..60b5c14 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,5 @@ docs/_build/ test_pylast.yaml lastfm.txt.pkl secrets.sh + +.dir-locals.el \ No newline at end of file diff --git a/README.md b/README.md index d089d68..5d22d6b 100644 --- a/README.md +++ b/README.md @@ -11,12 +11,10 @@ Try using the pydoc utility for help on usage or see [test_pylast.py](test_pylas Installation ------------ -The easiest way is via pip: +Install via pip: pip install pylast -Or copy [pylast.py](pylast.py) to somewhere your Python can see it. No other dependencies are needed. - Features -------- @@ -27,7 +25,6 @@ Features * Full object-oriented design. * Proxy support. * 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. * Python 3-friendly (Starting from 0.5). @@ -70,9 +67,9 @@ More examples in hugovk/lastfm- 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 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 ``` -To run all: +To run all unit and integration tests: ```sh -pip install -r test_requirements.txt -./test_pylast.py +pip install pytest +py.test ``` -Or run just one: +Or run just one test case: ```sh -./test_pylast.py -1 test_scrobble -``` - -Or all those tests matching a term: -```sh -./test_pylast.py -m geo +py.test -k test_scrobble ``` To run with coverage: ```sh -coverage run --source=pylast ./test_pylast.py +py.test -v --cov pylast --cov-report term-missing coverage report # for command-line report coverage html # for HTML report open htmlcov/index.html ``` - -To perform some static analysis: -```sh -./check.sh -``` diff --git a/clonedigger.sh b/clonedigger.sh new file mode 100755 index 0000000..96dc493 --- /dev/null +++ b/clonedigger.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +clonedigger pylast +grep -E "Clones detected|lines are duplicates" output.html +exit 0 diff --git a/pylast/__init__.py b/pylast/__init__.py index 7020eee..38f3727 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -20,7 +20,7 @@ # # http://code.google.com/p/pylast/ -__version__ = '1.0.0' +__version__ = '1.0.1' __author__ = 'Amr Hassan, hugovk' __copyright__ = "Copyright (C) 2008-2010 Amr Hassan, 2013-2014 hugovk" __license__ = "apache2" @@ -37,6 +37,8 @@ import collections import warnings import re +import six + def _deprecation_warning(message): warnings.warn(message, DeprecationWarning) @@ -1326,9 +1328,9 @@ class _BaseObject(object): def __hash__(self): # 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) ).lower()) @@ -1910,9 +1912,12 @@ class Artist(_BaseObject, _Taggable): return "pylast.Artist(%s, %s)" % ( repr(self.get_name()), repr(self.network)) + def __unicode__(self): + return six.text_type(self.get_name()) + @_string_output def __str__(self): - return self.get_name() + return self.__unicode__() def __eq__(self, other): if type(self) is type(other): @@ -3966,40 +3971,22 @@ def md5(text): def _unicode(text): - if sys.version_info[0] == 3: - if type(text) in (bytes, bytearray): - return str(text, "utf-8") - elif type(text) == str: - return text - else: - 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) + if isinstance(text, six.binary_type): + return six.text_type(text, "utf-8") + elif isinstance(text, six.text_type): + return text + else: + return six.text_type(text) -def _string(text): +def _string(string): """For Python2 routines that can only process str type.""" - - if sys.version_info[0] == 3: - if type(text) != str: - return str(text) - else: - return text - - elif sys.version_info[0] == 2: - if type(text) == str: - return text - - if type(text) == int: - return str(text) - - return text.encode("utf-8") + if isinstance(string, str): + return string + casted = six.text_type(string) + if sys.version_info[0] == 2: + casted = casted.encode("utf-8") + return casted def _collect_nodes(limit, sender, method_name, cacheable, params=None): diff --git a/setup.py b/setup.py index 8e73099..f8657e4 100755 --- a/setup.py +++ b/setup.py @@ -6,8 +6,9 @@ from setuptools import setup, find_packages setup( name="pylast", - version="1.0.0", + version="1.0.1", author="Amr Hassan ", + install_requires=['six'], tests_require=['mock', 'pytest', 'coverage', 'pep8', 'pyyaml', 'pyflakes'], description=("A Python interface to Last.fm " "(and other API compatible social networks)"), @@ -24,9 +25,9 @@ setup( "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", - ], + ], keywords=["Last.fm", "music", "scrobble", "scrobbling"], - packages=find_packages(exclude=('tests*')), + packages=find_packages(exclude=('tests*',)), license="Apache2" ) diff --git a/tests/request_test.py b/tests/request_test.py deleted file mode 100644 index 24fdf66..0000000 --- a/tests/request_test.py +++ /dev/null @@ -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() diff --git a/tests/test_pylast.py b/tests/test_pylast.py index 36f5c60..37695bf 100755 --- a/tests/test_pylast.py +++ b/tests/test_pylast.py @@ -3,6 +3,7 @@ Integration (not unit) tests for pylast.py """ import os +import pytest from random import choice import sys import time @@ -19,10 +20,13 @@ def load_secrets(): doc = yaml.load(f) else: doc = {} - doc["username"] = os.environ['PYLAST_USERNAME'].strip() - doc["password_hash"] = os.environ['PYLAST_PASSWORD_HASH'].strip() - doc["api_key"] = os.environ['PYLAST_API_KEY'].strip() - doc["api_secret"] = os.environ['PYLAST_API_SECRET'].strip() + try: + doc["username"] = os.environ['PYLAST_USERNAME'].strip() + doc["password_hash"] = os.environ['PYLAST_PASSWORD_HASH'].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 diff --git a/tests/unicode_test.py b/tests/unicode_test.py new file mode 100644 index 0000000..511ecd1 --- /dev/null +++ b/tests/unicode_test.py @@ -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) diff --git a/tox.ini b/tox.ini index 21c569b..28796b2 100644 --- a/tox.ini +++ b/tox.ini @@ -8,6 +8,7 @@ deps = pyyaml pytest mock + ipdb pytest-cov commands = py.test -v --cov pylast --cov-report term-missing {posargs} @@ -27,4 +28,4 @@ commands = pyflakes tests pep8 pylast pep8 tests - clonedigger pylast -o /dev/stdout | grep -E "Clones detected\|lines are duplicates" \ No newline at end of file + ./clonedigger.sh \ No newline at end of file