From da26531c87978f56ae2b3ef7e4be543f56eab334 Mon Sep 17 00:00:00 2001 From: hugovk Date: Thu, 8 Jan 2015 11:14:40 +0200 Subject: [PATCH 1/8] Skip if PYLAST_USERNAME etc. env vars missing --- tests/test_pylast.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) 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 From df75cf2aa2434224dbd99dbf5177d9bfc30558f8 Mon Sep 17 00:00:00 2001 From: Ivan Malison Date: Thu, 8 Jan 2015 01:03:52 -0800 Subject: [PATCH 2/8] Fix UnicodeDecodeError from #114. Replace definitions of _unicode and _string. Add six as a dependeny. Fix clonedigger script. --- .dir-locals.el | 1 + clonedigger.sh | 3 +++ pylast/__init__.py | 51 ++++++++++++++++--------------------------- setup.py | 1 + tests/request_test.py | 18 --------------- tests/unicode_test.py | 28 ++++++++++++++++++++++++ tox.ini | 3 ++- 7 files changed, 54 insertions(+), 51 deletions(-) create mode 100644 .dir-locals.el create mode 100755 clonedigger.sh delete mode 100644 tests/request_test.py create mode 100644 tests/unicode_test.py diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 0000000..427ee57 --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1 @@ +((nil . ((pytest-global-name . "source secrets.sh && tox -e py34 --")))) diff --git a/clonedigger.sh b/clonedigger.sh new file mode 100755 index 0000000..14a016f --- /dev/null +++ b/clonedigger.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +clonedigger pylast +! grep -E "Clones detected\|lines are duplicates" output.html diff --git a/pylast/__init__.py b/pylast/__init__.py index 7020eee..88b7ccb 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -37,6 +37,8 @@ import collections import warnings import re +import six + def _deprecation_warning(message): warnings.warn(message, DeprecationWarning) @@ -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..2d58901 100755 --- a/setup.py +++ b/setup.py @@ -8,6 +8,7 @@ setup( name="pylast", version="1.0.0", 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)"), 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/unicode_test.py b/tests/unicode_test.py new file mode 100644 index 0000000..d05c529 --- /dev/null +++ b/tests/unicode_test.py @@ -0,0 +1,28 @@ +# -*- 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(obj): + assert type(six.text_type(obj)) is six.text_type 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 From 3f9137a7002a8ef2c1dc312d48ea514632806ec7 Mon Sep 17 00:00:00 2001 From: Ivan Malison Date: Thu, 8 Jan 2015 15:00:03 -0800 Subject: [PATCH 3/8] Version bump, fix setup.py, which was previously broken because excludes was not a tuple, so each character was being excluded and thus pylast the package was being excluded. --- pylast/__init__.py | 2 +- setup.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index 88b7ccb..9c99afe 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" diff --git a/setup.py b/setup.py index 2d58901..f8657e4 100755 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ 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'], @@ -25,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" ) From de31fc33f6488169a5eb19816600c4670ec47bb5 Mon Sep 17 00:00:00 2001 From: Ivan Malison Date: Thu, 8 Jan 2015 16:05:28 -0800 Subject: [PATCH 4/8] fix base object hasing. Closes #120 . --- .dir-locals.el | 2 +- pylast/__init__.py | 4 ++-- tests/unicode_test.py | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.dir-locals.el b/.dir-locals.el index 427ee57..1fe46b5 100644 --- a/.dir-locals.el +++ b/.dir-locals.el @@ -1 +1 @@ -((nil . ((pytest-global-name . "source secrets.sh && tox -e py34 --")))) +((nil . ((pytest-global-name . "source secrets.sh && tox -e py27,py34 --")))) diff --git a/pylast/__init__.py b/pylast/__init__.py index 9c99afe..38f3727 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -1328,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()) diff --git a/tests/unicode_test.py b/tests/unicode_test.py index d05c529..511ecd1 100644 --- a/tests/unicode_test.py +++ b/tests/unicode_test.py @@ -24,5 +24,6 @@ def test_get_cache_key(artist): @pytest.mark.parametrize('obj', [pylast.Artist(u'B\xe9l', mock_network())]) -def test_cast(obj): +def test_cast_and_hash(obj): assert type(six.text_type(obj)) is six.text_type + assert isinstance(hash(obj), int) From 159925ff086431a57868c1c7dee0c302fe99e550 Mon Sep 17 00:00:00 2001 From: Ivan Malison Date: Thu, 8 Jan 2015 23:13:38 -0800 Subject: [PATCH 5/8] remove dir-locals. make clonedigger always exit 0 --- .dir-locals.el | 1 - .gitignore | 2 ++ clonedigger.sh | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) delete mode 100644 .dir-locals.el diff --git a/.dir-locals.el b/.dir-locals.el deleted file mode 100644 index 1fe46b5..0000000 --- a/.dir-locals.el +++ /dev/null @@ -1 +0,0 @@ -((nil . ((pytest-global-name . "source secrets.sh && tox -e py27,py34 --")))) 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/clonedigger.sh b/clonedigger.sh index 14a016f..1c80b8d 100755 --- a/clonedigger.sh +++ b/clonedigger.sh @@ -1,3 +1,4 @@ #!/usr/bin/env bash clonedigger pylast -! grep -E "Clones detected\|lines are duplicates" output.html +grep -E "Clones detected\|lines are duplicates" output.html +exit 0 From b24417ea562c40886c4732f1234274a9d9641f00 Mon Sep 17 00:00:00 2001 From: Ivan Malison Date: Thu, 8 Jan 2015 23:23:29 -0800 Subject: [PATCH 6/8] fix grep filter for clonedigger. --- clonedigger.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clonedigger.sh b/clonedigger.sh index 1c80b8d..96dc493 100755 --- a/clonedigger.sh +++ b/clonedigger.sh @@ -1,4 +1,4 @@ #!/usr/bin/env bash clonedigger pylast -grep -E "Clones detected\|lines are duplicates" output.html +grep -E "Clones detected|lines are duplicates" output.html exit 0 From 959622fcf2c6e6b3fb7fa7cfb9b4d74414ac7453 Mon Sep 17 00:00:00 2001 From: Hugo Date: Fri, 9 Jan 2015 10:38:06 +0200 Subject: [PATCH 7/8] Remove unused .build file --- .build | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .build 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 From 925ea840c43158ce2b2db1043e20ccb9dcd52649 Mon Sep 17 00:00:00 2001 From: Hugo Date: Fri, 9 Jan 2015 12:53:43 +0200 Subject: [PATCH 8/8] Update testing instructions [CI skip] --- README.md | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) 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 -```