tunigo-1.0.0/ 0000755 0001750 0001750 00000000000 12655637226 013715 5 ustar trygve trygve 0000000 0000000 tunigo-1.0.0/.travis.yml 0000644 0001750 0001750 00000001007 12655635161 016020 0 ustar trygve trygve 0000000 0000000 sudo: 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.ini 0000644 0001750 0001750 00000000647 12655632275 015236 0 ustar trygve trygve 0000000 0000000 [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/LICENSE 0000644 0001750 0001750 00000026136 12436376176 014733 0 ustar trygve trygve 0000000 0000000
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-INFO 0000644 0001750 0001750 00000011756 12655637226 015024 0 ustar trygve trygve 0000000 0000000 Metadata-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/ 0000755 0001750 0001750 00000000000 12655637226 015533 5 ustar trygve trygve 0000000 0000000 tunigo-1.0.0/examples/playlists.py 0000644 0001750 0001750 00000000722 12655546014 020124 0 ustar trygve trygve 0000000 0000000 from 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.py 0000644 0001750 0001750 00000001431 12655546014 017361 0 ustar trygve trygve 0000000 0000000 from 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.py 0000644 0001750 0001750 00000000647 12655546014 020562 0 ustar trygve trygve 0000000 0000000 from 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.py 0000644 0001750 0001750 00000000521 12655546014 020215 0 ustar trygve trygve 0000000 0000000 from 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.py 0000644 0001750 0001750 00000002756 12655632400 015426 0 ustar trygve trygve 0000000 0000000 from __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.cfg 0000644 0001750 0001750 00000000230 12655637226 015531 0 ustar trygve trygve 0000000 0000000 [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.txt 0000644 0001750 0001750 00000000216 12655546175 017756 0 ustar trygve trygve 0000000 0000000 # 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.in 0000644 0001750 0001750 00000000316 12655546175 015455 0 ustar trygve trygve 0000000 0000000 include *.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/ 0000755 0001750 0001750 00000000000 12655637226 016714 5 ustar trygve trygve 0000000 0000000 tunigo-1.0.0/tunigo.egg-info/PKG-INFO 0000644 0001750 0001750 00000011756 12655637226 020023 0 ustar trygve trygve 0000000 0000000 Metadata-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.txt 0000644 0001750 0001750 00000000035 12655637226 021312 0 ustar trygve trygve 0000000 0000000 requests >= 2.0.0
setuptools
tunigo-1.0.0/tunigo.egg-info/top_level.txt 0000644 0001750 0001750 00000000007 12655637226 021443 0 ustar trygve trygve 0000000 0000000 tunigo
tunigo-1.0.0/tunigo.egg-info/dependency_links.txt 0000644 0001750 0001750 00000000001 12655637226 022762 0 ustar trygve trygve 0000000 0000000
tunigo-1.0.0/tunigo.egg-info/SOURCES.txt 0000644 0001750 0001750 00000001226 12655637226 020601 0 ustar trygve trygve 0000000 0000000 .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.txt tunigo-1.0.0/tunigo.egg-info/not-zip-safe 0000644 0001750 0001750 00000000001 12655326256 021140 0 ustar trygve trygve 0000000 0000000
tunigo-1.0.0/README.rst 0000644 0001750 0001750 00000006206 12655633704 015405 0 ustar trygve trygve 0000000 0000000 *************
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/ 0000755 0001750 0001750 00000000000 12655637226 015222 5 ustar trygve trygve 0000000 0000000 tunigo-1.0.0/tunigo/utils.py 0000644 0001750 0001750 00000001507 12655546175 016741 0 ustar trygve trygve 0000000 0000000 from __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.py 0000644 0001750 0001750 00000007512 12655546175 017444 0 ustar trygve trygve 0000000 0000000 from __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.py 0000644 0001750 0001750 00000006323 12655546175 017222 0 ustar trygve trygve 0000000 0000000 from __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.py 0000644 0001750 0001750 00000000602 12436376176 017764 0 ustar trygve trygve 0000000 0000000 from __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.py 0000644 0001750 0001750 00000007063 12655556554 016357 0 ustar trygve trygve 0000000 0000000 from __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.py 0000644 0001750 0001750 00000001364 12655546014 016635 0 ustar trygve trygve 0000000 0000000 from __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__.py 0000644 0001750 0001750 00000000453 12655633704 017332 0 ustar trygve trygve 0000000 0000000 from __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.py 0000644 0001750 0001750 00000011160 12655546175 016675 0 ustar trygve trygve 0000000 0000000 from __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/ 0000755 0001750 0001750 00000000000 12655637226 015057 5 ustar trygve trygve 0000000 0000000 tunigo-1.0.0/tests/test_api.py 0000644 0001750 0001750 00000040671 12655546175 017253 0 ustar trygve trygve 0000000 0000000 from __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.py 0000644 0001750 0001750 00000010000 12655546175 020322 0 ustar trygve trygve 0000000 0000000 from __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.py 0000644 0001750 0001750 00000003635 12655546175 020121 0 ustar trygve trygve 0000000 0000000 from __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.py 0000644 0001750 0001750 00000011263 12655546175 017575 0 ustar trygve trygve 0000000 0000000 from __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__.py 0000644 0001750 0001750 00000000000 12436376176 017157 0 ustar trygve trygve 0000000 0000000 tunigo-1.0.0/tests/test_cache.py 0000644 0001750 0001750 00000002153 12655546175 017536 0 ustar trygve trygve 0000000 0000000 from __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.py 0000644 0001750 0001750 00000000446 12655546175 020667 0 ustar trygve trygve 0000000 0000000 from __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.py 0000644 0001750 0001750 00000003663 12655546175 017642 0 ustar trygve trygve 0000000 0000000 from __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 == ''