tunigo-1.0.0/0000755000175000017500000000000012655637226013715 5ustar trygvetrygve00000000000000tunigo-1.0.0/.travis.yml0000644000175000017500000000100712655635161016020 0ustar trygvetrygve00000000000000sudo: false language: python env: - TOX_ENV=py27 - TOX_ENV=py33 - TOX_ENV=py34 # - TOX_ENV=py35 - TOX_ENV=pypy - TOX_ENV=pypy3 - TOX_ENV=flake8 # It is currently necessary to specify python 3.5 when using py35 on Travis, # see https://github.com/travis-ci/travis-ci/issues/4794 matrix: include: - python: 3.5 env: - TOX_ENV=py35 install: - "pip install tox" script: - "tox -e $TOX_ENV" after_success: - "if [ $TOX_ENV == 'py27' ]; then pip install coveralls; coveralls; fi" tunigo-1.0.0/tox.ini0000644000175000017500000000064712655632275015236 0ustar trygvetrygve00000000000000[flake8] application-import-names = tunigo, tests exclude = .git, .tox [tox] envlist = py27, py33, py34, py35, pypy, pypy3, flake8 [testenv] deps = -rdev-requirements.txt commands = py.test \ --basetemp={envtmpdir} \ --cov=tunigo --cov-report=term-missing \ {posargs} [testenv:flake8] deps = flake8 flake8-import-order pep8-naming commands = flake8 --show-source --statistics tunigo-1.0.0/LICENSE0000644000175000017500000002613612436376176014733 0ustar trygvetrygve00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. tunigo-1.0.0/PKG-INFO0000644000175000017500000001175612655637226015024 0ustar trygvetrygve00000000000000Metadata-Version: 1.1 Name: tunigo Version: 1.0.0 Summary: Python API for the browse feature of Spotify Home-page: https://github.com/trygveaa/python-tunigo Author: Trygve Aaberge Author-email: trygveaa@gmail.com License: Apache License, Version 2.0 Description: ************* Python-Tunigo ************* .. image:: https://img.shields.io/pypi/v/tunigo.svg?style=flat :target: https://pypi.python.org/pypi/tunigo/ :alt: Latest PyPI version .. image:: https://img.shields.io/pypi/dm/tunigo.svg?style=flat :target: https://pypi.python.org/pypi/tunigo/ :alt: Number of PyPI downloads .. image:: https://img.shields.io/travis/trygveaa/python-tunigo/master.png?style=flat :target: https://travis-ci.org/trygveaa/python-tunigo :alt: Travis CI build status .. image:: https://img.shields.io/coveralls/trygveaa/python-tunigo/master.svg?style=flat :target: https://coveralls.io/r/trygveaa/python-tunigo?branch=master :alt: Test coverage Python-Tunigo is a python package that allows for simple access to `Tunigo `_'s API. This is an API for fetching featured playlists and new releases for `Spotify `_. It supports featured playlists, top playlists, new album releases and playlists for a range of different genres. Tunigo's API is what the Spotify client uses to provide its Browse-feature. Note that the API is not documented or officially released, so it may change at any time. Installation ============ Debian/Ubuntu/Raspbian: Install the ``python-tunigo`` or the ``python3-tunigo`` package from `apt.mopidy.com `_:: sudo apt-get install python-tunigo sudo apt-get install python3-tunigo Arch Linux: Install the ``python2-tunigo`` or the ``python-tunigo`` package from `AUR `_, e.g.:: yaourt -S python2-tunigo yaourt -S python-tunigo Else: Install the ``tunigo`` package from PyPI:: pip install tunigo Examples ======== .. code-block:: python import tunigo tunigo = tunigo.Tunigo() for playlist in tunigo.get_featured_playlists(): print(playlist.title) See the ``examples/`` directory for further examples. License ======= Python-Tunigo is licensed under the `Apache License, Version 2.0 `_. Project resources ================= - `Source code `_ - `Issue tracker `_ - `Download development snapshot `_ Changelog ========= v1.0.0 (2016-02-07) ------------------- - Add support for specifying a proxy to use. - Don't specify region in API call if not given. (Fixes: #2) - Add the new field Release.artist_uri. - Fix type of Release.created to be int all places. - Add tests for all classes. v0.1.3 (2014-11-29) ------------------- - Fix check for content-type so it doesn't fail after a change in the API. v0.1.2 (2014-08-03) ------------------- - Fix that some genres were not listed by using the same query options as play.spotify.com. v0.1.1 (2014-07-21) ------------------- - Allow Genre- and SubGenre-objects as arguments to get_genre_playlists. - Allow a SubGenre to be created with main_genre as a string. - Add __repr__ and __str__ methods to classes. v0.1.0 (2014-06-24) ------------------- - Initial release. Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development :: Libraries tunigo-1.0.0/examples/0000755000175000017500000000000012655637226015533 5ustar trygvetrygve00000000000000tunigo-1.0.0/examples/playlists.py0000644000175000017500000000072212655546014020124 0ustar trygvetrygve00000000000000from tunigo import Tunigo tunigo = Tunigo() # Returns the currently featured playlists. featured_playlists = tunigo.get_featured_playlists() # Returns playlists of top tracks in various categories, as well as other # popular playlists. top_lists = tunigo.get_top_lists() # Print the title and URI for each playlist. # All the available fields can be seen in tunigo/playlist.py for playlist in top_lists: print('{} ({})'.format(playlist.title, playlist.uri)) tunigo-1.0.0/examples/genres.py0000644000175000017500000000143112655546014017361 0ustar trygvetrygve00000000000000from tunigo import Tunigo tunigo = Tunigo() # Returns the available genres, including sub genres and a playlist containing # top tracks for each genre. genres = tunigo.get_genres() # Returns playlists for a genre. genre_playlists = tunigo.get_genre_playlists(genres[0]) # Or use a specific genre key. genre_playlists = tunigo.get_genre_playlists('rock') # Returns playlists for a sub genre. genre_playlists = tunigo.get_genre_playlists(sub_genre=genres[0].sub_genres[0]) # Or use specific genre and sub genre keys. genre_playlists = tunigo.get_genre_playlists('rock', 'metal') # Print the name and number of playlists for each genre. # All the available fields can be seen in tunigo/genre.py for genre in genres: print('{}: {} playlists'.format(genre.name, genre.number_playlists)) tunigo-1.0.0/examples/new_releases.py0000644000175000017500000000064712655546014020562 0ustar trygvetrygve00000000000000from tunigo import Tunigo tunigo = Tunigo() # Returns new album releases. releases = tunigo.get_new_releases() # Print the artist and album name, as well as the URI for each release. # All the available fields can be seen in tunigo/release.py. for release in releases: print('{} - {} ({})'.format(release.artist_name, release.album_name, release.uri)) tunigo-1.0.0/examples/set_config.py0000644000175000017500000000052112655546014020215 0ustar trygvetrygve00000000000000from tunigo import Tunigo tunigo = Tunigo( # Region to use in API calls. Should be a two letter country code. # Defaults to 'all'. region='no', # Max number of results for each API call. Defaults to 1000. max_results=100, # Number of seconds to cache results from the API. Defaults to 3600. cache_time=60) tunigo-1.0.0/setup.py0000644000175000017500000000275612655632400015426 0ustar trygvetrygve00000000000000from __future__ import unicode_literals import re from setuptools import find_packages, setup def get_version(filename): content = open(filename).read() metadata = dict(re.findall("__([a-z]+)__ = '([^']+)'", content)) return metadata['version'] setup( name='tunigo', version=get_version('tunigo/__init__.py'), url='https://github.com/trygveaa/python-tunigo', license='Apache License, Version 2.0', author='Trygve Aaberge', author_email='trygveaa@gmail.com', description='Python API for the browse feature of Spotify', long_description=open('README.rst').read(), packages=find_packages(exclude=['tests', 'tests.*']), zip_safe=False, include_package_data=True, install_requires=[ 'requests >= 2.0.0', 'setuptools', ], classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: Apache Software License', 'Operating System :: OS Independent', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Software Development :: Libraries', ], ) tunigo-1.0.0/setup.cfg0000644000175000017500000000023012655637226015531 0ustar trygvetrygve00000000000000[flake8] application-import-names = tunigo,tests exclude = .git,.tox [wheel] universal = 1 [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 tunigo-1.0.0/dev-requirements.txt0000644000175000017500000000021612655546175017756 0ustar trygvetrygve00000000000000# Check code style, errors, etc flake8 flake8-import-order # Mock dependencies in tests mock responses # Test runners pytest pytest-cov tox tunigo-1.0.0/MANIFEST.in0000644000175000017500000000031612655546175015455 0ustar trygvetrygve00000000000000include *.py include *.rst include *.txt include .travis.yml include LICENSE include MANIFEST.in include tox.ini recursive-include examples *.py recursive-include tests *.py global-exclude __pycache__/* tunigo-1.0.0/tunigo.egg-info/0000755000175000017500000000000012655637226016714 5ustar trygvetrygve00000000000000tunigo-1.0.0/tunigo.egg-info/PKG-INFO0000644000175000017500000001175612655637226020023 0ustar trygvetrygve00000000000000Metadata-Version: 1.1 Name: tunigo Version: 1.0.0 Summary: Python API for the browse feature of Spotify Home-page: https://github.com/trygveaa/python-tunigo Author: Trygve Aaberge Author-email: trygveaa@gmail.com License: Apache License, Version 2.0 Description: ************* Python-Tunigo ************* .. image:: https://img.shields.io/pypi/v/tunigo.svg?style=flat :target: https://pypi.python.org/pypi/tunigo/ :alt: Latest PyPI version .. image:: https://img.shields.io/pypi/dm/tunigo.svg?style=flat :target: https://pypi.python.org/pypi/tunigo/ :alt: Number of PyPI downloads .. image:: https://img.shields.io/travis/trygveaa/python-tunigo/master.png?style=flat :target: https://travis-ci.org/trygveaa/python-tunigo :alt: Travis CI build status .. image:: https://img.shields.io/coveralls/trygveaa/python-tunigo/master.svg?style=flat :target: https://coveralls.io/r/trygveaa/python-tunigo?branch=master :alt: Test coverage Python-Tunigo is a python package that allows for simple access to `Tunigo `_'s API. This is an API for fetching featured playlists and new releases for `Spotify `_. It supports featured playlists, top playlists, new album releases and playlists for a range of different genres. Tunigo's API is what the Spotify client uses to provide its Browse-feature. Note that the API is not documented or officially released, so it may change at any time. Installation ============ Debian/Ubuntu/Raspbian: Install the ``python-tunigo`` or the ``python3-tunigo`` package from `apt.mopidy.com `_:: sudo apt-get install python-tunigo sudo apt-get install python3-tunigo Arch Linux: Install the ``python2-tunigo`` or the ``python-tunigo`` package from `AUR `_, e.g.:: yaourt -S python2-tunigo yaourt -S python-tunigo Else: Install the ``tunigo`` package from PyPI:: pip install tunigo Examples ======== .. code-block:: python import tunigo tunigo = tunigo.Tunigo() for playlist in tunigo.get_featured_playlists(): print(playlist.title) See the ``examples/`` directory for further examples. License ======= Python-Tunigo is licensed under the `Apache License, Version 2.0 `_. Project resources ================= - `Source code `_ - `Issue tracker `_ - `Download development snapshot `_ Changelog ========= v1.0.0 (2016-02-07) ------------------- - Add support for specifying a proxy to use. - Don't specify region in API call if not given. (Fixes: #2) - Add the new field Release.artist_uri. - Fix type of Release.created to be int all places. - Add tests for all classes. v0.1.3 (2014-11-29) ------------------- - Fix check for content-type so it doesn't fail after a change in the API. v0.1.2 (2014-08-03) ------------------- - Fix that some genres were not listed by using the same query options as play.spotify.com. v0.1.1 (2014-07-21) ------------------- - Allow Genre- and SubGenre-objects as arguments to get_genre_playlists. - Allow a SubGenre to be created with main_genre as a string. - Add __repr__ and __str__ methods to classes. v0.1.0 (2014-06-24) ------------------- - Initial release. Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development :: Libraries tunigo-1.0.0/tunigo.egg-info/requires.txt0000644000175000017500000000003512655637226021312 0ustar trygvetrygve00000000000000requests >= 2.0.0 setuptools tunigo-1.0.0/tunigo.egg-info/top_level.txt0000644000175000017500000000000712655637226021443 0ustar trygvetrygve00000000000000tunigo tunigo-1.0.0/tunigo.egg-info/dependency_links.txt0000644000175000017500000000000112655637226022762 0ustar trygvetrygve00000000000000 tunigo-1.0.0/tunigo.egg-info/SOURCES.txt0000644000175000017500000000122612655637226020601 0ustar trygvetrygve00000000000000.travis.yml LICENSE MANIFEST.in README.rst dev-requirements.txt setup.cfg setup.py tox.ini examples/genres.py examples/new_releases.py examples/playlists.py examples/set_config.py tests/__init__.py tests/test_api.py tests/test_cache.py tests/test_genre.py tests/test_playlist.py tests/test_release.py tests/test_translator.py tests/test_utils.py tunigo/__init__.py tunigo/api.py tunigo/cache.py tunigo/genre.py tunigo/playlist.py tunigo/release.py tunigo/translator.py tunigo/utils.py tunigo.egg-info/PKG-INFO tunigo.egg-info/SOURCES.txt tunigo.egg-info/dependency_links.txt tunigo.egg-info/not-zip-safe tunigo.egg-info/requires.txt tunigo.egg-info/top_level.txttunigo-1.0.0/tunigo.egg-info/not-zip-safe0000644000175000017500000000000112655326256021140 0ustar trygvetrygve00000000000000 tunigo-1.0.0/README.rst0000644000175000017500000000620612655633704015405 0ustar trygvetrygve00000000000000************* Python-Tunigo ************* .. image:: https://img.shields.io/pypi/v/tunigo.svg?style=flat :target: https://pypi.python.org/pypi/tunigo/ :alt: Latest PyPI version .. image:: https://img.shields.io/pypi/dm/tunigo.svg?style=flat :target: https://pypi.python.org/pypi/tunigo/ :alt: Number of PyPI downloads .. image:: https://img.shields.io/travis/trygveaa/python-tunigo/master.png?style=flat :target: https://travis-ci.org/trygveaa/python-tunigo :alt: Travis CI build status .. image:: https://img.shields.io/coveralls/trygveaa/python-tunigo/master.svg?style=flat :target: https://coveralls.io/r/trygveaa/python-tunigo?branch=master :alt: Test coverage Python-Tunigo is a python package that allows for simple access to `Tunigo `_'s API. This is an API for fetching featured playlists and new releases for `Spotify `_. It supports featured playlists, top playlists, new album releases and playlists for a range of different genres. Tunigo's API is what the Spotify client uses to provide its Browse-feature. Note that the API is not documented or officially released, so it may change at any time. Installation ============ Debian/Ubuntu/Raspbian: Install the ``python-tunigo`` or the ``python3-tunigo`` package from `apt.mopidy.com `_:: sudo apt-get install python-tunigo sudo apt-get install python3-tunigo Arch Linux: Install the ``python2-tunigo`` or the ``python-tunigo`` package from `AUR `_, e.g.:: yaourt -S python2-tunigo yaourt -S python-tunigo Else: Install the ``tunigo`` package from PyPI:: pip install tunigo Examples ======== .. code-block:: python import tunigo tunigo = tunigo.Tunigo() for playlist in tunigo.get_featured_playlists(): print(playlist.title) See the ``examples/`` directory for further examples. License ======= Python-Tunigo is licensed under the `Apache License, Version 2.0 `_. Project resources ================= - `Source code `_ - `Issue tracker `_ - `Download development snapshot `_ Changelog ========= v1.0.0 (2016-02-07) ------------------- - Add support for specifying a proxy to use. - Don't specify region in API call if not given. (Fixes: #2) - Add the new field Release.artist_uri. - Fix type of Release.created to be int all places. - Add tests for all classes. v0.1.3 (2014-11-29) ------------------- - Fix check for content-type so it doesn't fail after a change in the API. v0.1.2 (2014-08-03) ------------------- - Fix that some genres were not listed by using the same query options as play.spotify.com. v0.1.1 (2014-07-21) ------------------- - Allow Genre- and SubGenre-objects as arguments to get_genre_playlists. - Allow a SubGenre to be created with main_genre as a string. - Add __repr__ and __str__ methods to classes. v0.1.0 (2014-06-24) ------------------- - Initial release. tunigo-1.0.0/tunigo/0000755000175000017500000000000012655637226015222 5ustar trygvetrygve00000000000000tunigo-1.0.0/tunigo/utils.py0000644000175000017500000000150712655546175016741 0ustar trygvetrygve00000000000000from __future__ import unicode_literals from tunigo import translator def set_instance_variables(instance, keys, values, convert): for key_underscore in keys: key_camelcase = translator.underscore_to_camelcase(key_underscore) if key_camelcase in values: setattr(instance, key_underscore, convert(values[key_camelcase])) else: setattr(instance, key_underscore, convert(None)) def set_instance_array_variables(instance, keys, values): set_instance_variables(instance, keys, values, lambda x: x or []) def set_instance_int_variables(instance, keys, values): set_instance_variables( instance, keys, values, lambda x: int(x) if x else 0) def set_instance_string_variables(instance, keys, values): set_instance_variables(instance, keys, values, lambda x: x or '') tunigo-1.0.0/tunigo/playlist.py0000644000175000017500000000751212655546175017444 0ustar trygvetrygve00000000000000from __future__ import unicode_literals import tunigo from tunigo import utils class Playlist(object): def __init__( self, created=0, description='', id='', image='', location='', main_genre=None, main_genre_template='', num_subscribers=0, sub_genre=None, sub_genre_template='', title='', updated=0, uri='', version=0, item_array=None): if item_array: utils.set_instance_int_variables( self, ['_created', '_num_subscribers', '_updated', '_version'], item_array) utils.set_instance_string_variables( self, ['_description', '_id', '_image', '_location', '_title', '_uri'], item_array) if 'mainGenreTemplate' in item_array: self._main_genre = tunigo.Genre( playlist=self, template_name=item_array['mainGenreTemplate']) else: self._main_genre = tunigo.Genre(playlist=self) if 'subGenreTemplate' in item_array: self._sub_genre = tunigo.SubGenre( key=item_array['subGenreTemplate'], main_genre=self._main_genre) else: self._sub_genre = tunigo.SubGenre(main_genre=self._main_genre) else: self._created = int(created) self._description = description self._id = id self._image = image self._location = location if main_genre_template: self._main_genre = tunigo.Genre( playlist=self, template_name=main_genre_template) elif isinstance(main_genre, tunigo.Genre): self._main_genre = main_genre self._main_genre._playlist = self else: self._main_genre = tunigo.Genre(playlist=self) self._num_subscribers = int(num_subscribers) if sub_genre_template: self._sub_genre = tunigo.SubGenre( key=sub_genre_template, main_genre=self._main_genre) elif isinstance(sub_genre, tunigo.SubGenre): self._sub_genre = sub_genre self._sub_genre._main_genre = self._main_genre else: self._sub_genre = tunigo.SubGenre(main_genre=self._main_genre) self._title = title self._updated = int(updated) self._uri = uri self._version = int(version) def __repr__(self): return "Playlist(uri='{}')".format(self._uri) def __str__(self): return '{} ({})'.format(self._title, self._uri) @property def created(self): return self._created @property def description(self): return self._description @property def id(self): return self._id @property def image(self): return self._image @property def location(self): return self._location @property def main_genre(self): return self._main_genre @property def main_genre_template(self): return self._main_genre.template_name @property def num_subscribers(self): return self._num_subscribers @property def sub_genre(self): return self._sub_genre @property def sub_genre_template(self): return self._sub_genre.key @property def title(self): return self._title @property def updated(self): return self._updated @property def uri(self): return self._uri @property def version(self): return self._version tunigo-1.0.0/tunigo/release.py0000644000175000017500000000632312655546175017222 0ustar trygvetrygve00000000000000from __future__ import unicode_literals from tunigo import utils class Release(object): def __init__( self, album_name='', artist_name='', artist_uri='', author_ids=[], created=0, description='', genre_id='', id='', image='', location='', num_tracks=0, publication_date='', regions=[], tags=[], updated=0, uri='', version=0, item_array=None): if item_array: utils.set_instance_array_variables( self, ['_author_ids', '_regions', '_tags'], item_array) utils.set_instance_int_variables( self, ['_created', '_num_tracks', '_updated', '_version'], item_array) utils.set_instance_string_variables( self, ['_album_name', '_artist_name', '_artist_uri', '_description', '_genre_id', '_id', '_image', '_location', '_publication_date', '_uri'], item_array) else: self._album_name = album_name self._artist_name = artist_name self._artist_uri = artist_uri self._author_ids = author_ids self._created = int(created) self._description = description self._genre_id = genre_id self._id = id self._image = image self._location = location self._num_tracks = int(num_tracks) self._publication_date = publication_date self._regions = regions self._tags = tags self._updated = int(updated) self._uri = uri self._version = int(version) def __repr__(self): return "Release(uri='{}')".format(self._uri) def __str__(self): return '{} - {} ({})'.format( self._artist_name, self._album_name, self._uri) @property def album_name(self): return self._album_name @property def artist_name(self): return self._artist_name @property def artist_uri(self): return self._artist_uri @property def author_ids(self): return self._author_ids @property def created(self): return self._created @property def description(self): return self._description @property def genre_id(self): return self._genre_id @property def id(self): return self._id @property def image(self): return self._image @property def location(self): return self._location @property def num_tracks(self): return self._num_tracks @property def publication_date(self): return self._publication_date @property def regions(self): return self._regions @property def tags(self): return self._tags @property def updated(self): return self._updated @property def uri(self): return self._uri @property def version(self): return self._version tunigo-1.0.0/tunigo/translator.py0000644000175000017500000000060212436376176017764 0ustar trygvetrygve00000000000000from __future__ import unicode_literals underscore_to_camelcase_cache = {} def underscore_to_camelcase(word): if word in underscore_to_camelcase_cache: return underscore_to_camelcase_cache[word] else: s = ''.join(part.capitalize() for part in word.split('_')) s = s[0].lower() + s[1:] underscore_to_camelcase_cache[word] = s return s tunigo-1.0.0/tunigo/api.py0000644000175000017500000000706312655556554016357 0ustar trygvetrygve00000000000000from __future__ import unicode_literals import time import requests from tunigo.cache import Cache from tunigo.genre import Genre, SubGenre from tunigo.playlist import Playlist from tunigo.release import Release BASE_URL = 'https://api.tunigo.com/v3/space' BASE_QUERY = 'locale=en&product=premium&version=6.38.31&platform=web' class Tunigo(object): def __init__( self, region=None, max_results=1000, cache_time=3600, proxies=None): self._region = region self._max_results = max_results self._cache = Cache(cache_time) self._proxies = proxies def __repr__(self): return "Tunigo(region='{}', max_results={}, cache_time={})".format( self._region, self._max_results, self._cache._cache_time) def _get(self, key, options=''): uri = ('{}/{}?{}&per_page={}' .format(BASE_URL, key, BASE_QUERY, self._max_results)) if self._region: uri = '{}®ion={}'.format(uri, self._region) if options: uri = '{}&{}'.format(uri, options) result = requests.get(uri, proxies=self._proxies) if (result.status_code != 200 or 'application/json' not in result.headers['content-type']): return [] return result.json()['items'] def get_playlists(self, key, options='', cache_key=''): if not cache_key: cache_key = 'playlists-{}-{}'.format(key, options) cache_value = self._cache.get(cache_key) if cache_value is not None: return cache_value else: playlists = [] for item in self._get(key, options): playlists.append(Playlist(item_array=item['playlist'])) self._cache.insert(cache_key, playlists) return playlists def get_featured_playlists(self): return self.get_playlists( 'featured-playlists', 'dt={}'.format(time.strftime('%FT%H:01:00')), 'featured-playlists') def get_top_lists(self): return self.get_playlists('toplists') def get_genres(self): cache_key = 'genres' cache_value = self._cache.get(cache_key) if cache_value is not None: return cache_value else: genres = [] for item in self._get('genres'): if item['genre']['templateName'] != 'toplists': genres.append(Genre(item_array=item['genre'])) self._cache.insert(cache_key, genres) return genres def get_genre_playlists(self, genre=None, sub_genre=None): if type(genre) == Genre: genre_key = genre.key else: genre_key = genre if type(sub_genre) == SubGenre: sub_genre_key = sub_genre.key if not genre_key: genre_key = sub_genre.main_genre.key else: sub_genre_key = sub_genre if sub_genre_key and sub_genre_key != 'all': options = 'filter={}'.format(sub_genre_key) else: options = '' return self.get_playlists(genre_key, options) def get_new_releases(self): cache_key = 'releases' cache_value = self._cache.get(cache_key) if cache_value is not None: return cache_value else: releases = [] for item in self._get('new-releases'): releases.append(Release(item_array=item['release'])) self._cache.insert(cache_key, releases) return releases tunigo-1.0.0/tunigo/cache.py0000644000175000017500000000136412655546014016635 0ustar trygvetrygve00000000000000from __future__ import unicode_literals import time class Cache(object): def __init__(self, cache_time): self._cache = {} self._cache_time = cache_time def __repr__(self): return 'Cache(cache_time={})'.format(self._cache_time) def _cache_valid(self, key): return (key in self._cache and self._cache[key]['access_time'] > time.time() - self._cache_time) def get(self, key): if self._cache_valid(key): return self._cache[key]['obj'] else: return None def insert(self, key, obj): if self._cache_time: self._cache[key] = { 'access_time': time.time(), 'obj': obj } tunigo-1.0.0/tunigo/__init__.py0000644000175000017500000000045312655633704017332 0ustar trygvetrygve00000000000000from __future__ import unicode_literals from tunigo.api import Tunigo from tunigo.genre import Genre, SubGenre from tunigo.playlist import Playlist from tunigo.release import Release __version__ = '1.0.0' __all__ = [ 'Genre', 'Playlist', 'Release', 'SubGenre', 'Tunigo', ] tunigo-1.0.0/tunigo/genre.py0000644000175000017500000001116012655546175016675 0ustar trygvetrygve00000000000000from __future__ import unicode_literals import tunigo from tunigo import utils class Genre(object): def __init__( self, created=0, header_image_url='', icon_image_url='', icon_url='', id='', location='', mood_image_url='', name='', number_playlists=0, playlist=None, playlist_uri='', sub_genres=[], template_name='', type='', updated=0, version=0, item_array=None): if item_array: utils.set_instance_int_variables( self, ['_created', '_number_playlists', '_updated', '_version'], item_array) utils.set_instance_string_variables( self, ['_header_image_url', '_icon_image_url', '_icon_url', '_id', '_location', '_mood_image_url', '_name', '_template_name', '_type'], item_array) if 'playlistUri' in item_array: self._playlist = tunigo.Playlist( main_genre=self, uri=item_array['playlistUri']) else: self._playlist = tunigo.Playlist(main_genre=self) self._sub_genres = [] if 'subGenres' in item_array: for sub_genre in item_array['subGenres']: self._sub_genres.append(SubGenre( key=sub_genre['key'], main_genre=self, name=sub_genre['name'])) else: self._created = int(created) self._header_image_url = header_image_url self._icon_image_url = icon_image_url self._icon_url = icon_url self._id = id self._location = location self._mood_image_url = mood_image_url self._name = name self._number_playlists = int(number_playlists) if playlist_uri: self._playlist = tunigo.Playlist( main_genre=self, uri=playlist_uri) elif isinstance(playlist, tunigo.Playlist): self._playlist = playlist self._playlist._main_genre = self else: self._playlist = tunigo.Playlist(main_genre=self) self._sub_genres = sub_genres self._template_name = template_name self._type = type self._updated = int(updated) self._version = int(version) def __repr__(self): return "Genre(template_name='{}')".format(self._template_name) def __str__(self): return self._template_name @property def created(self): return self._created @property def header_image_url(self): return self._header_image_url @property def icon_image_url(self): return self._icon_image_url @property def icon_url(self): return self._icon_url @property def id(self): return self._id @property def key(self): return self._template_name @property def location(self): return self._location @property def mood_image_url(self): return self._mood_image_url @property def name(self): return self._name @property def number_playlists(self): return self._number_playlists @property def playlist(self): return self._playlist @property def playlist_uri(self): return self._playlist.uri @property def sub_genres(self): return self._sub_genres @property def template_name(self): return self._template_name @property def type(self): return self._type @property def updated(self): return self._updated @property def version(self): return self._version class SubGenre(object): def __init__(self, key='', main_genre=None, name=''): self._key = key if type(main_genre) == Genre: self._main_genre = main_genre else: self._main_genre = Genre(template_name=main_genre) self._name = name def __repr__(self): return ("SubGenre(main_genre='{}', key='{}')" .format(self._main_genre, self._key)) def __str__(self): return '{}/{}'.format(self._main_genre, self._key) @property def key(self): return self._key @property def main_genre(self): return self._main_genre @property def name(self): return self._name tunigo-1.0.0/tests/0000755000175000017500000000000012655637226015057 5ustar trygvetrygve00000000000000tunigo-1.0.0/tests/test_api.py0000644000175000017500000004067112655546175017253 0ustar trygvetrygve00000000000000from __future__ import unicode_literals import time import responses from tunigo.api import BASE_QUERY, BASE_URL, Tunigo from tunigo.genre import Genre, SubGenre from tunigo.playlist import Playlist from tunigo.release import Release class TestApi(object): @responses.activate def test_repr(self): tunigo = Tunigo(region='no', max_results=100, cache_time=3600) assert ( tunigo.__repr__() == "Tunigo(region='no', max_results=100, cache_time=3600)") @responses.activate def test_returns_items(self): max_results = 100 tunigo = Tunigo(max_results=max_results) responses.add( responses.GET, BASE_URL + '/key', content_type='application/json', body='{"items": [{"test": 1}]}') result = tunigo._get('key') assert len(responses.calls) == 1 assert ( responses.calls[0].request.url == '{}/key?{}&per_page={}'.format( BASE_URL, BASE_QUERY, max_results)) assert len(result) == 1 assert result[0]['test'] == 1 @responses.activate def test_returns_empty_array_if_status_not_200(self): max_results = 100 tunigo = Tunigo(max_results=max_results) responses.add( responses.GET, BASE_URL + '/key', status=404, content_type='application/json', body='{"items": [{"test": 1}]}') result = tunigo._get('key') assert len(responses.calls) == 1 assert ( responses.calls[0].request.url == '{}/key?{}&per_page={}'.format( BASE_URL, BASE_QUERY, max_results)) assert result == [] @responses.activate def test_returns_empty_array_if_content_type_not_application_json(self): max_results = 100 tunigo = Tunigo(max_results=max_results) responses.add( responses.GET, BASE_URL + '/key', body='{"items": [{"test": 1}]}') result = tunigo._get('key') assert len(responses.calls) == 1 assert ( responses.calls[0].request.url == '{}/key?{}&per_page={}'.format( BASE_URL, BASE_QUERY, max_results)) assert result == [] @responses.activate def test_set_given_region_query_option(self): max_results = 100 tunigo = Tunigo(region='no', max_results=max_results) responses.add(responses.GET, BASE_URL + '/key') tunigo._get('key') assert len(responses.calls) == 1 assert ( responses.calls[0].request.url == '{}/key?{}&per_page={}®ion=no'.format( BASE_URL, BASE_QUERY, max_results)) @responses.activate def test_set_given_query_options(self): max_results = 100 tunigo = Tunigo(max_results=max_results) responses.add(responses.GET, BASE_URL + '/key') tunigo._get('key', 'test=value') assert len(responses.calls) == 1 assert ( responses.calls[0].request.url == '{}/key?{}&per_page={}&test=value'.format( BASE_URL, BASE_QUERY, max_results)) @responses.activate def test_returns_playlists(self): max_results = 100 tunigo = Tunigo(max_results=max_results) responses.add( responses.GET, BASE_URL + '/key', content_type='application/json', body=""" { "items": [ { "playlist": { "title": "Title 0", "description": "Description 0", "image": "Image 0", "uri": "uri:0", "numSubscribers": 0 } }, { "playlist": { "title": "Title 1", "description": "Description 1", "image": "Image 1", "uri": "uri:1", "numSubscribers": 1 } } ] }""") playlists = tunigo.get_playlists('key') assert len(responses.calls) == 1 assert ( responses.calls[0].request.url == '{}/key?{}&per_page={}'.format( BASE_URL, BASE_QUERY, max_results)) assert len(playlists) == 2 assert isinstance(playlists[0], Playlist) assert playlists[0].title == 'Title 0' assert playlists[0].description == 'Description 0' assert playlists[0].image == 'Image 0' assert playlists[0].uri == 'uri:0' assert playlists[0].num_subscribers == 0 assert isinstance(playlists[1], Playlist) assert playlists[1].title == 'Title 1' assert playlists[1].description == 'Description 1' assert playlists[1].image == 'Image 1' assert playlists[1].uri == 'uri:1' assert playlists[1].num_subscribers == 1 @responses.activate def test_sets_playlists_query_options(self): max_results = 100 tunigo = Tunigo(max_results=max_results) responses.add(responses.GET, BASE_URL + '/key') tunigo.get_playlists('key', 'test=value') assert len(responses.calls) == 1 assert ( responses.calls[0].request.url == '{}/key?{}&per_page={}&test=value'.format( BASE_URL, BASE_QUERY, max_results)) @responses.activate def test_caches_playlists_result(self): max_results = 100 tunigo = Tunigo(max_results=max_results) responses.add(responses.GET, BASE_URL + '/key') tunigo.get_playlists('key') tunigo.get_playlists('key') assert len(responses.calls) == 1 assert ( responses.calls[0].request.url == '{}/key?{}&per_page={}'.format( BASE_URL, BASE_QUERY, max_results)) @responses.activate def test_featured_playlist_calls_uri(self): max_results = 100 tunigo = Tunigo(max_results=max_results) responses.add(responses.GET, BASE_URL + '/featured-playlists') tunigo.get_featured_playlists() assert len(responses.calls) == 1 assert ( responses.calls[0].request.url == '{}/featured-playlists?{}&per_page={}&dt={}'.format( BASE_URL, BASE_QUERY, max_results, time.strftime('%FT%H:01:00'))) @responses.activate def test_top_lists_calls_uri(self): max_results = 100 tunigo = Tunigo(max_results=max_results) responses.add(responses.GET, BASE_URL + '/toplists') tunigo.get_top_lists() assert len(responses.calls) == 1 assert ( responses.calls[0].request.url == '{}/toplists?{}&per_page={}'.format( BASE_URL, BASE_QUERY, max_results)) @responses.activate def test_returns_genres(self): max_results = 100 tunigo = Tunigo(max_results=max_results) responses.add( responses.GET, BASE_URL + '/genres', content_type='application/json', body=""" { "items": [ { "genre": { "name": "Genre 0", "id": "Id 0", "type": "Type 0", "templateName": "Template name 0", "iconImageUrl": "Icon image url 0", "iconUrl": "Icon url 0", "moodImageUrl": "Mood image url 0", "headerImageUrl": "Header image url 0", "subGenres": [ { "name": "Genre 0, subgenre 0", "key": "Key 0, 0" }, { "name": "Genre 0, subgenre 1", "key": "Key 0, 1" } ] } }, { "genre": { "name": "Genre 1", "id": "Id 1", "type": "Type 1", "templateName": "Template name 1", "iconImageUrl": "Icon image url 1", "iconUrl": "Icon url 1", "moodImageUrl": "Mood image url 1", "headerImageUrl": "Header image url 1", "subGenres": [ { "name": "Genre 1, subgenre 0", "key": "Key 1, 0" }, { "name": "Genre 1, subgenre 1", "key": "Key 1, 1" } ] } } ] }""") genres = tunigo.get_genres() assert len(responses.calls) == 1 assert ( responses.calls[0].request.url == '{}/genres?{}&per_page={}'.format( BASE_URL, BASE_QUERY, max_results)) assert len(genres) == 2 assert isinstance(genres[0], Genre) assert genres[0].name == 'Genre 0' assert genres[0].id == 'Id 0' assert genres[0].type == 'Type 0' assert genres[0].template_name == 'Template name 0' assert genres[0].icon_image_url == 'Icon image url 0' assert genres[0].icon_url == 'Icon url 0' assert genres[0].mood_image_url == 'Mood image url 0' assert genres[0].header_image_url == 'Header image url 0' assert len(genres[0].sub_genres) == 2 assert isinstance(genres[0].sub_genres[0], SubGenre) assert genres[0].sub_genres[0].name == 'Genre 0, subgenre 0' assert genres[0].sub_genres[0].key == 'Key 0, 0' assert isinstance(genres[0].sub_genres[1], SubGenre) assert genres[0].sub_genres[1].name == 'Genre 0, subgenre 1' assert genres[0].sub_genres[1].key == 'Key 0, 1' assert isinstance(genres[0], Genre) assert genres[1].name == 'Genre 1' assert genres[1].id == 'Id 1' assert genres[1].type == 'Type 1' assert genres[1].template_name == 'Template name 1' assert genres[1].icon_image_url == 'Icon image url 1' assert genres[1].icon_url == 'Icon url 1' assert genres[1].mood_image_url == 'Mood image url 1' assert genres[1].header_image_url == 'Header image url 1' assert len(genres[1].sub_genres) == 2 assert isinstance(genres[1].sub_genres[0], SubGenre) assert genres[1].sub_genres[0].name == 'Genre 1, subgenre 0' assert genres[1].sub_genres[0].key == 'Key 1, 0' assert isinstance(genres[1].sub_genres[1], SubGenre) assert genres[1].sub_genres[1].name == 'Genre 1, subgenre 1' assert genres[1].sub_genres[1].key == 'Key 1, 1' @responses.activate def test_caches_genres_result(self): max_results = 100 tunigo = Tunigo(max_results=max_results) responses.add(responses.GET, BASE_URL + '/genres') tunigo.get_genres() tunigo.get_genres() assert len(responses.calls) == 1 assert ( responses.calls[0].request.url == '{}/genres?{}&per_page={}'.format( BASE_URL, BASE_QUERY, max_results)) @responses.activate def test_genre_playlists_with_genre_string_calls_uri(self): max_results = 100 tunigo = Tunigo(max_results=max_results) responses.add(responses.GET, BASE_URL + '/genre') tunigo.get_genre_playlists('genre') assert len(responses.calls) == 1 assert ( responses.calls[0].request.url == '{}/genre?{}&per_page={}'.format( BASE_URL, BASE_QUERY, max_results)) @responses.activate def test_genre_playlists_with_genre_and_subgenre_string_calls_uri(self): max_results = 100 tunigo = Tunigo(max_results=max_results) responses.add(responses.GET, BASE_URL + '/genre') tunigo.get_genre_playlists('genre', 'subgenre') assert len(responses.calls) == 1 assert ( responses.calls[0].request.url == '{}/genre?{}&per_page={}&filter=subgenre'.format( BASE_URL, BASE_QUERY, max_results)) @responses.activate def test_genre_playlists_with_genre_instance_calls_uri(self): max_results = 100 tunigo = Tunigo(max_results=max_results) responses.add(responses.GET, BASE_URL + '/genre') tunigo.get_genre_playlists(Genre(template_name='genre')) assert len(responses.calls) == 1 assert ( responses.calls[0].request.url == '{}/genre?{}&per_page={}'.format( BASE_URL, BASE_QUERY, max_results)) @responses.activate def test_genre_playlists_with_genre_and_subgenre_instance_calls_uri(self): max_results = 100 tunigo = Tunigo(max_results=max_results) responses.add(responses.GET, BASE_URL + '/genre') tunigo.get_genre_playlists( Genre(template_name='genre'), SubGenre(key='subgenre')) assert len(responses.calls) == 1 assert ( responses.calls[0].request.url == '{}/genre?{}&per_page={}&filter=subgenre'.format( BASE_URL, BASE_QUERY, max_results)) @responses.activate def test_genre_playlists_with_only_subgenre_instance_calls_uri(self): max_results = 100 tunigo = Tunigo(max_results=max_results) responses.add(responses.GET, BASE_URL + '/genre') tunigo.get_genre_playlists( sub_genre=SubGenre(key='subgenre', main_genre='genre')) assert len(responses.calls) == 1 assert ( responses.calls[0].request.url == '{}/genre?{}&per_page={}&filter=subgenre'.format( BASE_URL, BASE_QUERY, max_results)) @responses.activate def test_returns_releases(self): max_results = 100 tunigo = Tunigo(max_results=max_results) responses.add( responses.GET, BASE_URL + '/new-releases', content_type='application/json', body=""" { "items": [ { "release": { "albumName": "Album 0", "uri": "uri:0", "artistName": "Artist 0", "image": "Image 0", "artistUri": "artist:uri:0" } }, { "release": { "albumName": "Album 1", "uri": "uri:1", "artistName": "Artist 1", "image": "Image 1", "artistUri": "artist:uri:1" } } ] }""") releases = tunigo.get_new_releases() assert len(responses.calls) == 1 assert ( responses.calls[0].request.url == '{}/new-releases?{}&per_page={}'.format( BASE_URL, BASE_QUERY, max_results)) assert len(releases) == 2 assert isinstance(releases[0], Release) assert releases[0].album_name == 'Album 0' assert releases[0].uri == 'uri:0' assert releases[0].artist_name == 'Artist 0' assert releases[0].image == 'Image 0' assert releases[0].artist_uri == 'artist:uri:0' assert isinstance(releases[1], Release) assert releases[1].album_name == 'Album 1' assert releases[1].uri == 'uri:1' assert releases[1].artist_name == 'Artist 1' assert releases[1].image == 'Image 1' assert releases[1].artist_uri == 'artist:uri:1' @responses.activate def test_caches_releases_result(self): max_results = 100 tunigo = Tunigo(max_results=max_results) responses.add(responses.GET, BASE_URL + '/new-releases') tunigo.get_new_releases() tunigo.get_new_releases() assert len(responses.calls) == 1 assert ( responses.calls[0].request.url == '{}/new-releases?{}&per_page={}'.format( BASE_URL, BASE_QUERY, max_results)) tunigo-1.0.0/tests/test_playlist.py0000644000175000017500000001000012655546175020322 0ustar trygvetrygve00000000000000from __future__ import unicode_literals from tunigo.genre import Genre, SubGenre from tunigo.playlist import Playlist class TestPlaylist(object): def test_repr(self): playlist = Playlist(uri='some:uri') assert playlist.__repr__() == "Playlist(uri='some:uri')" def test_str(self): playlist = Playlist(title='Title', uri='some:uri') assert playlist.__str__() == 'Title (some:uri)' def test_creates_instance_from_item_array(self): playlist = Playlist(item_array={ 'created': 1, 'description': 'Some description', 'id': 'Some id', 'image': 'Some image', 'location': 'Some location', 'numSubscribers': 2, 'title': 'Some title', 'updated': 3, 'uri': 'some:uri', 'version': 4 }) assert playlist.created == 1 assert playlist.description == 'Some description' assert playlist.id == 'Some id' assert playlist.image == 'Some image' assert playlist.location == 'Some location' assert isinstance(playlist.main_genre, Genre) assert playlist.main_genre.playlist == playlist assert playlist.num_subscribers == 2 assert isinstance(playlist.sub_genre, SubGenre) assert playlist.sub_genre.main_genre == playlist.main_genre assert playlist.title == 'Some title' assert playlist.updated == 3 assert playlist.uri == 'some:uri' assert playlist.version == 4 def test_creates_genre_from_template_in_item_array(self): playlist = Playlist(item_array={ 'mainGenreTemplate': 'Some main genre template', 'subGenreTemplate': 'Some sub genre template' }) assert isinstance(playlist.main_genre, Genre) assert playlist.main_genre.playlist == playlist assert playlist.main_genre.template_name == 'Some main genre template' assert playlist.main_genre_template == 'Some main genre template' assert isinstance(playlist.sub_genre, SubGenre) assert playlist.sub_genre.main_genre == playlist.main_genre assert playlist.sub_genre.key == 'Some sub genre template' assert playlist.sub_genre_template == 'Some sub genre template' def test_creates_genre_from_template_in_arguments(self): playlist = Playlist( main_genre_template='Some main genre template', sub_genre_template='Some sub genre template' ) assert isinstance(playlist.main_genre, Genre) assert playlist.main_genre.playlist == playlist assert playlist.main_genre.template_name == 'Some main genre template' assert playlist.main_genre_template == 'Some main genre template' assert isinstance(playlist.sub_genre, SubGenre) assert playlist.sub_genre.main_genre == playlist.main_genre assert playlist.sub_genre.key == 'Some sub genre template' assert playlist.sub_genre_template == 'Some sub genre template' def test_sets_genre_to_given_genre_instance_in_arguments(self): genre = Genre(template_name='Some main genre template') sub_genre = SubGenre(key='Some sub genre template') playlist = Playlist(main_genre=genre, sub_genre=sub_genre) assert isinstance(playlist.main_genre, Genre) assert playlist.main_genre.playlist == playlist assert playlist.main_genre == genre assert playlist.main_genre_template == 'Some main genre template' assert isinstance(playlist.sub_genre, SubGenre) assert playlist.sub_genre.main_genre == playlist.main_genre assert playlist.sub_genre == sub_genre assert playlist.sub_genre_template == 'Some sub genre template' def test_creates_empty_genre_if_not_given(self): playlist = Playlist() assert isinstance(playlist.main_genre, Genre) assert playlist.main_genre.playlist == playlist assert isinstance(playlist.sub_genre, SubGenre) assert playlist.sub_genre.main_genre == playlist.main_genre tunigo-1.0.0/tests/test_release.py0000644000175000017500000000363512655546175020121 0ustar trygvetrygve00000000000000from __future__ import unicode_literals from tunigo.release import Release class TestRelease(object): def test_repr(self): release = Release(uri='some:uri') assert release.__repr__() == "Release(uri='some:uri')" def test_str(self): release = Release( artist_name='Some artist', album_name='Some album', uri='some:uri') assert release.__str__() == 'Some artist - Some album (some:uri)' def test_creates_instance_from_item_array(self): release = Release(item_array={ 'albumName': 'Some Album', 'artistName': 'Some Artist', 'artistUri': 'artist:uri', 'authorIds': [1, 2], 'created': 3, 'description': 'Some description', 'genreId': 'Some genre', 'id': 'Some id', 'image': 'Some image', 'location': 'Some location', 'numTracks': 4, 'publicationDate': '2016-02-06', 'regions': ['no', 'us'], 'tags': ['Tag 1', 'Tag 2'], 'updated': 5, 'uri': 'some:uri', 'version': 6 }) assert release.album_name == 'Some Album' assert release.artist_name == 'Some Artist' assert release.artist_uri == 'artist:uri' assert release.author_ids == [1, 2] assert release.created == 3 assert release.description == 'Some description' assert release.genre_id == 'Some genre' assert release.id == 'Some id' assert release.image == 'Some image' assert release.location == 'Some location' assert release.num_tracks == 4 assert release.publication_date == '2016-02-06' assert release.regions == ['no', 'us'] assert release.tags == ['Tag 1', 'Tag 2'] assert release.updated == 5 assert release.uri == 'some:uri' assert release.version == 6 tunigo-1.0.0/tests/test_genre.py0000644000175000017500000001126312655546175017575 0ustar trygvetrygve00000000000000from __future__ import unicode_literals from tunigo.genre import Genre, SubGenre from tunigo.playlist import Playlist class TestGenre(object): def test_repr(self): genre = Genre(template_name='genre') assert genre.__repr__() == "Genre(template_name='genre')" def test_str(self): genre = Genre(template_name='genre') assert genre.__str__() == 'genre' def test_creates_instance_from_item_array(self): genre = Genre(item_array={ 'created': 1, 'headerImageUrl': 'http://some.header.image', 'iconImageUrl': 'http://some.icon.image', 'iconUrl': 'http://some.icon', 'id': 'Some id', 'location': 'Some location', 'moodImageUrl': 'http://some.mood.image', 'name': 'Some name', 'numberPlaylists': 2, 'templateName': 'Some template name', 'type': 'Some type', 'updated': 3, 'version': 4 }) assert genre.created == 1 assert genre.header_image_url == 'http://some.header.image' assert genre.icon_image_url == 'http://some.icon.image' assert genre.icon_url == 'http://some.icon' assert genre.id == 'Some id' assert genre.location == 'Some location' assert genre.mood_image_url == 'http://some.mood.image' assert genre.name == 'Some name' assert genre.number_playlists == 2 assert isinstance(genre.playlist, Playlist) assert genre.playlist.main_genre == genre assert genre.sub_genres == [] assert genre.template_name == 'Some template name' assert genre.type == 'Some type' assert genre.updated == 3 assert genre.version == 4 def test_creates_playlist_from_playlist_uri_in_item_array(self): genre = Genre(item_array={ 'playlistUri': 'some:playlist:uri' }) assert isinstance(genre.playlist, Playlist) assert genre.playlist.main_genre == genre assert genre.playlist.uri == 'some:playlist:uri' assert genre.playlist_uri == 'some:playlist:uri' def test_creates_playlist_from_playlist_uri_in_arguments(self): genre = Genre(playlist_uri='some:playlist:uri') assert isinstance(genre.playlist, Playlist) assert genre.playlist.main_genre == genre assert genre.playlist.uri == 'some:playlist:uri' assert genre.playlist_uri == 'some:playlist:uri' def test_sets_playlist_to_given_playlist_instance_in_arguments(self): playlist = Playlist(uri='some:playlist:uri') genre = Genre(playlist=playlist) assert isinstance(genre.playlist, Playlist) assert genre.playlist.main_genre == genre assert genre.playlist == playlist assert genre.playlist_uri == 'some:playlist:uri' def test_creates_empty_playlist_if_not_given(self): genre = Genre() assert isinstance(genre.playlist, Playlist) assert genre.playlist.main_genre == genre def test_creates_sub_genres_from_sub_genres_in_item_array(self): genre = Genre(item_array={ 'subGenres': [{ 'key': 'Key 0', 'name': 'Name 0' }, { 'key': 'Key 1', 'name': 'Name 1' }] }) assert len(genre.sub_genres) == 2 assert isinstance(genre.sub_genres[0], SubGenre) assert genre.sub_genres[0].main_genre == genre assert genre.sub_genres[0].key == 'Key 0' assert genre.sub_genres[0].name == 'Name 0' assert isinstance(genre.sub_genres[1], SubGenre) assert genre.sub_genres[1].main_genre == genre assert genre.sub_genres[1].key == 'Key 1' assert genre.sub_genres[1].name == 'Name 1' class TestSubGenre(object): def test_repr(self): sub_genre = SubGenre(key='sub_genre', main_genre='genre') assert ( sub_genre.__repr__() == "SubGenre(main_genre='genre', key='sub_genre')") def test_str(self): sub_genre = SubGenre(key='sub_genre', main_genre='genre') assert sub_genre.__str__() == 'genre/sub_genre' def test_creates_empty_main_genre_if_not_given(self): sub_genre = SubGenre() assert isinstance(sub_genre.main_genre, Genre) def test_sets_main_genre_to_given_genre_instance(self): genre = Genre(template_name='Some genre') sub_genre = SubGenre(main_genre=genre) assert sub_genre.main_genre == genre def test_creates_main_genre_from_given_name(self): sub_genre = SubGenre(main_genre='Some genre') assert isinstance(sub_genre.main_genre, Genre) assert sub_genre.main_genre.template_name == 'Some genre' tunigo-1.0.0/tests/__init__.py0000644000175000017500000000000012436376176017157 0ustar trygvetrygve00000000000000tunigo-1.0.0/tests/test_cache.py0000644000175000017500000000215312655546175017536 0ustar trygvetrygve00000000000000from __future__ import unicode_literals import time import mock from tunigo.cache import Cache class TestCache(object): def test_repr(self): cache = Cache(cache_time=3600) assert cache.__repr__() == 'Cache(cache_time=3600)' def test_returns_none_for_unknown_key(self): cache = Cache(100) assert cache.get('key') is None def test_returns_value_for_known_key(self): cache = Cache(100) cache.insert('key', 'value') assert cache.get('key') == 'value' def test_returns_value_for_non_expired_key(self): cache = Cache(100) cache.insert('key', 'value') expire_time = time.time() + 99 with mock.patch('time.time') as time_mock: time_mock.return_value = expire_time assert cache.get('key') == 'value' def test_returns_none_for_expired_key(self): cache = Cache(100) cache.insert('key', 'value') expire_time = time.time() + 100 with mock.patch('time.time') as time_mock: time_mock.return_value = expire_time assert cache.get('key') is None tunigo-1.0.0/tests/test_translator.py0000644000175000017500000000044612655546175020667 0ustar trygvetrygve00000000000000from __future__ import unicode_literals from tunigo import translator class TestUndescoreToCamelCase(object): def test_returns_converted_to_camel_case(self): word = translator.underscore_to_camelcase('some_word_with_underscore') assert word == 'someWordWithUnderscore' tunigo-1.0.0/tests/test_utils.py0000644000175000017500000000366312655546175017642 0ustar trygvetrygve00000000000000from __future__ import unicode_literals from tunigo import utils from tunigo.release import Release class TestSetInstanceArrayVariables(object): def test_sets_variables(self): release = Release() utils.set_instance_array_variables( release, ['_author_ids', '_regions'], { 'authorIds': [1, 2], 'regions': ['no', 'us'] }) assert release.author_ids == [1, 2] assert release.regions == ['no', 'us'] def test_sets_missing_variables_to_empty_array(self): release = Release(author_ids=None) utils.set_instance_array_variables(release, ['_author_ids'], {}) assert release.author_ids == [] class TestSetInstanceIntVariables(object): def test_sets_variables(self): release = Release() utils.set_instance_int_variables( release, ['_num_tracks', '_version'], { 'numTracks': 4, 'version': 6 }) assert release.num_tracks == 4 assert release.version == 6 def test_sets_missing_variables_to_0(self): release = Release(num_tracks=1) utils.set_instance_int_variables(release, ['_num_tracks'], {}) assert release.num_tracks == 0 class TestSetInstanceStringVariables(object): def test_sets_variables(self): release = Release() utils.set_instance_string_variables( release, ['_album_name', '_artist_name'], { 'albumName': 'Some Album', 'artistName': 'Some Artist' }) assert release.album_name == 'Some Album' assert release.artist_name == 'Some Artist' def test_sets_missing_variables_to_empty_string(self): release = Release(album_name=None) utils.set_instance_string_variables(release, ['_album_name'], {}) assert release.album_name == ''